From 41f89a38b7594f54ee9906bc91051874a60b690d Mon Sep 17 00:00:00 2001 From: Damien Arrachequesne Date: Wed, 9 Dec 2020 01:57:52 +0100 Subject: [PATCH] feat: add support for Engine.IO v4 Reference: https://github.com/socketio/engine.io-protocol#difference-between-v3-and-v4 --- .../io/socket/engineio/client/Socket.java | 65 +- .../io/socket/engineio/client/Transport.java | 9 +- .../engineio/client/transports/Polling.java | 31 +- .../client/transports/PollingXHR.java | 48 +- .../engineio/client/transports/WebSocket.java | 15 +- .../io/socket/engineio/parser/Base64.java | 665 ++++++++++++++++++ .../io/socket/engineio/parser/Parser.java | 318 ++------- src/main/java/io/socket/utf8/UTF8.java | 199 ------ .../java/io/socket/utf8/UTF8Exception.java | 22 - .../engineio/client/ConnectionTest.java | 16 + .../io/socket/engineio/parser/ParserTest.java | 107 +-- src/test/java/io/socket/utf8/UTF8Test.java | 114 --- src/test/resources/package-lock.json | 82 +-- src/test/resources/package.json | 2 +- 14 files changed, 829 insertions(+), 864 deletions(-) create mode 100644 src/main/java/io/socket/engineio/parser/Base64.java delete mode 100644 src/main/java/io/socket/utf8/UTF8.java delete mode 100644 src/main/java/io/socket/utf8/UTF8Exception.java delete mode 100644 src/test/java/io/socket/utf8/UTF8Test.java diff --git a/src/main/java/io/socket/engineio/client/Socket.java b/src/main/java/io/socket/engineio/client/Socket.java index 3f61f9a..9b0423e 100644 --- a/src/main/java/io/socket/engineio/client/Socket.java +++ b/src/main/java/io/socket/engineio/client/Socket.java @@ -128,7 +128,6 @@ public class Socket extends Emitter { /*package*/ LinkedList writeBuffer = new LinkedList(); /*package*/ Transport transport; private Future pingTimeoutTimer; - private Future pingIntervalTimer; private okhttp3.WebSocket.Factory webSocketFactory; private okhttp3.Call.Factory callFactory; @@ -137,7 +136,7 @@ public class Socket extends Emitter { private final Listener onHeartbeatAsListener = new Listener() { @Override public void call(Object... args) { - Socket.this.onHeartbeat(args.length > 0 ? (Long)args[0]: 0); + Socket.this.onHeartbeat(); } }; @@ -540,9 +539,14 @@ public class Socket extends Emitter { } catch (JSONException e) { this.emit(EVENT_ERROR, new EngineIOException(e)); } - } else if (Packet.PONG.equals(packet.type)) { - this.setPing(); - this.emit(EVENT_PONG); + } else if (Packet.PING.equals(packet.type)) { + this.emit(EVENT_PING); + EventThread.exec(new Runnable() { + @Override + public void run() { + Socket.this.sendPacket(Packet.PONG, null); + } + }); } else if (Packet.ERROR.equals(packet.type)) { EngineIOException err = new EngineIOException("server error"); err.code = packet.data; @@ -568,20 +572,18 @@ public class Socket extends Emitter { this.onOpen(); // In case open handler closes socket if (ReadyState.CLOSED == this.readyState) return; - this.setPing(); + this.onHeartbeat(); this.off(EVENT_HEARTBEAT, this.onHeartbeatAsListener); this.on(EVENT_HEARTBEAT, this.onHeartbeatAsListener); } - private void onHeartbeat(long timeout) { + private void onHeartbeat() { if (this.pingTimeoutTimer != null) { pingTimeoutTimer.cancel(false); } - if (timeout <= 0) { - timeout = this.pingInterval + this.pingTimeout; - } + long timeout = this.pingInterval + this.pingTimeout; final Socket self = this; this.pingTimeoutTimer = this.getHeartbeatScheduler().schedule(new Runnable() { @@ -598,46 +600,6 @@ public class Socket extends Emitter { }, timeout, TimeUnit.MILLISECONDS); } - private void setPing() { - if (this.pingIntervalTimer != null) { - pingIntervalTimer.cancel(false); - } - - final Socket self = this; - this.pingIntervalTimer = this.getHeartbeatScheduler().schedule(new Runnable() { - @Override - public void run() { - EventThread.exec(new Runnable() { - @Override - public void run() { - if (logger.isLoggable(Level.FINE)) { - logger.fine(String.format("writing ping packet - expecting pong within %sms", self.pingTimeout)); - } - self.ping(); - self.onHeartbeat(self.pingTimeout); - } - }); - } - }, this.pingInterval, TimeUnit.MILLISECONDS); - } - - /** - * Sends a ping packet. - */ - private void ping() { - EventThread.exec(new Runnable() { - @Override - public void run() { - Socket.this.sendPacket(Packet.PING, new Runnable() { - @Override - public void run() { - Socket.this.emit(EVENT_PING); - } - }); - } - }); - } - private void onDrain() { for (int i = 0; i < this.prevBufferLen; i++) { this.writeBuffer.poll(); @@ -833,9 +795,6 @@ public class Socket extends Emitter { final Socket self = this; // clear timers - if (this.pingIntervalTimer != null) { - this.pingIntervalTimer.cancel(false); - } if (this.pingTimeoutTimer != null) { this.pingTimeoutTimer.cancel(false); } diff --git a/src/main/java/io/socket/engineio/client/Transport.java b/src/main/java/io/socket/engineio/client/Transport.java index 442c776..56b921c 100644 --- a/src/main/java/io/socket/engineio/client/Transport.java +++ b/src/main/java/io/socket/engineio/client/Transport.java @@ -7,7 +7,6 @@ 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 okhttp3.Call; import okhttp3.WebSocket; @@ -96,11 +95,7 @@ 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); - } + Transport.this.write(packets); } else { throw new RuntimeException("Transport not open"); } @@ -131,7 +126,7 @@ public abstract class Transport extends Emitter { this.emit(EVENT_CLOSE); } - abstract protected void write(Packet[] packets) throws UTF8Exception; + abstract protected void write(Packet[] packets); abstract protected void doOpen(); diff --git a/src/main/java/io/socket/engineio/client/transports/Polling.java b/src/main/java/io/socket/engineio/client/transports/Polling.java index 9341d9a..9aa73cc 100644 --- a/src/main/java/io/socket/engineio/client/transports/Polling.java +++ b/src/main/java/io/socket/engineio/client/transports/Polling.java @@ -7,7 +7,6 @@ import io.socket.engineio.parser.Packet; import io.socket.engineio.parser.Parser; import io.socket.parseqs.ParseQS; import io.socket.thread.EventThread; -import io.socket.utf8.UTF8Exception; import io.socket.yeast.Yeast; import java.util.HashMap; @@ -129,13 +128,7 @@ abstract public class Polling extends Transport { } }; - if (data instanceof String) { - @SuppressWarnings("unchecked") - Parser.DecodePayloadCallback tempCallback = callback; - Parser.decodePayload((String)data, tempCallback); - } else if (data instanceof byte[]) { - Parser.decodePayload((byte[])data, callback); - } + Parser.decodePayload((String) data, callback); if (this.readyState != ReadyState.CLOSED) { this.polling = false; @@ -158,11 +151,7 @@ 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); - } + self.write(new Packet[]{new Packet(Packet.CLOSE)}); } }; @@ -177,7 +166,7 @@ abstract public class Polling extends Transport { } } - protected void write(Packet[] packets) throws UTF8Exception { + protected void write(Packet[] packets) { final Polling self = this; this.writable = false; final Runnable callbackfn = new Runnable() { @@ -188,16 +177,10 @@ abstract public class Polling extends Transport { } }; - Parser.encodePayload(packets, new Parser.EncodeCallback() { + Parser.encodePayload(packets, new Parser.EncodeCallback() { @Override - public void call(Object data) { - if (data instanceof byte[]) { - self.doWrite((byte[])data, callbackfn); - } else if (data instanceof String) { - self.doWrite((String)data, callbackfn); - } else { - logger.warning("Unexpected data: " + data); - } + public void call(String data) { + self.doWrite(data, callbackfn); } }); } @@ -229,8 +212,6 @@ abstract public class Polling extends Transport { return schema + "://" + (ipv6 ? "[" + this.hostname + "]" : this.hostname) + port + this.path + derivedQuery; } - abstract protected void doWrite(byte[] data, Runnable fn); - abstract protected void doWrite(String data, Runnable fn); abstract protected void doPoll(); diff --git a/src/main/java/io/socket/engineio/client/transports/PollingXHR.java b/src/main/java/io/socket/engineio/client/transports/PollingXHR.java index 827c24f..64b3cda 100644 --- a/src/main/java/io/socket/engineio/client/transports/PollingXHR.java +++ b/src/main/java/io/socket/engineio/client/transports/PollingXHR.java @@ -67,17 +67,8 @@ public class PollingXHR extends Polling { return req; } - @Override - protected void doWrite(byte[] data, final Runnable fn) { - this.doWrite((Object) data, fn); - } - @Override protected void doWrite(String data, final Runnable fn) { - this.doWrite((Object) data, fn); - } - - private void doWrite(Object data, final Runnable fn) { Request.Options opts = new Request.Options(); opts.method = "POST"; opts.data = data; @@ -121,11 +112,7 @@ public class PollingXHR extends Polling { @Override public void run() { Object arg = args.length > 0 ? args[0] : null; - if (arg instanceof String) { - self.onData((String)arg); - } else if (arg instanceof byte[]) { - self.onData((byte[])arg); - } + self.onData((String)arg); } }); } @@ -153,16 +140,14 @@ public class PollingXHR extends Polling { public static final String EVENT_REQUEST_HEADERS = "requestHeaders"; public static final String EVENT_RESPONSE_HEADERS = "responseHeaders"; - private static final String BINARY_CONTENT_TYPE = "application/octet-stream"; private static final String TEXT_CONTENT_TYPE = "text/plain;charset=UTF-8"; - private static final MediaType BINARY_MEDIA_TYPE = MediaType.parse(BINARY_CONTENT_TYPE); private static final MediaType TEXT_MEDIA_TYPE = MediaType.parse(TEXT_CONTENT_TYPE); private String method; private String uri; - private Object data; + private String data; private Call.Factory callFactory; private Response response; @@ -181,11 +166,7 @@ public class PollingXHR extends Polling { Map> headers = new TreeMap>(String.CASE_INSENSITIVE_ORDER); if ("POST".equals(this.method)) { - if (this.data instanceof byte[]) { - headers.put("Content-type", new LinkedList(Collections.singletonList(BINARY_CONTENT_TYPE))); - } else { - headers.put("Content-type", new LinkedList(Collections.singletonList(TEXT_CONTENT_TYPE))); - } + headers.put("Content-type", new LinkedList(Collections.singletonList(TEXT_CONTENT_TYPE))); } headers.put("Accept", new LinkedList(Collections.singletonList("*/*"))); @@ -193,8 +174,7 @@ public class PollingXHR extends Polling { this.onRequestHeaders(headers); if (LOGGABLE_FINE) { - logger.fine(String.format("sending xhr with url %s | data %s", this.uri, - this.data instanceof byte[] ? Arrays.toString((byte[]) this.data) : this.data)); + logger.fine(String.format("sending xhr with url %s | data %s", this.uri, this.data)); } okhttp3.Request.Builder requestBuilder = new okhttp3.Request.Builder(); @@ -204,10 +184,8 @@ public class PollingXHR extends Polling { } } RequestBody body = null; - if (this.data instanceof byte[]) { - body = RequestBody.create(BINARY_MEDIA_TYPE, (byte[])this.data); - } else if (this.data instanceof String) { - body = RequestBody.create(TEXT_MEDIA_TYPE, (String)this.data); + if (this.data != null) { + body = RequestBody.create(TEXT_MEDIA_TYPE, this.data); } okhttp3.Request request = requestBuilder @@ -249,11 +227,6 @@ public class PollingXHR extends Polling { this.onSuccess(); } - private void onData(byte[] data) { - this.emit(EVENT_DATA, data); - this.onSuccess(); - } - private void onError(Exception err) { this.emit(EVENT_ERROR, err); } @@ -268,14 +241,9 @@ public class PollingXHR extends Polling { private void onLoad() { ResponseBody body = response.body(); - MediaType mediaType = body.contentType(); try { - if (mediaType != null && BINARY_CONTENT_TYPE.equalsIgnoreCase(mediaType.toString())) { - this.onData(body.bytes()); - } else { - this.onData(body.string()); - } + this.onData(body.string()); } catch (IOException e) { this.onError(e); } @@ -285,7 +253,7 @@ public class PollingXHR extends Polling { public String uri; public String method; - public Object data; + public String data; public Call.Factory callFactory; } } diff --git a/src/main/java/io/socket/engineio/client/transports/WebSocket.java b/src/main/java/io/socket/engineio/client/transports/WebSocket.java index d6e2f1a..74cf65f 100644 --- a/src/main/java/io/socket/engineio/client/transports/WebSocket.java +++ b/src/main/java/io/socket/engineio/client/transports/WebSocket.java @@ -1,18 +1,11 @@ package io.socket.engineio.client.transports; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; -import java.util.logging.Logger; - import io.socket.engineio.client.Transport; import io.socket.engineio.parser.Packet; import io.socket.engineio.parser.Parser; import io.socket.parseqs.ParseQS; import io.socket.thread.EventThread; -import io.socket.utf8.UTF8Exception; import io.socket.yeast.Yeast; import okhttp3.OkHttpClient; import okhttp3.Request; @@ -20,6 +13,12 @@ import okhttp3.Response; import okhttp3.WebSocketListener; import okio.ByteString; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.logging.Logger; + public class WebSocket extends Transport { @@ -111,7 +110,7 @@ public class WebSocket extends Transport { }); } - protected void write(Packet[] packets) throws UTF8Exception { + protected void write(Packet[] packets) { final WebSocket self = this; this.writable = false; diff --git a/src/main/java/io/socket/engineio/parser/Base64.java b/src/main/java/io/socket/engineio/parser/Base64.java new file mode 100644 index 0000000..d56a7ce --- /dev/null +++ b/src/main/java/io/socket/engineio/parser/Base64.java @@ -0,0 +1,665 @@ +// from https://android.googlesource.com/platform/frameworks/base/+/master/core/java/android/util/Base64.java +package io.socket.engineio.parser; + +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import java.io.UnsupportedEncodingException; + +/** + * Utilities for encoding and decoding the Base64 representation of + * binary data. See RFCs 2045 and 3548. + */ +public class Base64 { + /** + * Default values for encoder/decoder flags. + */ + public static final int DEFAULT = 0; + /** + * Encoder flag bit to omit the padding '=' characters at the end + * of the output (if any). + */ + public static final int NO_PADDING = 1; + /** + * Encoder flag bit to omit all line terminators (i.e., the output + * will be on one long line). + */ + public static final int NO_WRAP = 2; + /** + * Encoder flag bit to indicate lines should be terminated with a + * CRLF pair instead of just an LF. Has no effect if {@code + * NO_WRAP} is specified as well. + */ + public static final int CRLF = 4; + /** + * Encoder/decoder flag bit to indicate using the "URL and + * filename safe" variant of Base64 (see RFC 3548 section 4) where + * {@code -} and {@code _} are used in place of {@code +} and + * {@code /}. + */ + public static final int URL_SAFE = 8; + /** + * Flag to pass to Base64OutputStream to indicate that it + * should not close the output stream it is wrapping when it + * itself is closed. + */ + public static final int NO_CLOSE = 16; + // -------------------------------------------------------- + // shared code + // -------------------------------------------------------- + /* package */ static abstract class Coder { + public byte[] output; + public int op; + /** + * Encode/decode another block of input data. this.output is + * provided by the caller, and must be big enough to hold all + * the coded data. On exit, this.opwill be set to the length + * of the coded data. + * + * @param finish true if this is the final call to process for + * this object. Will finalize the coder state and + * include any final bytes in the output. + * + * @return true if the input so far is good; false if some + * error has been detected in the input stream.. + */ + public abstract boolean process(byte[] input, int offset, int len, boolean finish); + /** + * @return the maximum number of bytes a call to process() + * could produce for the given number of input bytes. This may + * be an overestimate. + */ + public abstract int maxOutputSize(int len); + } + // -------------------------------------------------------- + // decoding + // -------------------------------------------------------- + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param str the input String to decode, which is converted to + * bytes using the default charset + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(String str, int flags) { + return decode(str.getBytes(), flags); + } + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the input array to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int flags) { + return decode(input, 0, input.length, flags); + } + /** + * Decode the Base64-encoded data in input and return the data in + * a new byte array. + * + *

The padding '=' characters at the end are considered optional, but + * if any are present, there must be the correct number of them. + * + * @param input the data to decode + * @param offset the position within the input array at which to start + * @param len the number of bytes of input to decode + * @param flags controls certain features of the decoded output. + * Pass {@code DEFAULT} to decode standard Base64. + * + * @throws IllegalArgumentException if the input contains + * incorrect padding + */ + public static byte[] decode(byte[] input, int offset, int len, int flags) { + // Allocate space for the most data the input could represent. + // (It could contain less if it contains whitespace, etc.) + Decoder decoder = new Decoder(flags, new byte[len*3/4]); + if (!decoder.process(input, offset, len, true)) { + throw new IllegalArgumentException("bad base-64"); + } + // Maybe we got lucky and allocated exactly enough output space. + if (decoder.op == decoder.output.length) { + return decoder.output; + } + // Need to shorten the array, so allocate a new one of the + // right size and copy. + byte[] temp = new byte[decoder.op]; + System.arraycopy(decoder.output, 0, temp, 0, decoder.op); + return temp; + } + /* package */ static class Decoder extends Coder { + /** + * Lookup table for turning bytes into their position in the + * Base64 alphabet. + */ + private static final int DECODE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** + * Decode lookup table for the "web safe" variant (RFC 3548 + * sec. 4) where - and _ replace + and /. + */ + private static final int DECODE_WEBSAFE[] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -2, -1, -1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, 63, + -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + }; + /** Non-data values in the DECODE arrays. */ + private static final int SKIP = -1; + private static final int EQUALS = -2; + /** + * States 0-3 are reading through the next input tuple. + * State 4 is having read one '=' and expecting exactly + * one more. + * State 5 is expecting no more data or padding characters + * in the input. + * State 6 is the error state; an error has been detected + * in the input and no future input can "fix" it. + */ + private int state; // state number (0 to 6) + private int value; + final private int[] alphabet; + public Decoder(int flags, byte[] output) { + this.output = output; + alphabet = ((flags & URL_SAFE) == 0) ? DECODE : DECODE_WEBSAFE; + state = 0; + value = 0; + } + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could decode to. + */ + public int maxOutputSize(int len) { + return len * 3/4 + 10; + } + /** + * Decode another block of input data. + * + * @return true if the state machine is still healthy. false if + * bad base-64 data has been detected in the input stream. + */ + public boolean process(byte[] input, int offset, int len, boolean finish) { + if (this.state == 6) return false; + int p = offset; + len += offset; + // Using local variables makes the decoder about 12% + // faster than if we manipulate the member variables in + // the loop. (Even alphabet makes a measurable + // difference, which is somewhat surprising to me since + // the member variable is final.) + int state = this.state; + int value = this.value; + int op = 0; + final byte[] output = this.output; + final int[] alphabet = this.alphabet; + while (p < len) { + // Try the fast path: we're starting a new tuple and the + // next four bytes of the input stream are all data + // bytes. This corresponds to going through states + // 0-1-2-3-0. We expect to use this method for most of + // the data. + // + // If any of the next four bytes of input are non-data + // (whitespace, etc.), value will end up negative. (All + // the non-data values in decode are small negative + // numbers, so shifting any of them up and or'ing them + // together will result in a value with its top bit set.) + // + // You can remove this whole block and the output should + // be the same, just slower. + if (state == 0) { + while (p+4 <= len && + (value = ((alphabet[input[p] & 0xff] << 18) | + (alphabet[input[p+1] & 0xff] << 12) | + (alphabet[input[p+2] & 0xff] << 6) | + (alphabet[input[p+3] & 0xff]))) >= 0) { + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + p += 4; + } + if (p >= len) break; + } + // The fast path isn't available -- either we've read a + // partial tuple, or the next four input bytes aren't all + // data, or whatever. Fall back to the slower state + // machine implementation. + int d = alphabet[input[p++] & 0xff]; + switch (state) { + case 0: + if (d >= 0) { + value = d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 1: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 2: + if (d >= 0) { + value = (value << 6) | d; + ++state; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect exactly one more padding character. + output[op++] = (byte) (value >> 4); + state = 4; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 3: + if (d >= 0) { + // Emit the output triple and return to state 0. + value = (value << 6) | d; + output[op+2] = (byte) value; + output[op+1] = (byte) (value >> 8); + output[op] = (byte) (value >> 16); + op += 3; + state = 0; + } else if (d == EQUALS) { + // Emit the last (partial) output tuple; + // expect no further data or padding characters. + output[op+1] = (byte) (value >> 2); + output[op] = (byte) (value >> 10); + op += 2; + state = 5; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 4: + if (d == EQUALS) { + ++state; + } else if (d != SKIP) { + this.state = 6; + return false; + } + break; + case 5: + if (d != SKIP) { + this.state = 6; + return false; + } + break; + } + } + if (!finish) { + // We're out of input, but a future call could provide + // more. + this.state = state; + this.value = value; + this.op = op; + return true; + } + // Done reading input. Now figure out where we are left in + // the state machine and finish up. + switch (state) { + case 0: + // Output length is a multiple of three. Fine. + break; + case 1: + // Read one extra input byte, which isn't enough to + // make another output byte. Illegal. + this.state = 6; + return false; + case 2: + // Read two extra input bytes, enough to emit 1 more + // output byte. Fine. + output[op++] = (byte) (value >> 4); + break; + case 3: + // Read three extra input bytes, enough to emit 2 more + // output bytes. Fine. + output[op++] = (byte) (value >> 10); + output[op++] = (byte) (value >> 2); + break; + case 4: + // Read one padding '=' when we expected 2. Illegal. + this.state = 6; + return false; + case 5: + // Read all the padding '='s we expected and no more. + // Fine. + break; + } + this.state = state; + this.op = op; + return true; + } + } + // -------------------------------------------------------- + // encoding + // -------------------------------------------------------- + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int flags) { + try { + return new String(encode(input, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + /** + * Base64-encode the given data and return a newly allocated + * String with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static String encodeToString(byte[] input, int offset, int len, int flags) { + try { + return new String(encode(input, offset, len, flags), "US-ASCII"); + } catch (UnsupportedEncodingException e) { + // US-ASCII is guaranteed to be available. + throw new AssertionError(e); + } + } + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int flags) { + return encode(input, 0, input.length, flags); + } + /** + * Base64-encode the given data and return a newly allocated + * byte[] with the result. + * + * @param input the data to encode + * @param offset the position within the input array at which to + * start + * @param len the number of bytes of input to encode + * @param flags controls certain features of the encoded output. + * Passing {@code DEFAULT} results in output that + * adheres to RFC 2045. + */ + public static byte[] encode(byte[] input, int offset, int len, int flags) { + Encoder encoder = new Encoder(flags, null); + // Compute the exact length of the array we will produce. + int output_len = len / 3 * 4; + // Account for the tail of the data and the padding bytes, if any. + if (encoder.do_padding) { + if (len % 3 > 0) { + output_len += 4; + } + } else { + switch (len % 3) { + case 0: break; + case 1: output_len += 2; break; + case 2: output_len += 3; break; + } + } + // Account for the newlines, if any. + if (encoder.do_newline && len > 0) { + output_len += (((len-1) / (3 * Encoder.LINE_GROUPS)) + 1) * + (encoder.do_cr ? 2 : 1); + } + encoder.output = new byte[output_len]; + encoder.process(input, offset, len, true); + assert encoder.op == output_len; + return encoder.output; + } + /* package */ static class Encoder extends Coder { + /** + * Emit a new line every this many output tuples. Corresponds to + * a 76-character line length (the maximum allowable according to + * RFC 2045). + */ + public static final int LINE_GROUPS = 19; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', + }; + /** + * Lookup table for turning Base64 alphabet positions (6 bits) + * into output bytes. + */ + private static final byte ENCODE_WEBSAFE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_', + }; + final private byte[] tail; + /* package */ int tailLen; + private int count; + final public boolean do_padding; + final public boolean do_newline; + final public boolean do_cr; + final private byte[] alphabet; + public Encoder(int flags, byte[] output) { + this.output = output; + do_padding = (flags & NO_PADDING) == 0; + do_newline = (flags & NO_WRAP) == 0; + do_cr = (flags & CRLF) != 0; + alphabet = ((flags & URL_SAFE) == 0) ? ENCODE : ENCODE_WEBSAFE; + tail = new byte[2]; + tailLen = 0; + count = do_newline ? LINE_GROUPS : -1; + } + /** + * @return an overestimate for the number of bytes {@code + * len} bytes could encode to. + */ + public int maxOutputSize(int len) { + return len * 8/5 + 10; + } + public boolean process(byte[] input, int offset, int len, boolean finish) { + // Using local variables makes the encoder about 9% faster. + final byte[] alphabet = this.alphabet; + final byte[] output = this.output; + int op = 0; + int count = this.count; + int p = offset; + len += offset; + int v = -1; + // First we need to concatenate the tail of the previous call + // with any input bytes available now and see if we can empty + // the tail. + switch (tailLen) { + case 0: + // There was no tail. + break; + case 1: + if (p+2 <= len) { + // A 1-byte tail with at least 2 bytes of + // input available now. + v = ((tail[0] & 0xff) << 16) | + ((input[p++] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + }; + break; + case 2: + if (p+1 <= len) { + // A 2-byte tail with at least 1 byte of input. + v = ((tail[0] & 0xff) << 16) | + ((tail[1] & 0xff) << 8) | + (input[p++] & 0xff); + tailLen = 0; + } + break; + } + if (v != -1) { + output[op++] = alphabet[(v >> 18) & 0x3f]; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + // At this point either there is no tail, or there are fewer + // than 3 bytes of input available. + // The main loop, turning 3 input bytes into 4 output bytes on + // each iteration. + while (p+3 <= len) { + v = ((input[p] & 0xff) << 16) | + ((input[p+1] & 0xff) << 8) | + (input[p+2] & 0xff); + output[op] = alphabet[(v >> 18) & 0x3f]; + output[op+1] = alphabet[(v >> 12) & 0x3f]; + output[op+2] = alphabet[(v >> 6) & 0x3f]; + output[op+3] = alphabet[v & 0x3f]; + p += 3; + op += 4; + if (--count == 0) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + count = LINE_GROUPS; + } + } + if (finish) { + // Finish up the tail of the input. Note that we need to + // consume any bytes in tail before any bytes + // remaining in input; there should be at most two bytes + // total. + if (p-tailLen == len-1) { + int t = 0; + v = ((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 4; + tailLen -= t; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (p-tailLen == len-2) { + int t = 0; + v = (((tailLen > 1 ? tail[t++] : input[p++]) & 0xff) << 10) | + (((tailLen > 0 ? tail[t++] : input[p++]) & 0xff) << 2); + tailLen -= t; + output[op++] = alphabet[(v >> 12) & 0x3f]; + output[op++] = alphabet[(v >> 6) & 0x3f]; + output[op++] = alphabet[v & 0x3f]; + if (do_padding) { + output[op++] = '='; + } + if (do_newline) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + } else if (do_newline && op > 0 && count != LINE_GROUPS) { + if (do_cr) output[op++] = '\r'; + output[op++] = '\n'; + } + assert tailLen == 0; + assert p == len; + } else { + // Save the leftovers in tail to be consumed on the next + // call to encodeInternal. + if (p == len-1) { + tail[tailLen++] = input[p]; + } else if (p == len-2) { + tail[tailLen++] = input[p]; + tail[tailLen++] = input[p+1]; + } + } + this.op = op; + this.count = count; + return true; + } + } + + private Base64() { } // don't instantiate +} diff --git a/src/main/java/io/socket/engineio/parser/Parser.java b/src/main/java/io/socket/engineio/parser/Parser.java index fe6c362..f92e1e0 100644 --- a/src/main/java/io/socket/engineio/parser/Parser.java +++ b/src/main/java/io/socket/engineio/parser/Parser.java @@ -1,20 +1,13 @@ package io.socket.engineio.parser; - -import io.socket.utf8.UTF8; -import io.socket.utf8.UTF8Exception; - -import java.nio.ByteBuffer; -import java.util.ArrayList; import java.util.HashMap; -import java.util.List; import java.util.Map; public class Parser { - private static final int MAX_INT_CHAR_LENGTH = String.valueOf(Integer.MAX_VALUE).length(); + public static final int PROTOCOL = 4; - public static final int PROTOCOL = 3; + private static final char SEPARATOR = '\u001e'; private static final Map packets = new HashMap() {{ put(Packet.OPEN, 0); @@ -26,61 +19,38 @@ public class Parser { put(Packet.NOOP, 6); }}; - private static final Map packetslist = new HashMap(); + private static final Map packetslist = new HashMap<>(); static { for (Map.Entry entry : packets.entrySet()) { packetslist.put(entry.getValue(), entry.getKey()); } } - private static Packet err = new Packet(Packet.ERROR, "parser error"); - - private static UTF8.Options utf8Options = new UTF8.Options(); - static { - utf8Options.strict = false; - } - + private static final Packet err = new Packet(Packet.ERROR, "parser error"); private Parser() {} - public static void encodePacket(Packet packet, EncodeCallback callback) throws UTF8Exception { - encodePacket(packet, false, callback); - } - - public static void encodePacket(Packet packet, boolean utf8encode, EncodeCallback callback) throws UTF8Exception { + public static void encodePacket(Packet packet, EncodeCallback callback) { if (packet.data instanceof byte[]) { - @SuppressWarnings("unchecked") - Packet packetToEncode = packet; - @SuppressWarnings("unchecked") - EncodeCallback callbackToEncode = callback; - encodeByteArray(packetToEncode, callbackToEncode); - return; + ((EncodeCallback) callback).call(((Packet) packet).data); + } else { + String type = String.valueOf(packets.get(packet.type)); + String content = packet.data != null ? String.valueOf(packet.data) : ""; + ((EncodeCallback) callback).call(type + content); } - - String encoded = String.valueOf(packets.get(packet.type)); - - if (null != packet.data) { - encoded += utf8encode ? UTF8.encode(String.valueOf(packet.data), utf8Options) : String.valueOf(packet.data); - } - - @SuppressWarnings("unchecked") - EncodeCallback tempCallback = callback; - tempCallback.call(encoded); } - private static void encodeByteArray(Packet packet, EncodeCallback callback) { - byte[] data = packet.data; - byte[] resultArray = new byte[1 + data.length]; - resultArray[0] = packets.get(packet.type).byteValue(); - System.arraycopy(data, 0, resultArray, 1, data.length); - callback.call(resultArray); + private static void encodePacketAsBase64(Packet packet, EncodeCallback callback) { + if (packet.data instanceof byte[]) { + byte[] data = ((Packet) packet).data; + String value = "b" + Base64.encodeToString(data, Base64.DEFAULT); + callback.call(value); + } else { + encodePacket(packet, callback); + } } public static Packet decodePacket(String data) { - return decodePacket(data, false); - } - - public static Packet decodePacket(String data, boolean utf8decode) { if (data == null) { return err; } @@ -92,14 +62,6 @@ public class Parser { type = -1; } - if (utf8decode) { - try { - data = UTF8.decode(data, utf8Options); - } catch (UTF8Exception e) { - return err; - } - } - if (type < 0 || type >= packetslist.size()) { return err; } @@ -111,23 +73,23 @@ public class Parser { } } - public static Packet decodePacket(byte[] data) { - int type = data[0]; - byte[] intArray = new byte[data.length - 1]; - System.arraycopy(data, 1, intArray, 0, intArray.length); - return new Packet(packetslist.get(type), intArray); - } - - public static void encodePayload(Packet[] packets, EncodeCallback callback) throws UTF8Exception { - for (Packet packet : packets) { - if (packet.data instanceof byte[]) { - @SuppressWarnings("unchecked") - EncodeCallback _callback = (EncodeCallback) callback; - encodePayloadAsBinary(packets, _callback); - return; - } + public static Packet decodeBase64Packet(String data) { + if (data == null) { + return err; } + if (data.charAt(0) == 'b') { + return new Packet(Packet.MESSAGE, Base64.decode(data.substring(1), Base64.DEFAULT)); + } else { + return decodePacket(data); + } + } + + public static Packet decodePacket(byte[] data) { + return new Packet<>(Packet.MESSAGE, data); + } + + public static void encodePayload(Packet[] packets, EncodeCallback callback) { if (packets.length == 0) { callback.call("0:"); return; @@ -135,11 +97,15 @@ public class Parser { final StringBuilder result = new StringBuilder(); - for (Packet packet : packets) { - encodePacket(packet, false, new EncodeCallback() { + for (int i = 0, l = packets.length; i < l; i++) { + final boolean isLast = i == l - 1; + encodePacketAsBase64(packets[i], new EncodeCallback() { @Override - public void call(Object message) { - result.append(setLengthHeader((String)message)); + public void call(String message) { + result.append(message); + if (!isLast) { + result.append(SEPARATOR); + } } }); } @@ -147,218 +113,36 @@ public class Parser { callback.call(result.toString()); } - private static String setLengthHeader(String message) { - return message.length() + ":" + message; - } - - private static void encodePayloadAsBinary(Packet[] packets, EncodeCallback callback) throws UTF8Exception { - if (packets.length == 0) { - callback.call(new byte[0]); - return; - } - - final ArrayList results = new ArrayList(packets.length); - - for (Packet packet : packets) { - encodeOneBinaryPacket(packet, new EncodeCallback() { - @Override - public void call(byte[] data) { - results.add(data); - } - }); - } - - callback.call(Buffer.concat(results.toArray(new byte[results.size()][]))); - } - - private static void encodeOneBinaryPacket(Packet p, final EncodeCallback doneCallback) throws UTF8Exception { - encodePacket(p, true, new EncodeCallback() { - @Override - public void call(Object packet) { - if (packet instanceof String) { - String encodingLength = String.valueOf(((String) packet).length()); - byte[] sizeBuffer = new byte[encodingLength.length() + 2]; - - sizeBuffer[0] = (byte)0; // is a string - for (int i = 0; i < encodingLength.length(); i ++) { - sizeBuffer[i + 1] = (byte)Character.getNumericValue(encodingLength.charAt(i)); - } - sizeBuffer[sizeBuffer.length - 1] = (byte)255; - doneCallback.call(Buffer.concat(new byte[][] {sizeBuffer, stringToByteArray((String)packet)})); - return; - } - - String encodingLength = String.valueOf(((byte[])packet).length); - byte[] sizeBuffer = new byte[encodingLength.length() + 2]; - sizeBuffer[0] = (byte)1; // is binary - for (int i = 0; i < encodingLength.length(); i ++) { - sizeBuffer[i + 1] = (byte)Character.getNumericValue(encodingLength.charAt(i)); - } - sizeBuffer[sizeBuffer.length - 1] = (byte)255; - doneCallback.call(Buffer.concat(new byte[][] {sizeBuffer, (byte[])packet})); - } - }); - } - public static void decodePayload(String data, DecodePayloadCallback callback) { if (data == null || data.length() == 0) { callback.call(err, 0, 1); return; } - StringBuilder length = new StringBuilder(); - for (int i = 0, l = data.length(); i < l; i++) { - char chr = data.charAt(i); + String[] messages = data.split(String.valueOf(SEPARATOR)); - if (':' != chr) { - length.append(chr); - continue; - } - - int n; - try { - n = Integer.parseInt(length.toString()); - } catch (NumberFormatException e) { + for (int i = 0, l = messages.length; i < l; i++) { + Packet packet = decodeBase64Packet(messages[i]); + if (err.type.equals(packet.type) && err.data.equals(packet.data)) { callback.call(err, 0, 1); return; } - String msg; - try { - msg = data.substring(i + 1, i + 1 + n); - } catch (IndexOutOfBoundsException e) { - callback.call(err, 0, 1); + boolean ret = callback.call(packet, i, l); + if (!ret) { return; } - - if (msg.length() != 0) { - Packet packet = decodePacket(msg, false); - if (err.type.equals(packet.type) && err.data.equals(packet.data)) { - callback.call(err, 0, 1); - return; - } - - boolean ret = callback.call(packet, i + n, l); - if (!ret) { - return; - } - } - - i += n; - length = new StringBuilder(); - } - - if (length.length() > 0) { - callback.call(err, 0, 1); } } - public static void decodePayload(byte[] data, DecodePayloadCallback callback) { - ByteBuffer bufferTail = ByteBuffer.wrap(data); - List buffers = new ArrayList(); + public interface EncodeCallback { - while (bufferTail.capacity() > 0) { - StringBuilder strLen = new StringBuilder(); - boolean isString = (bufferTail.get(0) & 0xFF) == 0; - for (int i = 1; ; i++) { - int b = bufferTail.get(i) & 0xFF; - if (b == 255) break; - // supports only integer - if (strLen.length() > MAX_INT_CHAR_LENGTH) { - callback.call(err, 0, 1); - return; - } - strLen.append(b); - } - - bufferTail.position(strLen.length() + 1); - bufferTail = bufferTail.slice(); - - int msgLength = Integer.parseInt(strLen.toString()); - - bufferTail.position(1); - bufferTail.limit(msgLength + 1); - byte[] msg = new byte[bufferTail.remaining()]; - bufferTail.get(msg); - if (isString) { - buffers.add(byteArrayToString(msg)); - } else { - buffers.add(msg); - } - bufferTail.clear(); - bufferTail.position(msgLength + 1); - bufferTail = bufferTail.slice(); - } - - int total = buffers.size(); - for (int i = 0; i < total; i++) { - Object buffer = buffers.get(i); - if (buffer instanceof String) { - @SuppressWarnings("unchecked") - DecodePayloadCallback tempCallback = callback; - tempCallback.call(decodePacket((String)buffer, true), i, total); - } else if (buffer instanceof byte[]) { - @SuppressWarnings("unchecked") - DecodePayloadCallback tempCallback = callback; - tempCallback.call(decodePacket((byte[])buffer), i, total); - } - } - } - - private static String byteArrayToString(byte[] bytes) { - StringBuilder builder = new StringBuilder(); - for (byte b : bytes) { - builder.appendCodePoint(b & 0xFF); - } - return builder.toString(); - } - - private static byte[] stringToByteArray(String string) { - int len = string.length(); - byte[] bytes = new byte[len]; - for (int i = 0; i < len; i++) { - bytes[i] = (byte)Character.codePointAt(string, i); - } - return bytes; - } - - public static interface EncodeCallback { - - public void call(T data); + void call(T data); } - public static interface DecodePayloadCallback { + public interface DecodePayloadCallback { - public boolean call(Packet packet, int index, int total); - } -} - - -class Buffer { - - private Buffer() {} - - public static byte[] concat(byte[][] list) { - int length = 0; - for (byte[] buf : list) { - length += buf.length; - } - return concat(list, length); - } - - public static byte[] concat(byte[][] list, int length) { - if (list.length == 0) { - return new byte[0]; - } else if (list.length == 1) { - return list[0]; - } - - ByteBuffer buffer = ByteBuffer.allocate(length); - for (byte[] buf : list) { - buffer.put(buf); - } - - return buffer.array(); + boolean call(Packet packet, int index, int total); } } diff --git a/src/main/java/io/socket/utf8/UTF8.java b/src/main/java/io/socket/utf8/UTF8.java deleted file mode 100644 index e714579..0000000 --- a/src/main/java/io/socket/utf8/UTF8.java +++ /dev/null @@ -1,199 +0,0 @@ -package io.socket.utf8; - -import java.util.ArrayList; -import java.util.List; - -/** - * UTF-8 encoder/decoder ported from utf8.js. - * - * @see https://github.com/mathiasbynens/utf8.js - */ -public final class UTF8 { - - private static final String INVALID_CONTINUATION_BYTE = "Invalid continuation byte"; - private static int[] byteArray; - private static int byteCount; - private static int byteIndex; - - private UTF8 () {} - - public static String encode(String string) throws UTF8Exception { - return encode(string, new Options()); - } - - public static String encode(String string, Options opts) throws UTF8Exception { - boolean strict = opts.strict; - - int[] codePoints = ucs2decode(string); - int length = codePoints.length; - int index = -1; - int codePoint; - StringBuilder byteString = new StringBuilder(); - while (++index < length) { - codePoint = codePoints[index]; - byteString.append(encodeCodePoint(codePoint, strict)); - } - return byteString.toString(); - } - - public static String decode(String byteString) throws UTF8Exception { - return decode(byteString, new Options()); - } - - public static String decode(String byteString, Options opts) throws UTF8Exception { - boolean strict = opts.strict; - - byteArray = ucs2decode(byteString); - byteCount = byteArray.length; - byteIndex = 0; - List codePoints = new ArrayList(); - int tmp; - while ((tmp = decodeSymbol(strict)) != -1) { - codePoints.add(tmp); - } - return ucs2encode(listToArray(codePoints)); - } - - private static int[] ucs2decode(String string) { - int length = string.length(); - int[] output = new int[string.codePointCount(0, length)]; - int counter = 0; - int value; - for (int i = 0; i < length; i += Character.charCount(value)) { - value = string.codePointAt(i); - output[counter++] = value; - } - return output; - } - - private static String encodeCodePoint(int codePoint, boolean strict) throws UTF8Exception { - StringBuilder symbol = new StringBuilder(); - if ((codePoint & 0xFFFFFF80) == 0) { - return symbol.append(Character.toChars(codePoint)).toString(); - } - if ((codePoint & 0xFFFFF800) == 0) { - symbol.append(Character.toChars(((codePoint >> 6) & 0x1F) | 0xC0)); - } else if ((codePoint & 0xFFFF0000) == 0) { - if (!checkScalarValue(codePoint, strict)) { - codePoint = 0xFFFD; - } - symbol.append(Character.toChars(((codePoint >> 12) & 0x0F) | 0xE0)); - symbol.append(createByte(codePoint, 6)); - } else if ((codePoint & 0xFFE00000) == 0) { - symbol.append(Character.toChars(((codePoint >> 18) & 0x07) | 0xF0)); - symbol.append(createByte(codePoint, 12)); - symbol.append(createByte(codePoint, 6)); - } - symbol.append(Character.toChars((codePoint & 0x3F) | 0x80)); - return symbol.toString(); - } - - private static char[] createByte(int codePoint, int shift) { - return Character.toChars(((codePoint >> shift) & 0x3F) | 0x80); - } - - private static int decodeSymbol(boolean strict) throws UTF8Exception { - int byte1; - int byte2; - int byte3; - int byte4; - int codePoint; - - if (byteIndex > byteCount) { - throw new UTF8Exception("Invalid byte index"); - } - - if (byteIndex == byteCount) { - return -1; - } - - byte1 = byteArray[byteIndex] & 0xFF; - byteIndex++; - - if ((byte1 & 0x80) == 0) { - return byte1; - } - - if ((byte1 & 0xE0) == 0xC0) { - byte2 = readContinuationByte(); - codePoint = ((byte1 & 0x1F) << 6) | byte2; - if (codePoint >= 0x80) { - return codePoint; - } else { - throw new UTF8Exception(INVALID_CONTINUATION_BYTE); - } - } - - if ((byte1 & 0xF0) == 0xE0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 12) | (byte2 << 6) | byte3; - if (codePoint >= 0x0800) { - return checkScalarValue(codePoint, strict) ? codePoint : 0xFFFD; - } else { - throw new UTF8Exception(INVALID_CONTINUATION_BYTE); - } - } - - if ((byte1 & 0xF8) == 0xF0) { - byte2 = readContinuationByte(); - byte3 = readContinuationByte(); - byte4 = readContinuationByte(); - codePoint = ((byte1 & 0x0F) << 0x12) | (byte2 << 0x0C) | (byte3 << 0x06) | byte4; - if (codePoint >= 0x010000 && codePoint <= 0x10FFFF) { - return codePoint; - } - } - - throw new UTF8Exception(INVALID_CONTINUATION_BYTE); - } - - private static int readContinuationByte() throws UTF8Exception { - if (byteIndex >= byteCount) { - throw new UTF8Exception("Invalid byte index"); - } - - int continuationByte = byteArray[byteIndex] & 0xFF; - byteIndex++; - - if ((continuationByte & 0xC0) == 0x80) { - return continuationByte & 0x3F; - } - - throw new UTF8Exception(INVALID_CONTINUATION_BYTE); - } - - private static String ucs2encode(int[] array) { - StringBuilder output = new StringBuilder(); - for (int value : array) { - output.appendCodePoint(value); - } - return output.toString(); - } - - private static boolean checkScalarValue(int codePoint, boolean strict) throws UTF8Exception { - if (codePoint >= 0xD800 && codePoint <= 0xDFFF) { - if (strict) { - throw new UTF8Exception( - "Lone surrogate U+" + Integer.toHexString(codePoint).toUpperCase() + - " is not a scalar value" - ); - } - return false; - } - return true; - } - - private static int[] listToArray(List list) { - int size = list.size(); - int[] array = new int[size]; - for (int i = 0; i < size; i++) { - array[i] = list.get(i); - } - return array; - } - - public static class Options { - public boolean strict = true; - } -} diff --git a/src/main/java/io/socket/utf8/UTF8Exception.java b/src/main/java/io/socket/utf8/UTF8Exception.java deleted file mode 100644 index 077da0b..0000000 --- a/src/main/java/io/socket/utf8/UTF8Exception.java +++ /dev/null @@ -1,22 +0,0 @@ -package io.socket.utf8; - -import java.io.IOException; - -public class UTF8Exception extends IOException { - - public UTF8Exception() { - super(); - } - - public UTF8Exception(String message) { - super(message); - } - - public UTF8Exception(String message, Throwable cause) { - super(message, cause); - } - - public UTF8Exception(Throwable cause) { - super(cause); - } -} diff --git a/src/test/java/io/socket/engineio/client/ConnectionTest.java b/src/test/java/io/socket/engineio/client/ConnectionTest.java index ac0997e..a919279 100644 --- a/src/test/java/io/socket/engineio/client/ConnectionTest.java +++ b/src/test/java/io/socket/engineio/client/ConnectionTest.java @@ -242,4 +242,20 @@ public class ConnectionTest extends Connection { socket.open(); assertThat((Integer) values.take(), is(0)); } + + @Test(timeout = TIMEOUT) + public void receivePing() throws InterruptedException { + final BlockingQueue values = new LinkedBlockingQueue<>(); + + socket = new Socket(createOptions()); + socket.on(Socket.EVENT_PING, new Emitter.Listener() { + @Override + public void call(Object... args) { + values.offer("end"); + socket.close(); + } + }); + socket.open(); + assertThat(values.take(), is("end")); + } } diff --git a/src/test/java/io/socket/engineio/parser/ParserTest.java b/src/test/java/io/socket/engineio/parser/ParserTest.java index d8e7692..d5498b9 100644 --- a/src/test/java/io/socket/engineio/parser/ParserTest.java +++ b/src/test/java/io/socket/engineio/parser/ParserTest.java @@ -1,6 +1,5 @@ package io.socket.engineio.parser; -import io.socket.utf8.UTF8Exception; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -15,7 +14,7 @@ public class ParserTest { static final String ERROR_DATA = "parser error"; @Test - public void encodeAsString() throws UTF8Exception { + public void encodeAsString() { encodePacket(new Packet(Packet.MESSAGE, "test"), new EncodeCallback() { @Override public void call(String data) { @@ -25,7 +24,7 @@ public class ParserTest { } @Test - public void decodeAsPacket() throws UTF8Exception { + public void decodeAsPacket() { encodePacket(new Packet(Packet.MESSAGE, "test"), new EncodeCallback() { @Override public void call(String data) { @@ -35,7 +34,7 @@ public class ParserTest { } @Test - public void noData() throws UTF8Exception { + public void noData() { encodePacket(new Packet(Packet.MESSAGE), new EncodeCallback() { @Override public void call(String data) { @@ -47,7 +46,7 @@ public class ParserTest { } @Test - public void encodeOpenPacket() throws UTF8Exception { + public void encodeOpenPacket() { encodePacket(new Packet(Packet.OPEN, "{\"some\":\"json\"}"), new EncodeCallback() { @Override public void call(String data) { @@ -59,7 +58,7 @@ public class ParserTest { } @Test - public void encodeClosePacket() throws UTF8Exception { + public void encodeClosePacket() { encodePacket(new Packet(Packet.CLOSE), new EncodeCallback() { @Override public void call(String data) { @@ -70,7 +69,7 @@ public class ParserTest { } @Test - public void encodePingPacket() throws UTF8Exception { + public void encodePingPacket() { encodePacket(new Packet(Packet.PING, "1"), new EncodeCallback() { @Override public void call(String data) { @@ -82,7 +81,7 @@ public class ParserTest { } @Test - public void encodePongPacket() throws UTF8Exception { + public void encodePongPacket() { encodePacket(new Packet(Packet.PONG, "1"), new EncodeCallback() { @Override public void call(String data) { @@ -94,7 +93,7 @@ public class ParserTest { } @Test - public void encodeMessagePacket() throws UTF8Exception { + public void encodeMessagePacket() { encodePacket(new Packet(Packet.MESSAGE, "aaa"), new EncodeCallback() { @Override public void call(String data) { @@ -106,7 +105,7 @@ public class ParserTest { } @Test - public void encodeUTF8SpecialCharsMessagePacket() throws UTF8Exception { + public void encodeUTF8SpecialCharsMessagePacket() { encodePacket(new Packet(Packet.MESSAGE, "utf8 — string"), new EncodeCallback() { @Override public void call(String data) { @@ -118,7 +117,7 @@ public class ParserTest { } @Test - public void encodeMessagePacketCoercingToString() throws UTF8Exception { + public void encodeMessagePacketCoercingToString() { encodePacket(new Packet(Packet.MESSAGE, 1), new EncodeCallback() { @Override public void call(String data) { @@ -130,7 +129,7 @@ public class ParserTest { } @Test - public void encodeUpgradePacket() throws UTF8Exception { + public void encodeUpgradePacket() { encodePacket(new Packet(Packet.UPGRADE), new EncodeCallback() { @Override public void call(String data) { @@ -141,7 +140,7 @@ public class ParserTest { } @Test - public void encodingFormat() throws UTF8Exception { + public void encodingFormat() { encodePacket(new Packet(Packet.MESSAGE, "test"), new EncodeCallback() { @Override public void call(String data) { @@ -156,19 +155,6 @@ public class ParserTest { }); } - @Test - public void encodingStringMessageWithLoneSurrogatesReplacedByUFFFD() throws UTF8Exception { - String data = "\uDC00\uD834\uDF06\uDC00 \uD800\uD835\uDF07\uD800"; - encodePacket(new Packet(Packet.MESSAGE, data), true, new EncodeCallback() { - @Override - public void call(String encoded) { - Packet p = decodePacket(encoded, true); - assertThat(p.type, is(Packet.MESSAGE)); - assertThat(p.data, is("\uFFFD\uD834\uDF06\uFFFD \uFFFD\uD835\uDF07\uFFFD")); - } - }); - } - @Test public void decodeEmptyPayload() { Packet p = decodePacket((String)null); @@ -191,14 +177,7 @@ public class ParserTest { } @Test - public void decodeInvalidUTF8() { - Packet p = decodePacket("4\uffff", true); - assertThat(p.type, is(Packet.ERROR)); - assertThat(p.data, is(ERROR_DATA)); - } - - @Test - public void encodePayloads() throws UTF8Exception { + public void encodePayloads() { encodePayload(new Packet[]{new Packet(Packet.PING), new Packet(Packet.PONG)}, new EncodeCallback() { @Override public void call(String data) { @@ -208,7 +187,7 @@ public class ParserTest { } @Test - public void encodeAndDecodePayloads() throws UTF8Exception { + public void encodeAndDecodePayloads() { encodePayload(new Packet[] {new Packet(Packet.MESSAGE, "a")}, new EncodeCallback() { @Override public void call(String data) { @@ -242,7 +221,7 @@ public class ParserTest { } @Test - public void encodeAndDecodeEmptyPayloads() throws UTF8Exception { + public void encodeAndDecodeEmptyPayloads() { encodePayload(new Packet[] {}, new EncodeCallback() { @Override public void call(String data) { @@ -260,30 +239,20 @@ public class ParserTest { } @Test - public void notUTF8EncodeWhenDealingWithStringsOnly() throws UTF8Exception { + public void notUTF8EncodeWhenDealingWithStringsOnly() { encodePayload(new Packet[] { new Packet(Packet.MESSAGE, "€€€"), new Packet(Packet.MESSAGE, "α") }, new EncodeCallback() { @Override public void call(String data) { - assertThat(data, is("4:4€€€2:4α")); + assertThat(data, is("4€€€\u001e4α")); } }); } @Test public void decodePayloadBadFormat() { - decodePayload("1!", new DecodePayloadCallback() { - @Override - public boolean call(Packet packet, int index, int total) { - boolean isLast = index + 1 == total; - assertThat(packet.type, is(Packet.ERROR)); - assertThat(packet.data, is(ERROR_DATA)); - assertThat(isLast, is(true)); - return true; - } - }); decodePayload("", new DecodePayloadCallback() { @Override public boolean call(Packet packet, int index, int total) { @@ -306,23 +275,9 @@ public class ParserTest { }); } - @Test - public void decodePayloadBadLength() { - decodePayload("1:", new DecodePayloadCallback() { - @Override - public boolean call(Packet packet, int index, int total) { - boolean isLast = index + 1 == total; - assertThat(packet.type, is(Packet.ERROR)); - assertThat(packet.data, is(ERROR_DATA)); - assertThat(isLast, is(true)); - return true; - } - }); - } - @Test public void decodePayloadBadPacketFormat() { - decodePayload("3:99:", new DecodePayloadCallback() { + decodePayload("99:", new DecodePayloadCallback() { @Override public boolean call(Packet packet, int index, int total) { boolean isLast = index + 1 == total; @@ -332,17 +287,7 @@ public class ParserTest { return true; } }); - decodePayload("1:aa", new DecodePayloadCallback() { - @Override - public boolean call(Packet packet, int index, int total) { - boolean isLast = index + 1 == total; - assertThat(packet.type, is(Packet.ERROR)); - assertThat(packet.data, is(ERROR_DATA)); - assertThat(isLast, is(true)); - return true; - } - }); - decodePayload("1:a2:b", new DecodePayloadCallback() { + decodePayload("aa", new DecodePayloadCallback() { @Override public boolean call(Packet packet, int index, int total) { boolean isLast = index + 1 == total; @@ -355,7 +300,7 @@ public class ParserTest { } @Test - public void encodeBinaryMessage() throws UTF8Exception { + public void encodeBinaryMessage() { final byte[] data = new byte[5]; for (int i = 0; i < data.length; i++) { data[0] = (byte)i; @@ -371,7 +316,7 @@ public class ParserTest { } @Test - public void encodeBinaryContents() throws UTF8Exception { + public void encodeBinaryContents() { final byte[] firstBuffer = new byte[5]; for (int i = 0 ; i < firstBuffer.length; i++) { firstBuffer[0] = (byte)i; @@ -384,9 +329,9 @@ public class ParserTest { encodePayload(new Packet[]{ new Packet(Packet.MESSAGE, firstBuffer), new Packet(Packet.MESSAGE, secondBuffer), - }, new EncodeCallback() { + }, new EncodeCallback() { @Override - public void call(byte[] data) { + public void call(String data) { decodePayload(data, new DecodePayloadCallback() { @Override public boolean call(Packet packet, int index, int total) { @@ -405,7 +350,7 @@ public class ParserTest { } @Test - public void encodeMixedBinaryAndStringContents() throws UTF8Exception { + public void encodeMixedBinaryAndStringContents() { final byte[] firstBuffer = new byte[123]; for (int i = 0 ; i < firstBuffer.length; i++) { firstBuffer[0] = (byte)i; @@ -414,9 +359,9 @@ public class ParserTest { new Packet(Packet.MESSAGE, firstBuffer), new Packet(Packet.MESSAGE, "hello"), new Packet(Packet.CLOSE), - }, new EncodeCallback() { + }, new EncodeCallback() { @Override - public void call(byte[] encoded) { + public void call(String encoded) { decodePayload(encoded, new DecodePayloadCallback() { @Override public boolean call(Packet packet, int index, int total) { diff --git a/src/test/java/io/socket/utf8/UTF8Test.java b/src/test/java/io/socket/utf8/UTF8Test.java deleted file mode 100644 index 8357d7f..0000000 --- a/src/test/java/io/socket/utf8/UTF8Test.java +++ /dev/null @@ -1,114 +0,0 @@ -package io.socket.utf8; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import static org.hamcrest.CoreMatchers.is; -import static org.junit.Assert.assertThat; - -@RunWith(JUnit4.class) -public class UTF8Test { - private static final Data[] DATA = new Data[] { - // 1-byte - new Data(0x0000, "\u0000", "\u0000"), - new Data(0x005c, "\u005C\u005C", "\u005C\u005C"), // = backslash - new Data(0x007f, "\u007F", "\u007F"), - // 2-byte - new Data(0x0080, "\u0080", "\u00C2\u0080"), - new Data(0x05CA, "\u05CA", "\u00D7\u008A"), - new Data(0x07FF, "\u07FF", "\u00DF\u00BF"), - // 3-byte - new Data(0x0800, "\u0800", "\u00E0\u00A0\u0080"), - new Data(0x2C3C, "\u2C3C", "\u00E2\u00B0\u00BC"), - new Data(0x07FF, "\uFFFF", "\u00EF\u00BF\u00BF"), - // unmatched surrogate halves - // high surrogates: 0xD800 to 0xDBFF - 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", true), - new Data("High surrogate followed by a symbol that is not a surrogate", - "\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", 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", true), - new Data("Low surrogate followed by another low surrogate", - "\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", 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", 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"), - new Data(0x010FFF, "\uDBFF\uDFFF", "\u00F4\u008F\u00BF\u00BF"), - }; - - @Rule - public ExpectedException exception = ExpectedException.none(); - - @Test - 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"); - - exception.expect(UTF8Exception.class); - UTF8.decode("\u00E9\u0000\u0000"); - - exception.expect(UTF8Exception.class); - UTF8.decode("\u00C2\uFFFF"); - - exception.expect(UTF8Exception.class); - UTF8.decode("\u00F0\u009D"); - } - - private static class Data { - public int codePoint = -1; - 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; - } - } -} diff --git a/src/test/resources/package-lock.json b/src/test/resources/package-lock.json index 7269712..c37c07d 100644 --- a/src/test/resources/package-lock.json +++ b/src/test/resources/package-lock.json @@ -11,16 +11,6 @@ "negotiator": "0.6.2" } }, - "after": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" - }, - "arraybuffer.slice": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", - "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" - }, "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", @@ -31,15 +21,19 @@ "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==" }, - "blob": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", - "integrity": "sha512-gaqbzQPqOoamawKg0LGVd7SzLgXS+JH61oWprSLH+P+abTczqJbhTR8CmJ2u9/bUYNmHTGJx/UEmn6doAvvuig==" - }, "cookie": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", - "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } }, "debug": { "version": "4.1.1", @@ -50,43 +44,27 @@ } }, "engine.io": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.4.2.tgz", - "integrity": "sha512-b4Q85dFkGw+TqgytGPrGgACRUhsdKc9S9ErRAXpPGy/CXKs4tYoHDkvIRdsseAF7NjfVwjRFIn6KTnbw7LwJZg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.0.5.tgz", + "integrity": "sha512-Ri+whTNr2PKklxQkfbGjwEo+kCBUM4Qxk4wtLqLrhH+b1up2NFL9g9pjYWiCV/oazwB0rArnvF/ZmZN2ab5Hpg==", "requires": { "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "0.3.1", + "cookie": "~0.4.1", + "cors": "~2.8.5", "debug": "~4.1.0", - "engine.io-parser": "~2.2.0", + "engine.io-parser": "~4.0.0", "ws": "^7.1.2" } }, "engine.io-parser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-2.2.1.tgz", - "integrity": "sha512-x+dN/fBH8Ro8TFwJ+rkB2AmuVw9Yu2mockR/p3W8f8YtExwFgDvBDi0GWyb4ZLkpahtDGZgtr3zLovanJghPqg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", "requires": { - "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", - "base64-arraybuffer": "0.1.4", - "blob": "0.0.5", - "has-binary2": "~1.0.2" + "base64-arraybuffer": "0.1.4" } }, - "has-binary2": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", - "integrity": "sha512-G1LWKhDSvhGeAQ8mPVQlqNcOB2sJdwATtZKl2pDKKHfpf/rYj24lkinxf69blJbnsvtqqNU+L3SL50vzZhXOnw==", - "requires": { - "isarray": "2.0.1" - } - }, - "isarray": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" - }, "mime-db": { "version": "1.44.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", @@ -101,15 +79,25 @@ } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, "ws": { "version": "7.4.1", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.1.tgz", diff --git a/src/test/resources/package.json b/src/test/resources/package.json index d60498e..3b4d8a1 100644 --- a/src/test/resources/package.json +++ b/src/test/resources/package.json @@ -1,6 +1,6 @@ { "private": true, "dependencies": { - "engine.io": "^3.4.2" + "engine.io": "^4.0.5" } }