From 2252c180c0af822c28ff4305d7e729de3eb6a8c2 Mon Sep 17 00:00:00 2001 From: lotodore Date: Sat, 31 Jan 2015 22:48:58 +0100 Subject: [PATCH] Adding Java WebSocket lib for unit tests. --- src/third_party/Java-WebSocket/LICENSE | 22 + .../AbstractWrappedByteChannel.java | 75 + .../org/java_websocket/SSLSocketChannel2.java | 380 +++ .../java_websocket/SocketChannelIOHelper.java | 71 + .../org/java_websocket/WebSocket.java | 124 + .../org/java_websocket/WebSocketAdapter.java | 104 + .../org/java_websocket/WebSocketFactory.java | 12 + .../org/java_websocket/WebSocketImpl.java | 737 ++++++ .../org/java_websocket/WebSocketListener.java | 151 ++ .../java_websocket/WrappedByteChannel.java | 26 + .../client/AbstractClientProxyChannel.java | 38 + .../client/WebSocketClient.java | 454 ++++ .../org/java_websocket/drafts/Draft.java | 228 ++ .../org/java_websocket/drafts/Draft_10.java | 397 ++++ .../org/java_websocket/drafts/Draft_17.java | 28 + .../org/java_websocket/drafts/Draft_75.java | 206 ++ .../org/java_websocket/drafts/Draft_76.java | 242 ++ .../IncompleteHandshakeException.java | 20 + .../exceptions/InvalidDataException.java | 34 + .../exceptions/InvalidFrameException.java | 27 + .../exceptions/InvalidHandshakeException.java | 28 + .../exceptions/LimitExedeedException.java | 20 + .../exceptions/NotSendableException.java | 25 + .../WebsocketNotConnectedException.java | 5 + .../java_websocket/framing/CloseFrame.java | 98 + .../framing/CloseFrameBuilder.java | 123 + .../java_websocket/framing/FrameBuilder.java | 17 + .../org/java_websocket/framing/Framedata.java | 17 + .../framing/FramedataImpl1.java | 110 + .../handshake/ClientHandshake.java | 6 + .../handshake/ClientHandshakeBuilder.java | 5 + .../handshake/HandshakeBuilder.java | 6 + .../handshake/HandshakeImpl1Client.java | 18 + .../handshake/HandshakeImpl1Server.java | 29 + .../handshake/Handshakedata.java | 10 + .../handshake/HandshakedataImpl1.java | 60 + .../handshake/ServerHandshake.java | 6 + .../handshake/ServerHandshakeBuilder.java | 6 + .../DefaultSSLWebSocketServerFactory.java | 51 + .../server/DefaultWebSocketServerFactory.java | 26 + .../server/WebSocketServer.java | 736 ++++++ .../org/java_websocket/util/Base64.java | 2065 +++++++++++++++++ .../java_websocket/util/Charsetfunctions.java | 90 + 43 files changed, 6933 insertions(+) create mode 100644 src/third_party/Java-WebSocket/LICENSE create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/AbstractWrappedByteChannel.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/SSLSocketChannel2.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/SocketChannelIOHelper.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/WebSocket.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/WebSocketAdapter.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/WebSocketFactory.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/WebSocketImpl.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/WebSocketListener.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/WrappedByteChannel.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/client/AbstractClientProxyChannel.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/client/WebSocketClient.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_10.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_17.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_75.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_76.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/IncompleteHandshakeException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidDataException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidFrameException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidHandshakeException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/LimitExedeedException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/NotSendableException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/exceptions/WebsocketNotConnectedException.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrame.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrameBuilder.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/framing/FrameBuilder.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/framing/Framedata.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/framing/FramedataImpl1.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshake.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshakeBuilder.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeBuilder.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Client.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Server.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/Handshakedata.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakedataImpl1.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshake.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshakeBuilder.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/server/DefaultWebSocketServerFactory.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/server/WebSocketServer.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/util/Base64.java create mode 100644 src/third_party/Java-WebSocket/org/java_websocket/util/Charsetfunctions.java diff --git a/src/third_party/Java-WebSocket/LICENSE b/src/third_party/Java-WebSocket/LICENSE new file mode 100644 index 00000000..5a93449e --- /dev/null +++ b/src/third_party/Java-WebSocket/LICENSE @@ -0,0 +1,22 @@ + Copyright (c) 2010-2012 Nathan Rajlich + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. diff --git a/src/third_party/Java-WebSocket/org/java_websocket/AbstractWrappedByteChannel.java b/src/third_party/Java-WebSocket/org/java_websocket/AbstractWrappedByteChannel.java new file mode 100644 index 00000000..0481a6d4 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/AbstractWrappedByteChannel.java @@ -0,0 +1,75 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.SSLException; + + +public class AbstractWrappedByteChannel implements WrappedByteChannel { + + private final ByteChannel channel; + + public AbstractWrappedByteChannel( ByteChannel towrap ) { + this.channel = towrap; + } + + public AbstractWrappedByteChannel( WrappedByteChannel towrap ) { + this.channel = towrap; + } + + @Override + public int read( ByteBuffer dst ) throws IOException { + return channel.read( dst ); + } + + @Override + public boolean isOpen() { + return channel.isOpen(); + } + + @Override + public void close() throws IOException { + channel.close(); + } + + @Override + public int write( ByteBuffer src ) throws IOException { + return channel.write( src ); + } + + @Override + public boolean isNeedWrite() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedWrite() : false; + } + + @Override + public void writeMore() throws IOException { + if( channel instanceof WrappedByteChannel ) + ( (WrappedByteChannel) channel ).writeMore(); + + } + + @Override + public boolean isNeedRead() { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).isNeedRead() : false; + + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return channel instanceof WrappedByteChannel ? ( (WrappedByteChannel) channel ).readMore( dst ) : 0; + } + + @Override + public boolean isBlocking() { + if( channel instanceof SocketChannel ) + return ( (SocketChannel) channel ).isBlocking(); + else if( channel instanceof WrappedByteChannel ) + return ( (WrappedByteChannel) channel ).isBlocking(); + return false; + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/SSLSocketChannel2.java b/src/third_party/Java-WebSocket/org/java_websocket/SSLSocketChannel2.java new file mode 100644 index 00000000..ba8d8f88 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/SSLSocketChannel2.java @@ -0,0 +1,380 @@ +/** + * Copyright (C) 2003 Alexander Kout + * Originally from the jFxp project (http://jfxp.sourceforge.net/). + * Copied with permission June 11, 2012 by Femi Omojola (fomojola@ideasynthesis.com). + */ +package org.java_websocket; + +import java.io.IOException; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +import javax.net.ssl.SSLEngine; +import javax.net.ssl.SSLEngineResult; +import javax.net.ssl.SSLEngineResult.HandshakeStatus; +import javax.net.ssl.SSLEngineResult.Status; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; + +/** + * Implements the relevant portions of the SocketChannel interface with the SSLEngine wrapper. + */ +public class SSLSocketChannel2 implements ByteChannel, WrappedByteChannel { + /** + * This object is used to feed the {@link SSLEngine}'s wrap and unwrap methods during the handshake phase. + **/ + protected static ByteBuffer emptybuffer = ByteBuffer.allocate( 0 ); + + protected ExecutorService exec; + + protected List> tasks; + + /** raw payload incomming */ + protected ByteBuffer inData; + /** encrypted data outgoing */ + protected ByteBuffer outCrypt; + /** encrypted data incoming */ + protected ByteBuffer inCrypt; + + /** the underlying channel */ + protected SocketChannel socketChannel; + /** used to set interestOP SelectionKey.OP_WRITE for the underlying channel */ + protected SelectionKey selectionKey; + + protected SSLEngine sslEngine; + protected SSLEngineResult readEngineResult; + protected SSLEngineResult writeEngineResult; + + /** + * Should be used to count the buffer allocations. + * But because of #190 where HandshakeStatus.FINISHED is not properly returned by nio wrap/unwrap this variable is used to check whether {@link #createBuffers(SSLSession)} needs to be called. + **/ + protected int bufferallocations = 0; + + public SSLSocketChannel2( SocketChannel channel , SSLEngine sslEngine , ExecutorService exec , SelectionKey key ) throws IOException { + if( channel == null || sslEngine == null || exec == null ) + throw new IllegalArgumentException( "parameter must not be null" ); + + this.socketChannel = channel; + this.sslEngine = sslEngine; + this.exec = exec; + + readEngineResult = writeEngineResult = new SSLEngineResult( Status.BUFFER_UNDERFLOW, sslEngine.getHandshakeStatus(), 0, 0 ); // init to prevent NPEs + + tasks = new ArrayList>( 3 ); + if( key != null ) { + key.interestOps( key.interestOps() | SelectionKey.OP_WRITE ); + this.selectionKey = key; + } + createBuffers( sslEngine.getSession() ); + // kick off handshake + socketChannel.write( wrap( emptybuffer ) );// initializes res + processHandshake(); + } + + private void consumeFutureUninterruptible( Future f ) { + try { + boolean interrupted = false; + while ( true ) { + try { + f.get(); + break; + } catch ( InterruptedException e ) { + interrupted = true; + } + } + if( interrupted ) + Thread.currentThread().interrupt(); + } catch ( ExecutionException e ) { + throw new RuntimeException( e ); + } + } + + /** + * This method will do whatever necessary to process the sslengine handshake. + * Thats why it's called both from the {@link #read(ByteBuffer)} and {@link #write(ByteBuffer)} + **/ + private synchronized void processHandshake() throws IOException { + if( sslEngine.getHandshakeStatus() == HandshakeStatus.NOT_HANDSHAKING ) + return; // since this may be called either from a reading or a writing thread and because this method is synchronized it is necessary to double check if we are still handshaking. + if( !tasks.isEmpty() ) { + Iterator> it = tasks.iterator(); + while ( it.hasNext() ) { + Future f = it.next(); + if( f.isDone() ) { + it.remove(); + } else { + if( isBlocking() ) + consumeFutureUninterruptible( f ); + return; + } + } + } + + if( sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_UNWRAP ) { + if( !isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) { + inCrypt.compact(); + int read = socketChannel.read( inCrypt ); + if( read == -1 ) { + throw new IOException( "connection closed unexpectedly by peer" ); + } + inCrypt.flip(); + } + inData.compact(); + unwrap(); + if( readEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } + } + consumeDelegatedTasks(); + if( tasks.isEmpty() || sslEngine.getHandshakeStatus() == SSLEngineResult.HandshakeStatus.NEED_WRAP ) { + socketChannel.write( wrap( emptybuffer ) ); + if( writeEngineResult.getHandshakeStatus() == HandshakeStatus.FINISHED ) { + createBuffers( sslEngine.getSession() ); + return; + } + } + assert ( sslEngine.getHandshakeStatus() != HandshakeStatus.NOT_HANDSHAKING );// this function could only leave NOT_HANDSHAKING after createBuffers was called unless #190 occurs which means that nio wrap/unwrap never return HandshakeStatus.FINISHED + + bufferallocations = 1; // look at variable declaration why this line exists and #190. Without this line buffers would not be be recreated when #190 AND a rehandshake occur. + } + private synchronized ByteBuffer wrap( ByteBuffer b ) throws SSLException { + outCrypt.compact(); + writeEngineResult = sslEngine.wrap( b, outCrypt ); + outCrypt.flip(); + return outCrypt; + } + + /** + * performs the unwrap operation by unwrapping from {@link #inCrypt} to {@link #inData} + **/ + private synchronized ByteBuffer unwrap() throws SSLException { + int rem; + do { + rem = inData.remaining(); + readEngineResult = sslEngine.unwrap( inCrypt, inData ); + } while ( readEngineResult.getStatus() == SSLEngineResult.Status.OK && ( rem != inData.remaining() || sslEngine.getHandshakeStatus() == HandshakeStatus.NEED_UNWRAP ) ); + inData.flip(); + return inData; + } + + protected void consumeDelegatedTasks() { + Runnable task; + while ( ( task = sslEngine.getDelegatedTask() ) != null ) { + tasks.add( exec.submit( task ) ); + // task.run(); + } + } + + protected void createBuffers( SSLSession session ) { + int netBufferMax = session.getPacketBufferSize(); + int appBufferMax = Math.max(session.getApplicationBufferSize(), netBufferMax); + + if( inData == null ) { + inData = ByteBuffer.allocate( appBufferMax ); + outCrypt = ByteBuffer.allocate( netBufferMax ); + inCrypt = ByteBuffer.allocate( netBufferMax ); + } else { + if( inData.capacity() != appBufferMax ) + inData = ByteBuffer.allocate( appBufferMax ); + if( outCrypt.capacity() != netBufferMax ) + outCrypt = ByteBuffer.allocate( netBufferMax ); + if( inCrypt.capacity() != netBufferMax ) + inCrypt = ByteBuffer.allocate( netBufferMax ); + } + inData.rewind(); + inData.flip(); + inCrypt.rewind(); + inCrypt.flip(); + outCrypt.rewind(); + outCrypt.flip(); + bufferallocations++; + } + + public int write( ByteBuffer src ) throws IOException { + if( !isHandShakeComplete() ) { + processHandshake(); + return 0; + } + // assert ( bufferallocations > 1 ); //see #190 + //if( bufferallocations <= 1 ) { + // createBuffers( sslEngine.getSession() ); + //} + int num = socketChannel.write( wrap( src ) ); + return num; + + } + + /** + * Blocks when in blocking mode until at least one byte has been decoded.
+ * When not in blocking mode 0 may be returned. + * + * @return the number of bytes read. + **/ + public int read( ByteBuffer dst ) throws IOException { + if( !dst.hasRemaining() ) + return 0; + if( !isHandShakeComplete() ) { + if( isBlocking() ) { + while ( !isHandShakeComplete() ) { + processHandshake(); + } + } else { + processHandshake(); + if( !isHandShakeComplete() ) { + return 0; + } + } + } + // assert ( bufferallocations > 1 ); //see #190 + //if( bufferallocations <= 1 ) { + // createBuffers( sslEngine.getSession() ); + //} + /* 1. When "dst" is smaller than "inData" readRemaining will fill "dst" with data decoded in a previous read call. + * 2. When "inCrypt" contains more data than "inData" has remaining space, unwrap has to be called on more time(readRemaining) + */ + int purged = readRemaining( dst ); + if( purged != 0 ) + return purged; + + /* We only continue when we really need more data from the network. + * Thats the case if inData is empty or inCrypt holds to less data than necessary for decryption + */ + assert ( inData.position() == 0 ); + inData.clear(); + + if( !inCrypt.hasRemaining() ) + inCrypt.clear(); + else + inCrypt.compact(); + + if( isBlocking() || readEngineResult.getStatus() == Status.BUFFER_UNDERFLOW ) + if( socketChannel.read( inCrypt ) == -1 ) { + return -1; + } + inCrypt.flip(); + unwrap(); + + int transfered = transfereTo( inData, dst ); + if( transfered == 0 && isBlocking() ) { + return read( dst ); // "transfered" may be 0 when not enough bytes were received or during rehandshaking + } + return transfered; + } + /** + * {@link #read(ByteBuffer)} may not be to leave all buffers(inData, inCrypt) + **/ + private int readRemaining( ByteBuffer dst ) throws SSLException { + if( inData.hasRemaining() ) { + return transfereTo( inData, dst ); + } + if( !inData.hasRemaining() ) + inData.clear(); + // test if some bytes left from last read (e.g. BUFFER_UNDERFLOW) + if( inCrypt.hasRemaining() ) { + unwrap(); + int amount = transfereTo( inData, dst ); + if( amount > 0 ) + return amount; + } + return 0; + } + + public boolean isConnected() { + return socketChannel.isConnected(); + } + + public void close() throws IOException { + sslEngine.closeOutbound(); + sslEngine.getSession().invalidate(); + if( socketChannel.isOpen() ) + socketChannel.write( wrap( emptybuffer ) );// FIXME what if not all bytes can be written + socketChannel.close(); + exec.shutdownNow(); + } + + private boolean isHandShakeComplete() { + HandshakeStatus status = sslEngine.getHandshakeStatus(); + return status == SSLEngineResult.HandshakeStatus.FINISHED || status == SSLEngineResult.HandshakeStatus.NOT_HANDSHAKING; + } + + public SelectableChannel configureBlocking( boolean b ) throws IOException { + return socketChannel.configureBlocking( b ); + } + + public boolean connect( SocketAddress remote ) throws IOException { + return socketChannel.connect( remote ); + } + + public boolean finishConnect() throws IOException { + return socketChannel.finishConnect(); + } + + public Socket socket() { + return socketChannel.socket(); + } + + public boolean isInboundDone() { + return sslEngine.isInboundDone(); + } + + @Override + public boolean isOpen() { + return socketChannel.isOpen(); + } + + @Override + public boolean isNeedWrite() { + return outCrypt.hasRemaining() || !isHandShakeComplete(); // FIXME this condition can cause high cpu load during handshaking when network is slow + } + + @Override + public void writeMore() throws IOException { + write( outCrypt ); + } + + @Override + public boolean isNeedRead() { + return inData.hasRemaining() || ( inCrypt.hasRemaining() && readEngineResult.getStatus() != Status.BUFFER_UNDERFLOW && readEngineResult.getStatus() != Status.CLOSED ); + } + + @Override + public int readMore( ByteBuffer dst ) throws SSLException { + return readRemaining( dst ); + } + + private int transfereTo( ByteBuffer from, ByteBuffer to ) { + int fremain = from.remaining(); + int toremain = to.remaining(); + if( fremain > toremain ) { + // FIXME there should be a more efficient transfer method + int limit = Math.min( fremain, toremain ); + for( int i = 0 ; i < limit ; i++ ) { + to.put( from.get() ); + } + return limit; + } else { + to.put( from ); + return fremain; + } + + } + + @Override + public boolean isBlocking() { + return socketChannel.isBlocking(); + } + +} \ No newline at end of file diff --git a/src/third_party/Java-WebSocket/org/java_websocket/SocketChannelIOHelper.java b/src/third_party/Java-WebSocket/org/java_websocket/SocketChannelIOHelper.java new file mode 100644 index 00000000..e0da2bdc --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/SocketChannelIOHelper.java @@ -0,0 +1,71 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.spi.AbstractSelectableChannel; + +import org.java_websocket.WebSocket.Role; + +public class SocketChannelIOHelper { + + public static boolean read( final ByteBuffer buf, WebSocketImpl ws, ByteChannel channel ) throws IOException { + buf.clear(); + int read = channel.read( buf ); + buf.flip(); + + if( read == -1 ) { + ws.eot(); + return false; + } + return read != 0; + } + + /** + * @see WrappedByteChannel#readMore(ByteBuffer) + * @return returns whether there is more data left which can be obtained via {@link #readMore(ByteBuffer, WebSocketImpl, WrappedByteChannel)} + **/ + public static boolean readMore( final ByteBuffer buf, WebSocketImpl ws, WrappedByteChannel channel ) throws IOException { + buf.clear(); + int read = channel.readMore( buf ); + buf.flip(); + + if( read == -1 ) { + ws.eot(); + return false; + } + return channel.isNeedRead(); + } + + /** Returns whether the whole outQueue has been flushed */ + public static boolean batch( WebSocketImpl ws, ByteChannel sockchannel ) throws IOException { + ByteBuffer buffer = ws.outQueue.peek(); + WrappedByteChannel c = null; + + if( buffer == null ) { + if( sockchannel instanceof WrappedByteChannel ) { + c = (WrappedByteChannel) sockchannel; + if( c.isNeedWrite() ) { + c.writeMore(); + } + } + } else { + do {// FIXME writing as much as possible is unfair!! + /*int written = */sockchannel.write( buffer ); + if( buffer.remaining() > 0 ) { + return false; + } else { + ws.outQueue.poll(); // Buffer finished. Remove it. + buffer = ws.outQueue.peek(); + } + } while ( buffer != null ); + } + + if( ws.outQueue.isEmpty() && ws.isFlushAndClose() && ws.getDraft().getRole() == Role.SERVER ) {// + synchronized ( ws ) { + ws.closeConnection(); + } + } + return c != null ? !( (WrappedByteChannel) sockchannel ).isNeedWrite() : true; + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/WebSocket.java b/src/third_party/Java-WebSocket/org/java_websocket/WebSocket.java new file mode 100644 index 00000000..a661eddb --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/WebSocket.java @@ -0,0 +1,124 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; + +public interface WebSocket { + public enum Role { + CLIENT, SERVER + } + + public enum READYSTATE { + NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED; + } + + /** + * The default port of WebSockets, as defined in the spec. If the nullary + * constructor is used, DEFAULT_PORT will be the port the WebSocketServer + * is binded to. Note that ports under 1024 usually require root permissions. + */ + public static final int DEFAULT_PORT = 80; + + public static final int DEFAULT_WSS_PORT = 443; + + /** + * sends the closing handshake. + * may be send in response to an other handshake. + */ + public void close( int code, String message ); + + public void close( int code ); + + /** Convenience function which behaves like close(CloseFrame.NORMAL) */ + public void close(); + + /** + * This will close the connection immediately without a proper close handshake. + * The code and the message therefore won't be transfered over the wire also they will be forwarded to onClose/onWebsocketClose. + **/ + public abstract void closeConnection( int code, String message ); + + /** + * Send Text data to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + public abstract void send( String text ) throws NotYetConnectedException; + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + public abstract void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException; + + public abstract void send( byte[] bytes ) throws IllegalArgumentException , NotYetConnectedException; + + public abstract void sendFrame( Framedata framedata ); + + /** + * Allows to send continuous/fragmented frames conveniently.
+ * For more into on this frame type see http://tools.ietf.org/html/rfc6455#section-5.4
+ * + * If the first frame you send is also the last then it is not a fragmented frame and will received via onMessage instead of onFragmented even though it was send by this method. + * + * @param op + * This is only important for the first frame in the sequence. Opcode.TEXT, Opcode.BINARY are allowed. + * @param buffer + * The buffer which contains the payload. It may have no bytes remaining. + * @param fin + * true means the current frame is the last in the sequence. + **/ + public abstract void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ); + + public abstract boolean hasBufferedData(); + + /** + * @returns never returns null + */ + public abstract InetSocketAddress getRemoteSocketAddress(); + + /** + * @returns never returns null + */ + public abstract InetSocketAddress getLocalSocketAddress(); + + public abstract boolean isConnecting(); + + public abstract boolean isOpen(); + + public abstract boolean isClosing(); + + /** + * Returns true when no further frames may be submitted
+ * This happens before the socket connection is closed. + */ + public abstract boolean isFlushAndClose(); + + /** Returns whether the close handshake has been completed and the socket is closed. */ + public abstract boolean isClosed(); + + public abstract Draft getDraft(); + + /** + * Retrieve the WebSocket 'readyState'. + * This represents the state of the connection. + * It returns a numerical value, as per W3C WebSockets specs. + * + * @return Returns '0 = CONNECTING', '1 = OPEN', '2 = CLOSING' or '3 = CLOSED' + */ + public abstract READYSTATE getReadyState(); + + /** + * Returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2
+ * If the opening handshake has not yet happened it will return null. + **/ + public abstract String getResourceDescriptor(); +} \ No newline at end of file diff --git a/src/third_party/Java-WebSocket/org/java_websocket/WebSocketAdapter.java b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketAdapter.java new file mode 100644 index 00000000..290e1049 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketAdapter.java @@ -0,0 +1,104 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * This class default implements all methods of the WebSocketListener that can be overridden optionally when advances functionalities is needed.
+ **/ +public abstract class WebSocketAdapter implements WebSocketListener { + + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake) + */ + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return new HandshakeImpl1Server(); + } + + @Override + public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException { + } + + /** + * This default implementation does not do anything which will cause the connections to always progress. + * + * @see org.java_websocket.WebSocketListener#onWebsocketHandshakeSentAsClient(WebSocket, ClientHandshake) + */ + @Override + public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException { + } + + /** + * This default implementation does not do anything. Go ahead and overwrite it + * + * @see org.java_websocket.WebSocketListener#onWebsocketMessageFragment(WebSocket, Framedata) + */ + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + } + + /** + * This default implementation will send a pong in response to the received ping. + * The pong frame will have the same payload as the ping frame. + * + * @see org.java_websocket.WebSocketListener#onWebsocketPing(WebSocket, Framedata) + */ + @Override + public void onWebsocketPing( WebSocket conn, Framedata f ) { + FramedataImpl1 resp = new FramedataImpl1( f ); + resp.setOptcode( Opcode.PONG ); + conn.sendFrame( resp ); + } + + /** + * This default implementation does not do anything. Go ahead and overwrite it. + * + * @see @see org.java_websocket.WebSocketListener#onWebsocketPong(WebSocket, Framedata) + */ + @Override + public void onWebsocketPong( WebSocket conn, Framedata f ) { + } + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * + * The default implementation allows access from all remote domains, but + * only on the port that this WebSocketServer is listening on. + * + * This is specifically implemented for gitime's WebSocket client for Flash: + * http://github.com/gimite/web-socket-js + * + * @return An XML String that comforts to Flash's security policy. You MUST + * not include the null char at the end, it is appended automatically. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained e.g because the websocket is not connected. + */ + @Override + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException { + InetSocketAddress adr = conn.getLocalSocketAddress(); + if(null == adr){ + throw new InvalidHandshakeException( "socket not bound" ); + } + + StringBuffer sb = new StringBuffer( 90 ); + sb.append( "\0" ); + + return sb.toString(); + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/WebSocketFactory.java b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketFactory.java new file mode 100644 index 00000000..651e9743 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketFactory.java @@ -0,0 +1,12 @@ +package org.java_websocket; + +import java.net.Socket; +import java.util.List; + +import org.java_websocket.drafts.Draft; + +public interface WebSocketFactory { + public WebSocket createWebSocket( WebSocketAdapter a, Draft d, Socket s ); + public WebSocket createWebSocket( WebSocketAdapter a, List drafts, Socket s ); + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/WebSocketImpl.java b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketImpl.java new file mode 100644 index 00000000..669bee14 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketImpl.java @@ -0,0 +1,737 @@ +package org.java_websocket; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.NotYetConnectedException; +import java.nio.channels.SelectionKey; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.LinkedBlockingQueue; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft.CloseHandshakeType; +import org.java_websocket.drafts.Draft.HandshakeState; +import org.java_websocket.drafts.Draft_10; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.drafts.Draft_75; +import org.java_websocket.drafts.Draft_76; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.WebsocketNotConnectedException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.server.WebSocketServer.WebSocketWorker; +import org.java_websocket.util.Charsetfunctions; + +/** + * Represents one end (client or server) of a single WebSocketImpl connection. + * Takes care of the "handshake" phase, then allows for easy sending of + * text frames, and receiving frames through an event-based model. + * + */ +public class WebSocketImpl implements WebSocket { + + public static int RCVBUF = 16384; + + public static/*final*/boolean DEBUG = false; // must be final in the future in order to take advantage of VM optimization + + public static final List defaultdraftlist = new ArrayList( 4 ); + static { + defaultdraftlist.add( new Draft_17() ); + defaultdraftlist.add( new Draft_10() ); + defaultdraftlist.add( new Draft_76() ); + defaultdraftlist.add( new Draft_75() ); + } + + public SelectionKey key; + + /** the possibly wrapped channel object whose selection is controlled by {@link #key} */ + public ByteChannel channel; + /** + * Queue of buffers that need to be sent to the client. + */ + public final BlockingQueue outQueue; + /** + * Queue of buffers that need to be processed + */ + public final BlockingQueue inQueue; + + /** + * Helper variable meant to store the thread which ( exclusively ) triggers this objects decode method. + **/ + public volatile WebSocketWorker workerThread; // TODO reset worker? + + /** When true no further frames may be submitted to be sent */ + private volatile boolean flushandclosestate = false; + + private READYSTATE readystate = READYSTATE.NOT_YET_CONNECTED; + + /** + * The listener to notify of WebSocket events. + */ + private final WebSocketListener wsl; + + private List knownDrafts; + + private Draft draft = null; + + private Role role; + + private Opcode current_continuous_frame_opcode = null; + + /** the bytes of an incomplete received handshake */ + private ByteBuffer tmpHandshakeBytes = ByteBuffer.allocate( 0 ); + + /** stores the handshake sent by this websocket ( Role.CLIENT only ) */ + private ClientHandshake handshakerequest = null; + + private String closemessage = null; + private Integer closecode = null; + private Boolean closedremotely = null; + + private String resourceDescriptor = null; + + /** + * crates a websocket with server role + */ + public WebSocketImpl( WebSocketListener listener , List drafts ) { + this( listener, (Draft) null ); + this.role = Role.SERVER; + // draft.copyInstance will be called when the draft is first needed + if( drafts == null || drafts.isEmpty() ) { + knownDrafts = defaultdraftlist; + } else { + knownDrafts = drafts; + } + } + + /** + * crates a websocket with client role + * + * @param socket + * may be unbound + */ + public WebSocketImpl( WebSocketListener listener , Draft draft ) { + if( listener == null || ( draft == null && role == Role.SERVER ) )// socket can be null because we want do be able to create the object without already having a bound channel + throw new IllegalArgumentException( "parameters must not be null" ); + this.outQueue = new LinkedBlockingQueue(); + inQueue = new LinkedBlockingQueue(); + this.wsl = listener; + this.role = Role.CLIENT; + if( draft != null ) + this.draft = draft.copyInstance(); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , Draft draft , Socket socket ) { + this( listener, draft ); + } + + @Deprecated + public WebSocketImpl( WebSocketListener listener , List drafts , Socket socket ) { + this( listener, drafts ); + } + + /** + * + */ + public void decode( ByteBuffer socketBuffer ) { + assert ( socketBuffer.hasRemaining() ); + + if( DEBUG ) + System.out.println( "process(" + socketBuffer.remaining() + "): {" + ( socketBuffer.remaining() > 1000 ? "too big to display" : new String( socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining() ) ) + "}" ); + + if( readystate != READYSTATE.NOT_YET_CONNECTED ) { + decodeFrames( socketBuffer );; + } else { + if( decodeHandshake( socketBuffer ) ) { + assert ( tmpHandshakeBytes.hasRemaining() != socketBuffer.hasRemaining() || !socketBuffer.hasRemaining() ); // the buffers will never have remaining bytes at the same time + + if( socketBuffer.hasRemaining() ) { + decodeFrames( socketBuffer ); + } else if( tmpHandshakeBytes.hasRemaining() ) { + decodeFrames( tmpHandshakeBytes ); + } + } + } + assert ( isClosing() || isFlushAndClose() || !socketBuffer.hasRemaining() ); + } + /** + * Returns whether the handshake phase has is completed. + * In case of a broken handshake this will be never the case. + **/ + private boolean decodeHandshake( ByteBuffer socketBufferNew ) { + ByteBuffer socketBuffer; + if( tmpHandshakeBytes.capacity() == 0 ) { + socketBuffer = socketBufferNew; + } else { + if( tmpHandshakeBytes.remaining() < socketBufferNew.remaining() ) { + ByteBuffer buf = ByteBuffer.allocate( tmpHandshakeBytes.capacity() + socketBufferNew.remaining() ); + tmpHandshakeBytes.flip(); + buf.put( tmpHandshakeBytes ); + tmpHandshakeBytes = buf; + } + + tmpHandshakeBytes.put( socketBufferNew ); + tmpHandshakeBytes.flip(); + socketBuffer = tmpHandshakeBytes; + } + socketBuffer.mark(); + try { + if( draft == null ) { + HandshakeState isflashedgecase = isFlashEdgeCase( socketBuffer ); + if( isflashedgecase == HandshakeState.MATCHED ) { + try { + write( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( wsl.getFlashPolicy( this ) ) ) ); + close( CloseFrame.FLASHPOLICY, "" ); + } catch ( InvalidDataException e ) { + close( CloseFrame.ABNORMAL_CLOSE, "remote peer closed connection before flashpolicy could be transmitted", true ); + } + return false; + } + } + HandshakeState handshakestate = null; + + try { + if( role == Role.SERVER ) { + if( draft == null ) { + for( Draft d : knownDrafts ) { + d = d.copyInstance(); + try { + d.setParseMode( role ); + socketBuffer.reset(); + Handshakedata tmphandshake = d.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ClientHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = d.acceptHandshakeAsServer( handshake ); + if( handshakestate == HandshakeState.MATCHED ) { + resourceDescriptor = handshake.getResourceDescriptor(); + ServerHandshakeBuilder response; + try { + response = wsl.onWebsocketHandshakeReceivedAsServer( this, d, handshake ); + } catch ( InvalidDataException e ) { + flushAndClose( e.getCloseCode(), e.getMessage(), false ); + return false; + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); + return false; + } + write( d.createHandshake( d.postProcessHandshakeResponseAsServer( handshake, response ), role ) ); + draft = d; + open( handshake ); + return true; + } + } catch ( InvalidHandshakeException e ) { + // go on with an other draft + } + } + if( draft == null ) { + close( CloseFrame.PROTOCOL_ERROR, "no draft matches" ); + } + return false; + } else { + // special case for multiple step handshakes + Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ClientHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ClientHandshake handshake = (ClientHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsServer( handshake ); + + if( handshakestate == HandshakeState.MATCHED ) { + open( handshake ); + return true; + } else { + close( CloseFrame.PROTOCOL_ERROR, "the handshake did finaly not match" ); + } + return false; + } + } else if( role == Role.CLIENT ) { + draft.setParseMode( role ); + Handshakedata tmphandshake = draft.translateHandshake( socketBuffer ); + if( tmphandshake instanceof ServerHandshake == false ) { + flushAndClose( CloseFrame.PROTOCOL_ERROR, "wrong http function", false ); + return false; + } + ServerHandshake handshake = (ServerHandshake) tmphandshake; + handshakestate = draft.acceptHandshakeAsClient( handshakerequest, handshake ); + if( handshakestate == HandshakeState.MATCHED ) { + try { + wsl.onWebsocketHandshakeReceivedAsClient( this, handshakerequest, handshake ); + } catch ( InvalidDataException e ) { + flushAndClose( e.getCloseCode(), e.getMessage(), false ); + return false; + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.NEVER_CONNECTED, e.getMessage(), false ); + return false; + } + open( handshake ); + return true; + } else { + close( CloseFrame.PROTOCOL_ERROR, "draft " + draft + " refuses handshake" ); + } + } + } catch ( InvalidHandshakeException e ) { + close( e ); + } + } catch ( IncompleteHandshakeException e ) { + if( tmpHandshakeBytes.capacity() == 0 ) { + socketBuffer.reset(); + int newsize = e.getPreferedSize(); + if( newsize == 0 ) { + newsize = socketBuffer.capacity() + 16; + } else { + assert ( e.getPreferedSize() >= socketBuffer.remaining() ); + } + tmpHandshakeBytes = ByteBuffer.allocate( newsize ); + + tmpHandshakeBytes.put( socketBufferNew ); + // tmpHandshakeBytes.flip(); + } else { + tmpHandshakeBytes.position( tmpHandshakeBytes.limit() ); + tmpHandshakeBytes.limit( tmpHandshakeBytes.capacity() ); + } + } + return false; + } + + private void decodeFrames( ByteBuffer socketBuffer ) { + + List frames; + try { + frames = draft.translateFrame( socketBuffer ); + for( Framedata f : frames ) { + if( DEBUG ) + System.out.println( "matched frame: " + f ); + Opcode curop = f.getOpcode(); + boolean fin = f.isFin(); + + if( curop == Opcode.CLOSING ) { + int code = CloseFrame.NOCODE; + String reason = ""; + if( f instanceof CloseFrame ) { + CloseFrame cf = (CloseFrame) f; + code = cf.getCloseCode(); + reason = cf.getMessage(); + } + if( readystate == READYSTATE.CLOSING ) { + // complete the close handshake by disconnecting + closeConnection( code, reason, true ); + } else { + // echo close handshake + if( draft.getCloseHandshakeType() == CloseHandshakeType.TWOWAY ) + close( code, reason, true ); + else + flushAndClose( code, reason, false ); + } + continue; + } else if( curop == Opcode.PING ) { + wsl.onWebsocketPing( this, f ); + continue; + } else if( curop == Opcode.PONG ) { + wsl.onWebsocketPong( this, f ); + continue; + } else if( !fin || curop == Opcode.CONTINUOUS ) { + if( curop != Opcode.CONTINUOUS ) { + if( current_continuous_frame_opcode != null ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Previous continuous frame sequence not completed." ); + current_continuous_frame_opcode = curop; + } else if( fin ) { + if( current_continuous_frame_opcode == null ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); + current_continuous_frame_opcode = null; + } else if( current_continuous_frame_opcode == null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence was not started." ); + } + try { + wsl.onWebsocketMessageFragment( this, f ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + + } else if( current_continuous_frame_opcode != null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Continuous frame sequence not completed." ); + } else if( curop == Opcode.TEXT ) { + try { + wsl.onWebsocketMessage( this, Charsetfunctions.stringUtf8( f.getPayloadData() ) ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } else if( curop == Opcode.BINARY ) { + try { + wsl.onWebsocketMessage( this, f.getPayloadData() ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } else { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "non control or continious frame expected" ); + } + } + } catch ( InvalidDataException e1 ) { + wsl.onWebsocketError( this, e1 ); + close( e1 ); + return; + } + } + + private void close( int code, String message, boolean remote ) { + if( readystate != READYSTATE.CLOSING && readystate != READYSTATE.CLOSED ) { + if( readystate == READYSTATE.OPEN ) { + if( code == CloseFrame.ABNORMAL_CLOSE ) { + assert ( remote == false ); + readystate = READYSTATE.CLOSING; + flushAndClose( code, message, false ); + return; + } + if( draft.getCloseHandshakeType() != CloseHandshakeType.NONE ) { + try { + if( !remote ) { + try { + wsl.onWebsocketCloseInitiated( this, code, message ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } + sendFrame( new CloseFrameBuilder( code, message ) ); + } catch ( InvalidDataException e ) { + wsl.onWebsocketError( this, e ); + flushAndClose( CloseFrame.ABNORMAL_CLOSE, "generated frame is invalid", false ); + } + } + flushAndClose( code, message, remote ); + } else if( code == CloseFrame.FLASHPOLICY ) { + assert ( remote ); + flushAndClose( CloseFrame.FLASHPOLICY, message, true ); + } else { + flushAndClose( CloseFrame.NEVER_CONNECTED, message, false ); + } + if( code == CloseFrame.PROTOCOL_ERROR )// this endpoint found a PROTOCOL_ERROR + flushAndClose( code, message, remote ); + readystate = READYSTATE.CLOSING; + tmpHandshakeBytes = null; + return; + } + } + + @Override + public void close( int code, String message ) { + close( code, message, false ); + } + + /** + * + * @param remote + * Indicates who "generated" code.
+ * true means that this endpoint received the code from the other endpoint.
+ * false means this endpoint decided to send the given code,
+ * remote may also be true if this endpoint started the closing handshake since the other endpoint may not simply echo the code but close the connection the same time this endpoint does do but with an other code.
+ **/ + + protected synchronized void closeConnection( int code, String message, boolean remote ) { + if( readystate == READYSTATE.CLOSED ) { + return; + } + + if( key != null ) { + // key.attach( null ); //see issue #114 + key.cancel(); + } + if( channel != null ) { + try { + channel.close(); + } catch ( IOException e ) { + wsl.onWebsocketError( this, e ); + } + } + try { + this.wsl.onWebsocketClose( this, code, message, remote ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + if( draft != null ) + draft.reset(); + handshakerequest = null; + + readystate = READYSTATE.CLOSED; + this.outQueue.clear(); + } + + protected void closeConnection( int code, boolean remote ) { + closeConnection( code, "", remote ); + } + + public void closeConnection() { + if( closedremotely == null ) { + throw new IllegalStateException( "this method must be used in conjuction with flushAndClose" ); + } + closeConnection( closecode, closemessage, closedremotely ); + } + + public void closeConnection( int code, String message ) { + closeConnection( code, message, false ); + } + + protected synchronized void flushAndClose( int code, String message, boolean remote ) { + if( flushandclosestate ) { + return; + } + closecode = code; + closemessage = message; + closedremotely = remote; + + flushandclosestate = true; + + wsl.onWriteDemand( this ); // ensures that all outgoing frames are flushed before closing the connection + try { + wsl.onWebsocketClosing( this, code, message, remote ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + if( draft != null ) + draft.reset(); + handshakerequest = null; + } + + public void eot() { + if( getReadyState() == READYSTATE.NOT_YET_CONNECTED ) { + closeConnection( CloseFrame.NEVER_CONNECTED, true ); + } else if( flushandclosestate ) { + closeConnection( closecode, closemessage, closedremotely ); + } else if( draft.getCloseHandshakeType() == CloseHandshakeType.NONE ) { + closeConnection( CloseFrame.NORMAL, true ); + } else if( draft.getCloseHandshakeType() == CloseHandshakeType.ONEWAY ) { + if( role == Role.SERVER ) + closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + else + closeConnection( CloseFrame.NORMAL, true ); + } else { + closeConnection( CloseFrame.ABNORMAL_CLOSE, true ); + } + } + + @Override + public void close( int code ) { + close( code, "", false ); + } + + public void close( InvalidDataException e ) { + close( e.getCloseCode(), e.getMessage(), false ); + } + + /** + * Send Text data to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + @Override + public void send( String text ) throws WebsocketNotConnectedException { + if( text == null ) + throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); + send( draft.createFrames( text, role == Role.CLIENT ) ); + } + + /** + * Send Binary data (plain bytes) to the other end. + * + * @throws IllegalArgumentException + * @throws NotYetConnectedException + */ + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { + if( bytes == null ) + throw new IllegalArgumentException( "Cannot send 'null' data to a WebSocketImpl." ); + send( draft.createFrames( bytes, role == Role.CLIENT ) ); + } + + @Override + public void send( byte[] bytes ) throws IllegalArgumentException , WebsocketNotConnectedException { + send( ByteBuffer.wrap( bytes ) ); + } + + private void send( Collection frames ) { + if( !isOpen() ) + throw new WebsocketNotConnectedException(); + for( Framedata f : frames ) { + sendFrame( f ); + } + } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + send( draft.continuousFrame( op, buffer, fin ) ); + } + + @Override + public void sendFrame( Framedata framedata ) { + if( DEBUG ) + System.out.println( "send frame: " + framedata ); + write( draft.createBinaryFrame( framedata ) ); + } + + @Override + public boolean hasBufferedData() { + return !this.outQueue.isEmpty(); + } + + private HandshakeState isFlashEdgeCase( ByteBuffer request ) throws IncompleteHandshakeException { + request.mark(); + if( request.limit() > Draft.FLASH_POLICY_REQUEST.length ) { + return HandshakeState.NOT_MATCHED; + } else if( request.limit() < Draft.FLASH_POLICY_REQUEST.length ) { + throw new IncompleteHandshakeException( Draft.FLASH_POLICY_REQUEST.length ); + } else { + + for( int flash_policy_index = 0 ; request.hasRemaining() ; flash_policy_index++ ) { + if( Draft.FLASH_POLICY_REQUEST[ flash_policy_index ] != request.get() ) { + request.reset(); + return HandshakeState.NOT_MATCHED; + } + } + return HandshakeState.MATCHED; + } + } + + public void startHandshake( ClientHandshakeBuilder handshakedata ) throws InvalidHandshakeException { + assert ( readystate != READYSTATE.CONNECTING ) : "shall only be called once"; + + // Store the Handshake Request we are about to send + this.handshakerequest = draft.postProcessHandshakeRequestAsClient( handshakedata ); + + resourceDescriptor = handshakedata.getResourceDescriptor(); + assert( resourceDescriptor != null ); + + // Notify Listener + try { + wsl.onWebsocketHandshakeSentAsClient( this, this.handshakerequest ); + } catch ( InvalidDataException e ) { + // Stop if the client code throws an exception + throw new InvalidHandshakeException( "Handshake data rejected by client." ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + throw new InvalidHandshakeException( "rejected because of" + e ); + } + + // Send + write( draft.createHandshake( this.handshakerequest, role ) ); + } + + private void write( ByteBuffer buf ) { + if( DEBUG ) + System.out.println( "write(" + buf.remaining() + "): {" + ( buf.remaining() > 1000 ? "too big to display" : new String( buf.array() ) ) + "}" ); + + outQueue.add( buf ); + /*try { + outQueue.put( buf ); + } catch ( InterruptedException e ) { + write( buf ); + Thread.currentThread().interrupt(); // keep the interrupted status + e.printStackTrace(); + }*/ + wsl.onWriteDemand( this ); + } + + private void write( List bufs ) { + for( ByteBuffer b : bufs ) { + write( b ); + } + } + + private void open( Handshakedata d ) { + if( DEBUG ) + System.out.println( "open using draft: " + draft.getClass().getSimpleName() ); + readystate = READYSTATE.OPEN; + try { + wsl.onWebsocketOpen( this, d ); + } catch ( RuntimeException e ) { + wsl.onWebsocketError( this, e ); + } + } + + @Override + public boolean isConnecting() { + assert ( flushandclosestate ? readystate == READYSTATE.CONNECTING : true ); + return readystate == READYSTATE.CONNECTING; // ifflushandclosestate + } + + @Override + public boolean isOpen() { + assert ( readystate == READYSTATE.OPEN ? !flushandclosestate : true ); + return readystate == READYSTATE.OPEN; + } + + @Override + public boolean isClosing() { + return readystate == READYSTATE.CLOSING; + } + + @Override + public boolean isFlushAndClose() { + return flushandclosestate; + } + + @Override + public boolean isClosed() { + return readystate == READYSTATE.CLOSED; + } + + @Override + public READYSTATE getReadyState() { + return readystate; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return super.toString(); // its nice to be able to set breakpoints here + } + + @Override + public InetSocketAddress getRemoteSocketAddress() { + return wsl.getRemoteSocketAddress( this ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return wsl.getLocalSocketAddress( this ); + } + + @Override + public Draft getDraft() { + return draft; + } + + @Override + public void close() { + close( CloseFrame.NORMAL ); + } + + @Override + public String getResourceDescriptor() { + return resourceDescriptor; + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/WebSocketListener.java b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketListener.java new file mode 100644 index 00000000..93478d94 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/WebSocketListener.java @@ -0,0 +1,151 @@ +package org.java_websocket; + +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; + +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * Implemented by WebSocketClient and WebSocketServer. + * The methods within are called by WebSocket. + * Almost every method takes a first parameter conn which represents the source of the respective event. + */ +public interface WebSocketListener { + + /** + * Called on the server side when the socket connection is first established, and the WebSocket + * handshake has been received. This method allows to deny connections based on the received handshake.
+ * By default this method only requires protocol compliance. + * + * @param conn + * The WebSocket related to this event + * @param draft + * The protocol draft the client uses to connect + * @param request + * The opening http message send by the client. Can be used to access additional fields like cookies. + * @return Returns an incomplete handshake containing all optional fields + * @throws InvalidDataException + * Throwing this exception will cause this handshake to be rejected + */ + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the WebSocketImpl + * handshake response has been received. + * + * @param conn + * The WebSocket related to this event + * @param request + * The handshake initially send out to the server by this websocket. + * @param response + * The handshake the server sent in response to the request. + * @throws InvalidDataException + * Allows the client to reject the connection with the server in respect of its handshake response. + */ + public void onWebsocketHandshakeReceivedAsClient( WebSocket conn, ClientHandshake request, ServerHandshake response ) throws InvalidDataException; + + /** + * Called on the client side when the socket connection is first established, and the WebSocketImpl + * handshake has just been sent. + * + * @param conn + * The WebSocket related to this event + * @param request + * The handshake sent to the server by this websocket + * @throws InvalidDataException + * Allows the client to stop the connection from progressing + */ + public void onWebsocketHandshakeSentAsClient( WebSocket conn, ClientHandshake request ) throws InvalidDataException; + + /** + * Called when an entire text frame has been received. Do whatever you want + * here... + * + * @param conn + * The WebSocket instance this event is occurring on. + * @param message + * The UTF-8 decoded message that was received. + */ + public void onWebsocketMessage( WebSocket conn, String message ); + + /** + * Called when an entire binary frame has been received. Do whatever you want + * here... + * + * @param conn + * The WebSocket instance this event is occurring on. + * @param blob + * The binary message that was received. + */ + public void onWebsocketMessage( WebSocket conn, ByteBuffer blob ); + + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ); + + /** + * Called after onHandshakeReceived returns true. + * Indicates that a complete WebSocket connection has been established, + * and we are ready to send/receive data. + * + * @param conn + * The WebSocket instance this event is occuring on. + */ + public void onWebsocketOpen( WebSocket conn, Handshakedata d ); + + /** + * Called after WebSocket#close is explicity called, or when the + * other end of the WebSocket connection is closed. + * + * @param conn + * The WebSocket instance this event is occuring on. + */ + public void onWebsocketClose( WebSocket ws, int code, String reason, boolean remote ); + + /** called as soon as no further frames are accepted */ + public void onWebsocketClosing( WebSocket ws, int code, String reason, boolean remote ); + + /** send when this peer sends a close handshake */ + public void onWebsocketCloseInitiated( WebSocket ws, int code, String reason ); + + /** + * Called if an exception worth noting occurred. + * If an error causes the connection to fail onClose will be called additionally afterwards. + * + * @param ex + * The exception that occurred.
+ * Might be null if the exception is not related to any specific connection. For example if the server port could not be bound. + */ + public void onWebsocketError( WebSocket conn, Exception ex ); + + /** + * Called a ping frame has been received. + * This method must send a corresponding pong by itself. + * + * @param f + * The ping frame. Control frames may contain payload. + */ + public void onWebsocketPing( WebSocket conn, Framedata f ); + + /** + * Called when a pong frame is received. + **/ + public void onWebsocketPong( WebSocket conn, Framedata f ); + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * @throws InvalidDataException thrown when some data that is required to generate the flash-policy like the websocket local port could not be obtained. + */ + public String getFlashPolicy( WebSocket conn ) throws InvalidDataException; + + /** This method is used to inform the selector thread that there is data queued to be written to the socket. */ + public void onWriteDemand( WebSocket conn ); + + public InetSocketAddress getLocalSocketAddress( WebSocket conn ); + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/WrappedByteChannel.java b/src/third_party/Java-WebSocket/org/java_websocket/WrappedByteChannel.java new file mode 100644 index 00000000..83a3290b --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/WrappedByteChannel.java @@ -0,0 +1,26 @@ +package org.java_websocket; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import javax.net.ssl.SSLException; + +public interface WrappedByteChannel extends ByteChannel { + public boolean isNeedWrite(); + public void writeMore() throws IOException; + + /** + * returns whether readMore should be called to fetch data which has been decoded but not yet been returned. + * + * @see #read(ByteBuffer) + * @see #readMore(ByteBuffer) + **/ + public boolean isNeedRead(); + /** + * This function does not read data from the underlying channel at all. It is just a way to fetch data which has already be received or decoded but was but was not yet returned to the user. + * This could be the case when the decoded data did not fit into the buffer the user passed to {@link #read(ByteBuffer)}. + **/ + public int readMore( ByteBuffer dst ) throws SSLException; + public boolean isBlocking(); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/client/AbstractClientProxyChannel.java b/src/third_party/Java-WebSocket/org/java_websocket/client/AbstractClientProxyChannel.java new file mode 100644 index 00000000..bbac6725 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/client/AbstractClientProxyChannel.java @@ -0,0 +1,38 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; + +import org.java_websocket.AbstractWrappedByteChannel; + +public abstract class AbstractClientProxyChannel extends AbstractWrappedByteChannel { + protected final ByteBuffer proxyHandshake; + + + /** + * @param towrap + * The channel to the proxy server + **/ + public AbstractClientProxyChannel( ByteChannel towrap ) { + super( towrap ); + try { + proxyHandshake = ByteBuffer.wrap( buildHandShake().getBytes( "ASCII" ) ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + @Override + public int write( ByteBuffer src ) throws IOException { + if( !proxyHandshake.hasRemaining() ) { + return super.write( src ); + } else { + return super.write( proxyHandshake ); + } + } + + public abstract String buildHandShake(); + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/client/WebSocketClient.java b/src/third_party/Java-WebSocket/org/java_websocket/client/WebSocketClient.java new file mode 100644 index 00000000..86eff794 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/client/WebSocketClient.java @@ -0,0 +1,454 @@ +package org.java_websocket.client; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.Socket; +import java.net.URI; +import java.nio.ByteBuffer; +import java.nio.channels.NotYetConnectedException; +import java.util.Map; +import java.util.concurrent.CountDownLatch; + +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.drafts.Draft_17; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; + +/** + * A subclass must implement at least onOpen, onClose, and onMessage to be + * useful. At runtime the user is expected to establish a connection via {@link #connect()}, then receive events like {@link #onMessage(String)} via the overloaded methods and to {@link #send(String)} data to the server. + */ +public abstract class WebSocketClient extends WebSocketAdapter implements Runnable, WebSocket { + + /** + * The URI this channel is supposed to connect to. + */ + protected URI uri = null; + + private WebSocketImpl engine = null; + + private Socket socket = null; + + private InputStream istream; + + private OutputStream ostream; + + private Proxy proxy = Proxy.NO_PROXY; + + private Thread writeThread; + + private Draft draft; + + private Map headers; + + private CountDownLatch connectLatch = new CountDownLatch( 1 ); + + private CountDownLatch closeLatch = new CountDownLatch( 1 ); + + private int connectTimeout = 0; + + /** This open a websocket connection as specified by rfc6455 */ + public WebSocketClient( URI serverURI ) { + this( serverURI, new Draft_17() ); + } + + /** + * Constructs a WebSocketClient instance and sets it to the connect to the + * specified URI. The channel does not attampt to connect automatically. The connection + * will be established once you call connect. + */ + public WebSocketClient( URI serverUri , Draft draft ) { + this( serverUri, draft, null, 0 ); + } + + public WebSocketClient( URI serverUri , Draft protocolDraft , Map httpHeaders , int connectTimeout ) { + if( serverUri == null ) { + throw new IllegalArgumentException(); + } else if( protocolDraft == null ) { + throw new IllegalArgumentException( "null as draft is permitted for `WebSocketServer` only!" ); + } + this.uri = serverUri; + this.draft = protocolDraft; + this.headers = httpHeaders; + this.connectTimeout = connectTimeout; + this.engine = new WebSocketImpl( this, protocolDraft ); + } + + /** + * Returns the URI that this WebSocketClient is connected to. + */ + public URI getURI() { + return uri; + } + + /** + * Returns the protocol version this channel uses.
+ * For more infos see https://github.com/TooTallNate/Java-WebSocket/wiki/Drafts + */ + public Draft getDraft() { + return draft; + } + + /** + * Initiates the websocket connection. This method does not block. + */ + public void connect() { + if( writeThread != null ) + throw new IllegalStateException( "WebSocketClient objects are not reuseable" ); + writeThread = new Thread( this ); + writeThread.start(); + } + + /** + * Same as connect but blocks until the websocket connected or failed to do so.
+ * Returns whether it succeeded or not. + **/ + public boolean connectBlocking() throws InterruptedException { + connect(); + connectLatch.await(); + return engine.isOpen(); + } + + /** + * Initiates the websocket close handshake. This method does not block
+ * In oder to make sure the connection is closed use closeBlocking + */ + public void close() { + if( writeThread != null ) { + engine.close( CloseFrame.NORMAL ); + } + } + + public void closeBlocking() throws InterruptedException { + close(); + closeLatch.await(); + } + + /** + * Sends text to the connected websocket server. + * + * @param text + * The string which will be transmitted. + */ + public void send( String text ) throws NotYetConnectedException { + engine.send( text ); + } + + /** + * Sends binary data to the connected webSocket server. + * + * @param data + * The byte-Array of data to send to the WebSocket server. + */ + public void send( byte[] data ) throws NotYetConnectedException { + engine.send( data ); + } + + public void run() { + try { + if( socket == null ) { + socket = new Socket( proxy ); + } else if( socket.isClosed() ) { + throw new IOException(); + } + if( !socket.isBound() ) + socket.connect( new InetSocketAddress( uri.getHost(), getPort() ), connectTimeout ); + istream = socket.getInputStream(); + ostream = socket.getOutputStream(); + + sendHandshake(); + } catch ( /*IOException | SecurityException | UnresolvedAddressException | InvalidHandshakeException | ClosedByInterruptException | SocketTimeoutException */Exception e ) { + onWebsocketError( engine, e ); + engine.closeConnection( CloseFrame.NEVER_CONNECTED, e.getMessage() ); + return; + } + + writeThread = new Thread( new WebsocketWriteThread() ); + writeThread.start(); + + byte[] rawbuffer = new byte[ WebSocketImpl.RCVBUF ]; + int readBytes; + + try { + while ( !isClosed() && ( readBytes = istream.read( rawbuffer ) ) != -1 ) { + engine.decode( ByteBuffer.wrap( rawbuffer, 0, readBytes ) ); + } + engine.eot(); + } catch ( IOException e ) { + engine.eot(); + } catch ( RuntimeException e ) { + // this catch case covers internal errors only and indicates a bug in this websocket implementation + onError( e ); + engine.closeConnection( CloseFrame.ABNORMAL_CLOSE, e.getMessage() ); + } + assert ( socket.isClosed() ); + } + private int getPort() { + int port = uri.getPort(); + if( port == -1 ) { + String scheme = uri.getScheme(); + if( scheme.equals( "wss" ) ) { + return WebSocket.DEFAULT_WSS_PORT; + } else if( scheme.equals( "ws" ) ) { + return WebSocket.DEFAULT_PORT; + } else { + throw new RuntimeException( "unkonow scheme" + scheme ); + } + } + return port; + } + + private void sendHandshake() throws InvalidHandshakeException { + String path; + String part1 = uri.getPath(); + String part2 = uri.getQuery(); + if( part1 == null || part1.length() == 0 ) + path = "/"; + else + path = part1; + if( part2 != null ) + path += "?" + part2; + int port = getPort(); + String host = uri.getHost() + ( port != WebSocket.DEFAULT_PORT ? ":" + port : "" ); + + HandshakeImpl1Client handshake = new HandshakeImpl1Client(); + handshake.setResourceDescriptor( path ); + handshake.put( "Host", host ); + if( headers != null ) { + for( Map.Entry kv : headers.entrySet() ) { + handshake.put( kv.getKey(), kv.getValue() ); + } + } + engine.startHandshake( handshake ); + } + + /** + * This represents the state of the connection. + */ + public READYSTATE getReadyState() { + return engine.getReadyState(); + } + + /** + * Calls subclass' implementation of onMessage. + */ + @Override + public final void onWebsocketMessage( WebSocket conn, String message ) { + onMessage( message ); + } + + @Override + public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { + onMessage( blob ); + } + + @Override + public void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) { + onFragment( frame ); + } + + /** + * Calls subclass' implementation of onOpen. + */ + @Override + public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + connectLatch.countDown(); + onOpen( (ServerHandshake) handshake ); + } + + /** + * Calls subclass' implementation of onClose. + */ + @Override + public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + connectLatch.countDown(); + closeLatch.countDown(); + if( writeThread != null ) + writeThread.interrupt(); + try { + if( socket != null ) + socket.close(); + } catch ( IOException e ) { + onWebsocketError( this, e ); + } + onClose( code, reason, remote ); + } + + /** + * Calls subclass' implementation of onIOError. + */ + @Override + public final void onWebsocketError( WebSocket conn, Exception ex ) { + onError( ex ); + } + + @Override + public final void onWriteDemand( WebSocket conn ) { + // nothing to do + } + + @Override + public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { + onCloseInitiated( code, reason ); + } + + @Override + public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { + onClosing( code, reason, remote ); + } + + public void onCloseInitiated( int code, String reason ) { + } + + public void onClosing( int code, String reason, boolean remote ) { + } + + public WebSocket getConnection() { + return engine; + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + if( socket != null ) + return (InetSocketAddress) socket.getLocalSocketAddress(); + return null; + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + if( socket != null ) + return (InetSocketAddress) socket.getRemoteSocketAddress(); + return null; + } + + // ABTRACT METHODS ///////////////////////////////////////////////////////// + public abstract void onOpen( ServerHandshake handshakedata ); + public abstract void onMessage( String message ); + public abstract void onClose( int code, String reason, boolean remote ); + public abstract void onError( Exception ex ); + public void onMessage( ByteBuffer bytes ) { + } + public void onFragment( Framedata frame ) { + } + + private class WebsocketWriteThread implements Runnable { + @Override + public void run() { + Thread.currentThread().setName( "WebsocketWriteThread" ); + try { + while ( !Thread.interrupted() ) { + ByteBuffer buffer = engine.outQueue.take(); + ostream.write( buffer.array(), 0, buffer.limit() ); + ostream.flush(); + } + } catch ( IOException e ) { + engine.eot(); + } catch ( InterruptedException e ) { + // this thread is regularly terminated via an interrupt + } + } + } + + public void setProxy( Proxy proxy ) { + if( proxy == null ) + throw new IllegalArgumentException(); + this.proxy = proxy; + } + + /** + * Accepts bound and unbound sockets.
+ * This method must be called before connect. + * If the given socket is not yet bound it will be bound to the uri specified in the constructor. + **/ + public void setSocket( Socket socket ) { + if( this.socket != null ) { + throw new IllegalStateException( "socket has already been set" ); + } + this.socket = socket; + } + + @Override + public void sendFragmentedFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + engine.sendFragmentedFrame( op, buffer, fin ); + } + + @Override + public boolean isOpen() { + return engine.isOpen(); + } + + @Override + public boolean isFlushAndClose() { + return engine.isFlushAndClose(); + } + + @Override + public boolean isClosed() { + return engine.isClosed(); + } + + @Override + public boolean isClosing() { + return engine.isClosing(); + } + + @Override + public boolean isConnecting() { + return engine.isConnecting(); + } + + @Override + public boolean hasBufferedData() { + return engine.hasBufferedData(); + } + + @Override + public void close( int code ) { + engine.close(); + } + + @Override + public void close( int code, String message ) { + engine.close( code, message ); + } + + @Override + public void closeConnection( int code, String message ) { + engine.closeConnection( code, message ); + } + + @Override + public void send( ByteBuffer bytes ) throws IllegalArgumentException , NotYetConnectedException { + engine.send( bytes ); + } + + @Override + public void sendFrame( Framedata framedata ) { + engine.sendFrame( framedata ); + } + + @Override + public InetSocketAddress getLocalSocketAddress() { + return engine.getLocalSocketAddress(); + } + @Override + public InetSocketAddress getRemoteSocketAddress() { + return engine.getRemoteSocketAddress(); + } + + @Override + public String getResourceDescriptor() { + return uri.getPath(); + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft.java b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft.java new file mode 100644 index 00000000..65b34de8 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft.java @@ -0,0 +1,228 @@ +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.HandshakeImpl1Client; +import org.java_websocket.handshake.HandshakeImpl1Server; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Charsetfunctions; + +/** + * Base class for everything of a websocket specification which is not common such as the way the handshake is read or frames are transfered. + **/ +public abstract class Draft { + + public enum HandshakeState { + /** Handshake matched this Draft successfully */ + MATCHED, + /** Handshake is does not match this Draft */ + NOT_MATCHED + } + public enum CloseHandshakeType { + NONE, ONEWAY, TWOWAY + } + + public static int MAX_FAME_SIZE = 1000 * 1; + public static int INITIAL_FAMESIZE = 64; + + public static final byte[] FLASH_POLICY_REQUEST = Charsetfunctions.utf8Bytes( "\0" ); + + /** In some cases the handshake will be parsed different depending on whether */ + protected Role role = null; + + protected Opcode continuousFrameType = null; + + public static ByteBuffer readLine( ByteBuffer buf ) { + ByteBuffer sbuf = ByteBuffer.allocate( buf.remaining() ); + byte prev = '0'; + byte cur = '0'; + while ( buf.hasRemaining() ) { + prev = cur; + cur = buf.get(); + sbuf.put( cur ); + if( prev == (byte) '\r' && cur == (byte) '\n' ) { + sbuf.limit( sbuf.position() - 2 ); + sbuf.position( 0 ); + return sbuf; + + } + } + // ensure that there wont be any bytes skipped + buf.position( buf.position() - sbuf.position() ); + return null; + } + + public static String readStringLine( ByteBuffer buf ) { + ByteBuffer b = readLine( buf ); + return b == null ? null : Charsetfunctions.stringAscii( b.array(), 0, b.limit() ); + } + + public static HandshakeBuilder translateHandshakeHttp( ByteBuffer buf, Role role ) throws InvalidHandshakeException , IncompleteHandshakeException { + HandshakeBuilder handshake; + + String line = readStringLine( buf ); + if( line == null ) + throw new IncompleteHandshakeException( buf.capacity() + 128 ); + + String[] firstLineTokens = line.split( " ", 3 );// eg. HTTP/1.1 101 Switching the Protocols + if( firstLineTokens.length != 3 ) { + throw new InvalidHandshakeException(); + } + + if( role == Role.CLIENT ) { + // translating/parsing the response from the SERVER + handshake = new HandshakeImpl1Server(); + ServerHandshakeBuilder serverhandshake = (ServerHandshakeBuilder) handshake; + serverhandshake.setHttpStatus( Short.parseShort( firstLineTokens[ 1 ] ) ); + serverhandshake.setHttpStatusMessage( firstLineTokens[ 2 ] ); + } else { + // translating/parsing the request from the CLIENT + ClientHandshakeBuilder clienthandshake = new HandshakeImpl1Client(); + clienthandshake.setResourceDescriptor( firstLineTokens[ 1 ] ); + handshake = clienthandshake; + } + + line = readStringLine( buf ); + while ( line != null && line.length() > 0 ) { + String[] pair = line.split( ":", 2 ); + if( pair.length != 2 ) + throw new InvalidHandshakeException( "not an http header" ); + handshake.put( pair[ 0 ], pair[ 1 ].replaceFirst( "^ +", "" ) ); + line = readStringLine( buf ); + } + if( line == null ) + throw new IncompleteHandshakeException(); + return handshake; + } + + public abstract HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException; + + public abstract HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException; + + protected boolean basicAccept( Handshakedata handshakedata ) { + return handshakedata.getFieldValue( "Upgrade" ).equalsIgnoreCase( "websocket" ) && handshakedata.getFieldValue( "Connection" ).toLowerCase( Locale.ENGLISH ).contains( "upgrade" ); + } + + public abstract ByteBuffer createBinaryFrame( Framedata framedata ); // TODO Allow to send data on the base of an Iterator or InputStream + + public abstract List createFrames( ByteBuffer binary, boolean mask ); + + public abstract List createFrames( String text, boolean mask ); + + public List continuousFrame( Opcode op, ByteBuffer buffer, boolean fin ) { + if( op != Opcode.BINARY && op != Opcode.TEXT && op != Opcode.TEXT ) { + throw new IllegalArgumentException( "Only Opcode.BINARY or Opcode.TEXT are allowed" ); + } + + if( continuousFrameType != null ) { + continuousFrameType = Opcode.CONTINUOUS; + } else { + continuousFrameType = op; + } + + FrameBuilder bui = new FramedataImpl1( continuousFrameType ); + try { + bui.setPayload( buffer ); + } catch ( InvalidDataException e ) { + throw new RuntimeException( e ); // can only happen when one builds close frames(Opcode.Close) + } + bui.setFin( fin ); + if( fin ) { + continuousFrameType = null; + } else { + continuousFrameType = op; + } + return Collections.singletonList( (Framedata) bui ); + } + + public abstract void reset(); + + public List createHandshake( Handshakedata handshakedata, Role ownrole ) { + return createHandshake( handshakedata, ownrole, true ); + } + + public List createHandshake( Handshakedata handshakedata, Role ownrole, boolean withcontent ) { + StringBuilder bui = new StringBuilder( 100 ); + if( handshakedata instanceof ClientHandshake ) { + bui.append( "GET " ); + bui.append( ( (ClientHandshake) handshakedata ).getResourceDescriptor() ); + bui.append( " HTTP/1.1" ); + } else if( handshakedata instanceof ServerHandshake ) { + bui.append( "HTTP/1.1 101 " + ( (ServerHandshake) handshakedata ).getHttpStatusMessage() ); + } else { + throw new RuntimeException( "unknow role" ); + } + bui.append( "\r\n" ); + Iterator it = handshakedata.iterateHttpFields(); + while ( it.hasNext() ) { + String fieldname = it.next(); + String fieldvalue = handshakedata.getFieldValue( fieldname ); + bui.append( fieldname ); + bui.append( ": " ); + bui.append( fieldvalue ); + bui.append( "\r\n" ); + } + bui.append( "\r\n" ); + byte[] httpheader = Charsetfunctions.asciiBytes( bui.toString() ); + + byte[] content = withcontent ? handshakedata.getContent() : null; + ByteBuffer bytebuffer = ByteBuffer.allocate( ( content == null ? 0 : content.length ) + httpheader.length ); + bytebuffer.put( httpheader ); + if( content != null ) + bytebuffer.put( content ); + bytebuffer.flip(); + return Collections.singletonList( bytebuffer ); + } + + public abstract ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException; + + public abstract HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException; + + public abstract List translateFrame( ByteBuffer buffer ) throws InvalidDataException; + + public abstract CloseHandshakeType getCloseHandshakeType(); + + /** + * Drafts must only be by one websocket at all. To prevent drafts to be used more than once the Websocket implementation should call this method in order to create a new usable version of a given draft instance.
+ * The copy can be safely used in conjunction with a new websocket connection. + * */ + public abstract Draft copyInstance(); + + public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { + return translateHandshakeHttp( buf, role ); + } + + public int checkAlloc( int bytecount ) throws LimitExedeedException , InvalidDataException { + if( bytecount < 0 ) + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR, "Negative count" ); + return bytecount; + } + + public void setParseMode( Role role ) { + this.role = role; + } + + public Role getRole() { + return role; + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_10.java b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_10.java new file mode 100644 index 00000000..305460a5 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_10.java @@ -0,0 +1,397 @@ +package org.java_websocket.drafts; +import java.math.BigInteger; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Base64; +import org.java_websocket.util.Charsetfunctions; + +public class Draft_10 extends Draft { + + private class IncompleteException extends Throwable { + + /** + * It's Serializable. + */ + private static final long serialVersionUID = 7330519489840500997L; + + private int preferedsize; + public IncompleteException( int preferedsize ) { + this.preferedsize = preferedsize; + } + public int getPreferedSize() { + return preferedsize; + } + } + + public static int readVersion( Handshakedata handshakedata ) { + String vers = handshakedata.getFieldValue( "Sec-WebSocket-Version" ); + if( vers.length() > 0 ) { + int v; + try { + v = new Integer( vers.trim() ); + return v; + } catch ( NumberFormatException e ) { + return -1; + } + } + return -1; + } + + private ByteBuffer incompleteframe; + private Framedata fragmentedframe = null; + + private final Random reuseableRandom = new Random(); + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) throws InvalidHandshakeException { + if( !request.hasFieldValue( "Sec-WebSocket-Key" ) || !response.hasFieldValue( "Sec-WebSocket-Accept" ) ) + return HandshakeState.NOT_MATCHED; + + String seckey_answere = response.getFieldValue( "Sec-WebSocket-Accept" ); + String seckey_challenge = request.getFieldValue( "Sec-WebSocket-Key" ); + seckey_challenge = generateFinalKey( seckey_challenge ); + + if( seckey_challenge.equals( seckey_answere ) ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { + // Sec-WebSocket-Origin is only required for browser clients + int v = readVersion( handshakedata ); + if( v == 7 || v == 8 )// g + return basicAccept( handshakedata ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + ByteBuffer mes = framedata.getPayloadData(); + boolean mask = role == Role.CLIENT; // framedata.getTransfereMasked(); + int sizebytes = mes.remaining() <= 125 ? 1 : mes.remaining() <= 65535 ? 2 : 8; + ByteBuffer buf = ByteBuffer.allocate( 1 + ( sizebytes > 1 ? sizebytes + 1 : sizebytes ) + ( mask ? 4 : 0 ) + mes.remaining() ); + byte optcode = fromOpcode( framedata.getOpcode() ); + byte one = (byte) ( framedata.isFin() ? -128 : 0 ); + one |= optcode; + buf.put( one ); + byte[] payloadlengthbytes = toByteArray( mes.remaining(), sizebytes ); + assert ( payloadlengthbytes.length == sizebytes ); + + if( sizebytes == 1 ) { + buf.put( (byte) ( (byte) payloadlengthbytes[ 0 ] | ( mask ? (byte) -128 : 0 ) ) ); + } else if( sizebytes == 2 ) { + buf.put( (byte) ( (byte) 126 | ( mask ? (byte) -128 : 0 ) ) ); + buf.put( payloadlengthbytes ); + } else if( sizebytes == 8 ) { + buf.put( (byte) ( (byte) 127 | ( mask ? (byte) -128 : 0 ) ) ); + buf.put( payloadlengthbytes ); + } else + throw new RuntimeException( "Size representation not supported/specified" ); + + if( mask ) { + ByteBuffer maskkey = ByteBuffer.allocate( 4 ); + maskkey.putInt( reuseableRandom.nextInt() ); + buf.put( maskkey.array() ); + for( int i = 0 ; mes.hasRemaining() ; i++ ) { + buf.put( (byte) ( mes.get() ^ maskkey.get( i % 4 ) ) ); + } + } else + buf.put( mes ); + // translateFrame ( buf.array () , buf.array ().length ); + assert ( buf.remaining() == 0 ) : buf.remaining(); + buf.flip(); + + return buf; + } + + @Override + public List createFrames( ByteBuffer binary, boolean mask ) { + FrameBuilder curframe = new FramedataImpl1(); + try { + curframe.setPayload( binary ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + curframe.setFin( true ); + curframe.setOptcode( Opcode.BINARY ); + curframe.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) curframe ); + } + + @Override + public List createFrames( String text, boolean mask ) { + FrameBuilder curframe = new FramedataImpl1(); + try { + curframe.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + curframe.setFin( true ); + curframe.setOptcode( Opcode.TEXT ); + curframe.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) curframe ); + } + + private byte fromOpcode( Opcode opcode ) { + if( opcode == Opcode.CONTINUOUS ) + return 0; + else if( opcode == Opcode.TEXT ) + return 1; + else if( opcode == Opcode.BINARY ) + return 2; + else if( opcode == Opcode.CLOSING ) + return 8; + else if( opcode == Opcode.PING ) + return 9; + else if( opcode == Opcode.PONG ) + return 10; + throw new RuntimeException( "Don't know how to handle " + opcode.toString() ); + } + + private String generateFinalKey( String in ) { + String seckey = in.trim(); + String acc = seckey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + MessageDigest sh1; + try { + sh1 = MessageDigest.getInstance( "SHA1" ); + } catch ( NoSuchAlgorithmException e ) { + throw new RuntimeException( e ); + } + return Base64.encodeBytes( sh1.digest( acc.getBytes() ) ); + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + request.put( "Upgrade", "websocket" ); + request.put( "Connection", "Upgrade" ); // to respond to a Connection keep alives + request.put( "Sec-WebSocket-Version", "8" ); + + byte[] random = new byte[ 16 ]; + reuseableRandom.nextBytes( random ); + request.put( "Sec-WebSocket-Key", Base64.encodeBytes( random ) ); + + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.put( "Upgrade", "websocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alives + response.setHttpStatusMessage( "Switching Protocols" ); + String seckey = request.getFieldValue( "Sec-WebSocket-Key" ); + if( seckey == null ) + throw new InvalidHandshakeException( "missing Sec-WebSocket-Key" ); + response.put( "Sec-WebSocket-Accept", generateFinalKey( seckey ) ); + return response; + } + + private byte[] toByteArray( long val, int bytecount ) { + byte[] buffer = new byte[ bytecount ]; + int highest = 8 * bytecount - 8; + for( int i = 0 ; i < bytecount ; i++ ) { + buffer[ i ] = (byte) ( val >>> ( highest - 8 * i ) ); + } + return buffer; + } + + private Opcode toOpcode( byte opcode ) throws InvalidFrameException { + switch ( opcode ) { + case 0: + return Opcode.CONTINUOUS; + case 1: + return Opcode.TEXT; + case 2: + return Opcode.BINARY; + // 3-7 are not yet defined + case 8: + return Opcode.CLOSING; + case 9: + return Opcode.PING; + case 10: + return Opcode.PONG; + // 11-15 are not yet defined + default : + throw new InvalidFrameException( "unknow optcode " + (short) opcode ); + } + } + + @Override + public List translateFrame( ByteBuffer buffer ) throws LimitExedeedException , InvalidDataException { + List frames = new LinkedList(); + Framedata cur; + + if( incompleteframe != null ) { + // complete an incomplete frame + while ( true ) { + try { + buffer.mark(); + int available_next_byte_count = buffer.remaining();// The number of bytes received + int expected_next_byte_count = incompleteframe.remaining();// The number of bytes to complete the incomplete frame + + if( expected_next_byte_count > available_next_byte_count ) { + // did not receive enough bytes to complete the frame + incompleteframe.put( buffer.array(), buffer.position(), available_next_byte_count ); + buffer.position( buffer.position() + available_next_byte_count ); + return Collections.emptyList(); + } + incompleteframe.put( buffer.array(), buffer.position(), expected_next_byte_count ); + buffer.position( buffer.position() + expected_next_byte_count ); + + cur = translateSingleFrame( (ByteBuffer) incompleteframe.duplicate().position( 0 ) ); + frames.add( cur ); + incompleteframe = null; + break; // go on with the normal frame receival + } catch ( IncompleteException e ) { + // extending as much as suggested + int oldsize = incompleteframe.limit(); + ByteBuffer extendedframe = ByteBuffer.allocate( checkAlloc( e.getPreferedSize() ) ); + assert ( extendedframe.limit() > incompleteframe.limit() ); + incompleteframe.rewind(); + extendedframe.put( incompleteframe ); + incompleteframe = extendedframe; + + return translateFrame( buffer ); + } + } + } + + while ( buffer.hasRemaining() ) {// Read as much as possible full frames + buffer.mark(); + try { + cur = translateSingleFrame( buffer ); + frames.add( cur ); + } catch ( IncompleteException e ) { + // remember the incomplete data + buffer.reset(); + int pref = e.getPreferedSize(); + incompleteframe = ByteBuffer.allocate( checkAlloc( pref ) ); + incompleteframe.put( buffer ); + break; + } + } + return frames; + } + + public Framedata translateSingleFrame( ByteBuffer buffer ) throws IncompleteException , InvalidDataException { + int maxpacketsize = buffer.remaining(); + int realpacketsize = 2; + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte b1 = buffer.get( /*0*/); + boolean FIN = b1 >> 8 != 0; + byte rsv = (byte) ( ( b1 & ~(byte) 128 ) >> 4 ); + if( rsv != 0 ) + throw new InvalidFrameException( "bad rsv " + rsv ); + byte b2 = buffer.get( /*1*/); + boolean MASK = ( b2 & -128 ) != 0; + int payloadlength = (byte) ( b2 & ~(byte) 128 ); + Opcode optcode = toOpcode( (byte) ( b1 & 15 ) ); + + if( !FIN ) { + if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { + throw new InvalidFrameException( "control frames may no be fragmented" ); + } + } + + if( payloadlength >= 0 && payloadlength <= 125 ) { + } else { + if( optcode == Opcode.PING || optcode == Opcode.PONG || optcode == Opcode.CLOSING ) { + throw new InvalidFrameException( "more than 125 octets" ); + } + if( payloadlength == 126 ) { + realpacketsize += 2; // additional length bytes + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte[] sizebytes = new byte[ 3 ]; + sizebytes[ 1 ] = buffer.get( /*1 + 1*/); + sizebytes[ 2 ] = buffer.get( /*1 + 2*/); + payloadlength = new BigInteger( sizebytes ).intValue(); + } else { + realpacketsize += 8; // additional length bytes + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + byte[] bytes = new byte[ 8 ]; + for( int i = 0 ; i < 8 ; i++ ) { + bytes[ i ] = buffer.get( /*1 + i*/); + } + long length = new BigInteger( bytes ).longValue(); + if( length > Integer.MAX_VALUE ) { + throw new LimitExedeedException( "Payloadsize is to big..." ); + } else { + payloadlength = (int) length; + } + } + } + + // int maskskeystart = foff + realpacketsize; + realpacketsize += ( MASK ? 4 : 0 ); + // int payloadstart = foff + realpacketsize; + realpacketsize += payloadlength; + + if( maxpacketsize < realpacketsize ) + throw new IncompleteException( realpacketsize ); + + ByteBuffer payload = ByteBuffer.allocate( checkAlloc( payloadlength ) ); + if( MASK ) { + byte[] maskskey = new byte[ 4 ]; + buffer.get( maskskey ); + for( int i = 0 ; i < payloadlength ; i++ ) { + payload.put( (byte) ( (byte) buffer.get( /*payloadstart + i*/) ^ (byte) maskskey[ i % 4 ] ) ); + } + } else { + payload.put( buffer.array(), buffer.position(), payload.limit() ); + buffer.position( buffer.position() + payload.limit() ); + } + + FrameBuilder frame; + if( optcode == Opcode.CLOSING ) { + frame = new CloseFrameBuilder(); + } else { + frame = new FramedataImpl1(); + frame.setFin( FIN ); + frame.setOptcode( optcode ); + } + payload.flip(); + frame.setPayload( payload ); + return frame; + } + + @Override + public void reset() { + incompleteframe = null; + } + + @Override + public Draft copyInstance() { + return new Draft_10(); + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.TWOWAY; + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_17.java b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_17.java new file mode 100644 index 00000000..5c4088f7 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_17.java @@ -0,0 +1,28 @@ +package org.java_websocket.drafts; + +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; + +public class Draft_17 extends Draft_10 { + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) throws InvalidHandshakeException { + int v = readVersion( handshakedata ); + if( v == 13 ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + super.postProcessHandshakeRequestAsClient( request ); + request.put( "Sec-WebSocket-Version", "13" );// overwriting the previous + return request; + } + + @Override + public Draft copyInstance() { + return new Draft_17(); + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_75.java b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_75.java new file mode 100644 index 00000000..947a35ec --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_75.java @@ -0,0 +1,206 @@ +package org.java_websocket.drafts; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.exceptions.LimitExedeedException; +import org.java_websocket.exceptions.NotSendableException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.FrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.framing.FramedataImpl1; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; +import org.java_websocket.util.Charsetfunctions; + +public class Draft_75 extends Draft { + + /** + * The byte representing CR, or Carriage Return, or \r + */ + public static final byte CR = (byte) 0x0D; + /** + * The byte representing LF, or Line Feed, or \n + */ + public static final byte LF = (byte) 0x0A; + /** + * The byte representing the beginning of a WebSocket text frame. + */ + public static final byte START_OF_FRAME = (byte) 0x00; + /** + * The byte representing the end of a WebSocket text frame. + */ + public static final byte END_OF_FRAME = (byte) 0xFF; + + /** Is only used to detect protocol violations */ + protected boolean readingState = false; + + protected List readyframes = new LinkedList(); + protected ByteBuffer currentFrame; + + private final Random reuseableRandom = new Random(); + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { + return request.getFieldValue( "WebSocket-Origin" ).equals( response.getFieldValue( "Origin" ) ) && basicAccept( response ) ? HandshakeState.MATCHED : HandshakeState.NOT_MATCHED; + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { + if( handshakedata.hasFieldValue( "Origin" ) && basicAccept( handshakedata ) ) { + return HandshakeState.MATCHED; + } + return HandshakeState.NOT_MATCHED; + } + + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + if( framedata.getOpcode() != Opcode.TEXT ) { + throw new RuntimeException( "only text frames supported" ); + } + + ByteBuffer pay = framedata.getPayloadData(); + ByteBuffer b = ByteBuffer.allocate( pay.remaining() + 2 ); + b.put( START_OF_FRAME ); + pay.mark(); + b.put( pay ); + pay.reset(); + b.put( END_OF_FRAME ); + b.flip(); + return b; + } + + @Override + public List createFrames( ByteBuffer binary, boolean mask ) { + throw new RuntimeException( "not yet implemented" ); + } + + @Override + public List createFrames( String text, boolean mask ) { + FrameBuilder frame = new FramedataImpl1(); + try { + frame.setPayload( ByteBuffer.wrap( Charsetfunctions.utf8Bytes( text ) ) ); + } catch ( InvalidDataException e ) { + throw new NotSendableException( e ); + } + frame.setFin( true ); + frame.setOptcode( Opcode.TEXT ); + frame.setTransferemasked( mask ); + return Collections.singletonList( (Framedata) frame ); + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) throws InvalidHandshakeException { + request.put( "Upgrade", "WebSocket" ); + request.put( "Connection", "Upgrade" ); + if( !request.hasFieldValue( "Origin" ) ) { + request.put( "Origin", "random" + reuseableRandom.nextInt() ); + } + + return request; + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.setHttpStatusMessage( "Web Socket Protocol Handshake" ); + response.put( "Upgrade", "WebSocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive + response.put( "WebSocket-Origin", request.getFieldValue( "Origin" ) ); + String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); + response.put( "WebSocket-Location", location ); + // TODO handle Sec-WebSocket-Protocol and Set-Cookie + return response; + } + + protected List translateRegularFrame( ByteBuffer buffer ) throws InvalidDataException { + + while ( buffer.hasRemaining() ) { + byte newestByte = buffer.get(); + if( newestByte == START_OF_FRAME ) { // Beginning of Frame + if( readingState ) + throw new InvalidFrameException( "unexpected START_OF_FRAME" ); + readingState = true; + } else if( newestByte == END_OF_FRAME ) { // End of Frame + if( !readingState ) + throw new InvalidFrameException( "unexpected END_OF_FRAME" ); + // currentFrame will be null if END_OF_FRAME was send directly after + // START_OF_FRAME, thus we will send 'null' as the sent message. + if( this.currentFrame != null ) { + currentFrame.flip(); + FramedataImpl1 curframe = new FramedataImpl1(); + curframe.setPayload( currentFrame ); + curframe.setFin( true ); + curframe.setOptcode( Opcode.TEXT ); + readyframes.add( curframe ); + this.currentFrame = null; + buffer.mark(); + } + readingState = false; + } else if( readingState ) { // Regular frame data, add to current frame buffer //TODO This code is very expensive and slow + if( currentFrame == null ) { + currentFrame = createBuffer(); + } else if( !currentFrame.hasRemaining() ) { + currentFrame = increaseBuffer( currentFrame ); + } + currentFrame.put( newestByte ); + } else { + return null; + } + } + + // if no error occurred this block will be reached + /*if( readingState ) { + checkAlloc(currentFrame.position()+1); + }*/ + + List frames = readyframes; + readyframes = new LinkedList(); + return frames; + } + + @Override + public List translateFrame( ByteBuffer buffer ) throws InvalidDataException { + List frames = translateRegularFrame( buffer ); + if( frames == null ) { + throw new InvalidDataException( CloseFrame.PROTOCOL_ERROR ); + } + return frames; + } + + @Override + public void reset() { + readingState = false; + this.currentFrame = null; + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.NONE; + } + + public ByteBuffer createBuffer() { + return ByteBuffer.allocate( INITIAL_FAMESIZE ); + } + + public ByteBuffer increaseBuffer( ByteBuffer full ) throws LimitExedeedException , InvalidDataException { + full.flip(); + ByteBuffer newbuffer = ByteBuffer.allocate( checkAlloc( full.capacity() * 2 ) ); + newbuffer.put( full ); + return newbuffer; + } + + @Override + public Draft copyInstance() { + return new Draft_75(); + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_76.java b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_76.java new file mode 100644 index 00000000..26f23531 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/drafts/Draft_76.java @@ -0,0 +1,242 @@ +package org.java_websocket.drafts; + +import java.nio.BufferUnderflowException; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; +import java.util.Random; + +import org.java_websocket.WebSocket.Role; +import org.java_websocket.exceptions.IncompleteHandshakeException; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.exceptions.InvalidHandshakeException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.CloseFrameBuilder; +import org.java_websocket.framing.Framedata; +import org.java_websocket.framing.Framedata.Opcode; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.ClientHandshakeBuilder; +import org.java_websocket.handshake.HandshakeBuilder; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshake; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +public class Draft_76 extends Draft_75 { + private boolean failed = false; + private static final byte[] closehandshake = { -1, 0 }; + + private final Random reuseableRandom = new Random(); + + + public static byte[] createChallenge( String key1, String key2, byte[] key3 ) throws InvalidHandshakeException { + byte[] part1 = getPart( key1 ); + byte[] part2 = getPart( key2 ); + byte[] challenge = new byte[ 16 ]; + challenge[ 0 ] = part1[ 0 ]; + challenge[ 1 ] = part1[ 1 ]; + challenge[ 2 ] = part1[ 2 ]; + challenge[ 3 ] = part1[ 3 ]; + challenge[ 4 ] = part2[ 0 ]; + challenge[ 5 ] = part2[ 1 ]; + challenge[ 6 ] = part2[ 2 ]; + challenge[ 7 ] = part2[ 3 ]; + challenge[ 8 ] = key3[ 0 ]; + challenge[ 9 ] = key3[ 1 ]; + challenge[ 10 ] = key3[ 2 ]; + challenge[ 11 ] = key3[ 3 ]; + challenge[ 12 ] = key3[ 4 ]; + challenge[ 13 ] = key3[ 5 ]; + challenge[ 14 ] = key3[ 6 ]; + challenge[ 15 ] = key3[ 7 ]; + MessageDigest md5; + try { + md5 = MessageDigest.getInstance( "MD5" ); + } catch ( NoSuchAlgorithmException e ) { + throw new RuntimeException( e ); + } + return md5.digest( challenge ); + } + + private static String generateKey() { + Random r = new Random(); + long maxNumber = 4294967295L; + long spaces = r.nextInt( 12 ) + 1; + int max = new Long( maxNumber / spaces ).intValue(); + max = Math.abs( max ); + int number = r.nextInt( max ) + 1; + long product = number * spaces; + String key = Long.toString( product ); + // always insert atleast one random character + int numChars = r.nextInt( 12 ) + 1; + for( int i = 0 ; i < numChars ; i++ ) { + int position = r.nextInt( key.length() ); + position = Math.abs( position ); + char randChar = (char) ( r.nextInt( 95 ) + 33 ); + // exclude numbers here + if( randChar >= 48 && randChar <= 57 ) { + randChar -= 15; + } + key = new StringBuilder( key ).insert( position, randChar ).toString(); + } + for( int i = 0 ; i < spaces ; i++ ) { + int position = r.nextInt( key.length() - 1 ) + 1; + position = Math.abs( position ); + key = new StringBuilder( key ).insert( position, "\u0020" ).toString(); + } + return key; + } + + private static byte[] getPart( String key ) throws InvalidHandshakeException { + try { + long keyNumber = Long.parseLong( key.replaceAll( "[^0-9]", "" ) ); + long keySpace = key.split( "\u0020" ).length - 1; + if( keySpace == 0 ) { + throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key2/)" ); + } + long part = new Long( keyNumber / keySpace ); + return new byte[]{ (byte) ( part >> 24 ), (byte) ( ( part << 8 ) >> 24 ), (byte) ( ( part << 16 ) >> 24 ), (byte) ( ( part << 24 ) >> 24 ) }; + } catch ( NumberFormatException e ) { + throw new InvalidHandshakeException( "invalid Sec-WebSocket-Key (/key1/ or /key2/)" ); + } + } + + @Override + public HandshakeState acceptHandshakeAsClient( ClientHandshake request, ServerHandshake response ) { + if( failed ) { + return HandshakeState.NOT_MATCHED; + } + + try { + if( !response.getFieldValue( "Sec-WebSocket-Origin" ).equals( request.getFieldValue( "Origin" ) ) || !basicAccept( response ) ) { + return HandshakeState.NOT_MATCHED; + } + byte[] content = response.getContent(); + if( content == null || content.length == 0 ) { + throw new IncompleteHandshakeException(); + } + if( Arrays.equals( content, createChallenge( request.getFieldValue( "Sec-WebSocket-Key1" ), request.getFieldValue( "Sec-WebSocket-Key2" ), request.getContent() ) ) ) { + return HandshakeState.MATCHED; + } else { + return HandshakeState.NOT_MATCHED; + } + } catch ( InvalidHandshakeException e ) { + throw new RuntimeException( "bad handshakerequest", e ); + } + } + + @Override + public HandshakeState acceptHandshakeAsServer( ClientHandshake handshakedata ) { + if( handshakedata.getFieldValue( "Upgrade" ).equals( "WebSocket" ) && handshakedata.getFieldValue( "Connection" ).contains( "Upgrade" ) && handshakedata.getFieldValue( "Sec-WebSocket-Key1" ).length() > 0 && !handshakedata.getFieldValue( "Sec-WebSocket-Key2" ).isEmpty() && handshakedata.hasFieldValue( "Origin" ) ) + return HandshakeState.MATCHED; + return HandshakeState.NOT_MATCHED; + } + + @Override + public ClientHandshakeBuilder postProcessHandshakeRequestAsClient( ClientHandshakeBuilder request ) { + request.put( "Upgrade", "WebSocket" ); + request.put( "Connection", "Upgrade" ); + request.put( "Sec-WebSocket-Key1", generateKey() ); + request.put( "Sec-WebSocket-Key2", generateKey() ); + + if( !request.hasFieldValue( "Origin" ) ) { + request.put( "Origin", "random" + reuseableRandom.nextInt() ); + } + + byte[] key3 = new byte[ 8 ]; + reuseableRandom.nextBytes( key3 ); + request.setContent( key3 ); + return request; + + } + + @Override + public HandshakeBuilder postProcessHandshakeResponseAsServer( ClientHandshake request, ServerHandshakeBuilder response ) throws InvalidHandshakeException { + response.setHttpStatusMessage( "WebSocket Protocol Handshake" ); + response.put( "Upgrade", "WebSocket" ); + response.put( "Connection", request.getFieldValue( "Connection" ) ); // to respond to a Connection keep alive + response.put( "Sec-WebSocket-Origin", request.getFieldValue( "Origin" ) ); + String location = "ws://" + request.getFieldValue( "Host" ) + request.getResourceDescriptor(); + response.put( "Sec-WebSocket-Location", location ); + String key1 = request.getFieldValue( "Sec-WebSocket-Key1" ); + String key2 = request.getFieldValue( "Sec-WebSocket-Key2" ); + byte[] key3 = request.getContent(); + if( key1 == null || key2 == null || key3 == null || key3.length != 8 ) { + throw new InvalidHandshakeException( "Bad keys" ); + } + response.setContent( createChallenge( key1, key2, key3 ) ); + return response; + } + + @Override + public Handshakedata translateHandshake( ByteBuffer buf ) throws InvalidHandshakeException { + + HandshakeBuilder bui = translateHandshakeHttp( buf, role ); + // the first drafts are lacking a protocol number which makes them difficult to distinguish. Sec-WebSocket-Key1 is typical for draft76 + if( ( bui.hasFieldValue( "Sec-WebSocket-Key1" ) || role == Role.CLIENT ) && !bui.hasFieldValue( "Sec-WebSocket-Version" ) ) { + byte[] key3 = new byte[ role == Role.SERVER ? 8 : 16 ]; + try { + buf.get( key3 ); + } catch ( BufferUnderflowException e ) { + throw new IncompleteHandshakeException( buf.capacity() + 16 ); + } + bui.setContent( key3 ); + + } + return bui; + } + + @Override + public List translateFrame( ByteBuffer buffer ) throws InvalidDataException { + buffer.mark(); + List frames = super.translateRegularFrame( buffer ); + if( frames == null ) { + buffer.reset(); + frames = readyframes; + readingState = true; + if( currentFrame == null ) + currentFrame = ByteBuffer.allocate( 2 ); + else { + throw new InvalidFrameException(); + } + if( buffer.remaining() > currentFrame.remaining() ) { + throw new InvalidFrameException(); + } else { + currentFrame.put( buffer ); + } + if( !currentFrame.hasRemaining() ) { + if( Arrays.equals( currentFrame.array(), closehandshake ) ) { + frames.add( new CloseFrameBuilder( CloseFrame.NORMAL ) ); + return frames; + } + else{ + throw new InvalidFrameException(); + } + } else { + readyframes = new LinkedList(); + return frames; + } + } else { + return frames; + } + } + @Override + public ByteBuffer createBinaryFrame( Framedata framedata ) { + if( framedata.getOpcode() == Opcode.CLOSING ) + return ByteBuffer.wrap( closehandshake ); + return super.createBinaryFrame( framedata ); + } + + @Override + public CloseHandshakeType getCloseHandshakeType() { + return CloseHandshakeType.ONEWAY; + } + + @Override + public Draft copyInstance() { + return new Draft_76(); + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/IncompleteHandshakeException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/IncompleteHandshakeException.java new file mode 100644 index 00000000..2fdb5eae --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/IncompleteHandshakeException.java @@ -0,0 +1,20 @@ +package org.java_websocket.exceptions; + +public class IncompleteHandshakeException extends RuntimeException { + + private static final long serialVersionUID = 7906596804233893092L; + private int newsize; + + public IncompleteHandshakeException( int newsize ) { + this.newsize = newsize; + } + + public IncompleteHandshakeException() { + this.newsize = 0; + } + + public int getPreferedSize() { + return newsize; + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidDataException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidDataException.java new file mode 100644 index 00000000..2ab9d328 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidDataException.java @@ -0,0 +1,34 @@ +package org.java_websocket.exceptions; + +public class InvalidDataException extends Exception { + /** + * Serializable + */ + private static final long serialVersionUID = 3731842424390998726L; + + private int closecode; + + public InvalidDataException( int closecode ) { + this.closecode = closecode; + } + + public InvalidDataException( int closecode , String s ) { + super( s ); + this.closecode = closecode; + } + + public InvalidDataException( int closecode , Throwable t ) { + super( t ); + this.closecode = closecode; + } + + public InvalidDataException( int closecode , String s , Throwable t ) { + super( s, t ); + this.closecode = closecode; + } + + public int getCloseCode() { + return closecode; + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidFrameException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidFrameException.java new file mode 100644 index 00000000..c7fe4101 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidFrameException.java @@ -0,0 +1,27 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class InvalidFrameException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = -9016496369828887591L; + + public InvalidFrameException() { + super( CloseFrame.PROTOCOL_ERROR ); + } + + public InvalidFrameException( String arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidFrameException( Throwable arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidFrameException( String arg0 , Throwable arg1 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidHandshakeException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidHandshakeException.java new file mode 100644 index 00000000..4d0baec8 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/InvalidHandshakeException.java @@ -0,0 +1,28 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class InvalidHandshakeException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = -1426533877490484964L; + + public InvalidHandshakeException() { + super( CloseFrame.PROTOCOL_ERROR ); + } + + public InvalidHandshakeException( String arg0 , Throwable arg1 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0, arg1 ); + } + + public InvalidHandshakeException( String arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + + public InvalidHandshakeException( Throwable arg0 ) { + super( CloseFrame.PROTOCOL_ERROR, arg0 ); + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/LimitExedeedException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/LimitExedeedException.java new file mode 100644 index 00000000..1ac7f8c5 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/LimitExedeedException.java @@ -0,0 +1,20 @@ +package org.java_websocket.exceptions; + +import org.java_websocket.framing.CloseFrame; + +public class LimitExedeedException extends InvalidDataException { + + /** + * Serializable + */ + private static final long serialVersionUID = 6908339749836826785L; + + public LimitExedeedException() { + super( CloseFrame.TOOBIG ); + } + + public LimitExedeedException( String s ) { + super( CloseFrame.TOOBIG, s ); + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/NotSendableException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/NotSendableException.java new file mode 100644 index 00000000..2b2e2293 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/NotSendableException.java @@ -0,0 +1,25 @@ +package org.java_websocket.exceptions; + +public class NotSendableException extends RuntimeException { + + /** + * Serializable + */ + private static final long serialVersionUID = -6468967874576651628L; + + public NotSendableException() { + } + + public NotSendableException( String message ) { + super( message ); + } + + public NotSendableException( Throwable cause ) { + super( cause ); + } + + public NotSendableException( String message , Throwable cause ) { + super( message, cause ); + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/exceptions/WebsocketNotConnectedException.java b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/WebsocketNotConnectedException.java new file mode 100644 index 00000000..45c5f4df --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/exceptions/WebsocketNotConnectedException.java @@ -0,0 +1,5 @@ +package org.java_websocket.exceptions; + +public class WebsocketNotConnectedException extends RuntimeException { + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrame.java b/src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrame.java new file mode 100644 index 00000000..f253b8dd --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrame.java @@ -0,0 +1,98 @@ +package org.java_websocket.framing; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; + +public interface CloseFrame extends Framedata { + /** + * indicates a normal closure, meaning whatever purpose the + * connection was established for has been fulfilled. + */ + public static final int NORMAL = 1000; + /** + * 1001 indicates that an endpoint is "going away", such as a server + * going down, or a browser having navigated away from a page. + */ + public static final int GOING_AWAY = 1001; + /** + * 1002 indicates that an endpoint is terminating the connection due + * to a protocol error. + */ + public static final int PROTOCOL_ERROR = 1002; + /** + * 1003 indicates that an endpoint is terminating the connection + * because it has received a type of data it cannot accept (e.g. an + * endpoint that understands only text data MAY send this if it + * receives a binary message). + */ + public static final int REFUSE = 1003; + /*1004: Reserved. The specific meaning might be defined in the future.*/ + /** + * 1005 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that no status + * code was actually present. + */ + public static final int NOCODE = 1005; + /** + * 1006 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that the + * connection was closed abnormally, e.g. without sending or + * receiving a Close control frame. + */ + public static final int ABNORMAL_CLOSE = 1006; + /** + * 1007 indicates that an endpoint is terminating the connection + * because it has received data within a message that was not + * consistent with the type of the message (e.g., non-UTF-8 [RFC3629] + * data within a text message). + */ + public static final int NO_UTF8 = 1007; + /** + * 1008 indicates that an endpoint is terminating the connection + * because it has received a message that violates its policy. This + * is a generic status code that can be returned when there is no + * other more suitable status code (e.g. 1003 or 1009), or if there + * is a need to hide specific details about the policy. + */ + public static final int POLICY_VALIDATION = 1008; + /** + * 1009 indicates that an endpoint is terminating the connection + * because it has received a message which is too big for it to + * process. + */ + public static final int TOOBIG = 1009; + /** + * 1010 indicates that an endpoint (client) is terminating the + * connection because it has expected the server to negotiate one or + * more extension, but the server didn't return them in the response + * message of the WebSocket handshake. The list of extensions which + * are needed SHOULD appear in the /reason/ part of the Close frame. + * Note that this status code is not used by the server, because it + * can fail the WebSocket handshake instead. + */ + public static final int EXTENSION = 1010; + /** + * 1011 indicates that a server is terminating the connection because + * it encountered an unexpected condition that prevented it from + * fulfilling the request. + **/ + public static final int UNEXPECTED_CONDITION = 1011; + /** + * 1015 is a reserved value and MUST NOT be set as a status code in a + * Close control frame by an endpoint. It is designated for use in + * applications expecting a status code to indicate that the + * connection was closed due to a failure to perform a TLS handshake + * (e.g., the server certificate can't be verified). + **/ + public static final int TLS_ERROR = 1015; + + /** The connection had never been established */ + public static final int NEVER_CONNECTED = -1; + public static final int BUGGYCLOSE = -2; + public static final int FLASHPOLICY = -3; + + public int getCloseCode() throws InvalidFrameException; + public String getMessage() throws InvalidDataException; +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrameBuilder.java b/src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrameBuilder.java new file mode 100644 index 00000000..fee1b540 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/framing/CloseFrameBuilder.java @@ -0,0 +1,123 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.Charsetfunctions; + +public class CloseFrameBuilder extends FramedataImpl1 implements CloseFrame { + + static final ByteBuffer emptybytebuffer = ByteBuffer.allocate( 0 ); + + private int code; + private String reason; + + public CloseFrameBuilder() { + super( Opcode.CLOSING ); + setFin( true ); + } + + public CloseFrameBuilder( int code ) throws InvalidDataException { + super( Opcode.CLOSING ); + setFin( true ); + setCodeAndMessage( code, "" ); + } + + public CloseFrameBuilder( int code , String m ) throws InvalidDataException { + super( Opcode.CLOSING ); + setFin( true ); + setCodeAndMessage( code, m ); + } + + private void setCodeAndMessage( int code, String m ) throws InvalidDataException { + if( m == null ) { + m = ""; + } + // CloseFrame.TLS_ERROR is not allowed to be transfered over the wire + if( code == CloseFrame.TLS_ERROR ) { + code = CloseFrame.NOCODE; + m = ""; + } + if( code == CloseFrame.NOCODE ) { + if( 0 < m.length() ) { + throw new InvalidDataException( PROTOCOL_ERROR, "A close frame must have a closecode if it has a reason" ); + } + return;// empty payload + } + + byte[] by = Charsetfunctions.utf8Bytes( m ); + ByteBuffer buf = ByteBuffer.allocate( 4 ); + buf.putInt( code ); + buf.position( 2 ); + ByteBuffer pay = ByteBuffer.allocate( 2 + by.length ); + pay.put( buf ); + pay.put( by ); + pay.rewind(); + setPayload( pay ); + } + + private void initCloseCode() throws InvalidFrameException { + code = CloseFrame.NOCODE; + ByteBuffer payload = super.getPayloadData(); + payload.mark(); + if( payload.remaining() >= 2 ) { + ByteBuffer bb = ByteBuffer.allocate( 4 ); + bb.position( 2 ); + bb.putShort( payload.getShort() ); + bb.position( 0 ); + code = bb.getInt(); + + if( code == CloseFrame.ABNORMAL_CLOSE || code == CloseFrame.TLS_ERROR || code == CloseFrame.NOCODE || code > 4999 || code < 1000 || code == 1004 ) { + throw new InvalidFrameException( "closecode must not be sent over the wire: " + code ); + } + } + payload.reset(); + } + + @Override + public int getCloseCode() { + return code; + } + + private void initMessage() throws InvalidDataException { + if( code == CloseFrame.NOCODE ) { + reason = Charsetfunctions.stringUtf8( super.getPayloadData() ); + } else { + ByteBuffer b = super.getPayloadData(); + int mark = b.position();// because stringUtf8 also creates a mark + try { + b.position( b.position() + 2 ); + reason = Charsetfunctions.stringUtf8( b ); + } catch ( IllegalArgumentException e ) { + throw new InvalidFrameException( e ); + } finally { + b.position( mark ); + } + } + } + + @Override + public String getMessage() { + return reason; + } + + @Override + public String toString() { + return super.toString() + "code: " + code; + } + + @Override + public void setPayload( ByteBuffer payload ) throws InvalidDataException { + super.setPayload( payload ); + initCloseCode(); + initMessage(); + } + @Override + public ByteBuffer getPayloadData() { + if( code == NOCODE ) + return emptybytebuffer; + return super.getPayloadData(); + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/framing/FrameBuilder.java b/src/third_party/Java-WebSocket/org/java_websocket/framing/FrameBuilder.java new file mode 100644 index 00000000..25a20de4 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/framing/FrameBuilder.java @@ -0,0 +1,17 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidDataException; + +public interface FrameBuilder extends Framedata { + + public abstract void setFin( boolean fin ); + + public abstract void setOptcode( Opcode optcode ); + + public abstract void setPayload( ByteBuffer payload ) throws InvalidDataException; + + public abstract void setTransferemasked( boolean transferemasked ); + +} \ No newline at end of file diff --git a/src/third_party/Java-WebSocket/org/java_websocket/framing/Framedata.java b/src/third_party/Java-WebSocket/org/java_websocket/framing/Framedata.java new file mode 100644 index 00000000..3dfa8c08 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/framing/Framedata.java @@ -0,0 +1,17 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; + +import org.java_websocket.exceptions.InvalidFrameException; + +public interface Framedata { + public enum Opcode { + CONTINUOUS, TEXT, BINARY, PING, PONG, CLOSING + // more to come + } + public boolean isFin(); + public boolean getTransfereMasked(); + public Opcode getOpcode(); + public ByteBuffer getPayloadData();// TODO the separation of the application data and the extension data is yet to be done + public abstract void append( Framedata nextframe ) throws InvalidFrameException; +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/framing/FramedataImpl1.java b/src/third_party/Java-WebSocket/org/java_websocket/framing/FramedataImpl1.java new file mode 100644 index 00000000..5fba075b --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/framing/FramedataImpl1.java @@ -0,0 +1,110 @@ +package org.java_websocket.framing; + +import java.nio.ByteBuffer; +import java.util.Arrays; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.exceptions.InvalidFrameException; +import org.java_websocket.util.Charsetfunctions; + +public class FramedataImpl1 implements FrameBuilder { + protected static byte[] emptyarray = {}; + protected boolean fin; + protected Opcode optcode; + private ByteBuffer unmaskedpayload; + protected boolean transferemasked; + + public FramedataImpl1() { + } + + public FramedataImpl1( Opcode op ) { + this.optcode = op; + unmaskedpayload = ByteBuffer.wrap( emptyarray ); + } + + /** + * Helper constructor which helps to create "echo" frames. + * The new object will use the same underlying payload data. + **/ + public FramedataImpl1( Framedata f ) { + fin = f.isFin(); + optcode = f.getOpcode(); + unmaskedpayload = f.getPayloadData(); + transferemasked = f.getTransfereMasked(); + } + + @Override + public boolean isFin() { + return fin; + } + + @Override + public Opcode getOpcode() { + return optcode; + } + + @Override + public boolean getTransfereMasked() { + return transferemasked; + } + + @Override + public ByteBuffer getPayloadData() { + return unmaskedpayload; + } + + @Override + public void setFin( boolean fin ) { + this.fin = fin; + } + + @Override + public void setOptcode( Opcode optcode ) { + this.optcode = optcode; + } + + @Override + public void setPayload( ByteBuffer payload ) throws InvalidDataException { + unmaskedpayload = payload; + } + + @Override + public void setTransferemasked( boolean transferemasked ) { + this.transferemasked = transferemasked; + } + + @Override + public void append( Framedata nextframe ) throws InvalidFrameException { + ByteBuffer b = nextframe.getPayloadData(); + if( unmaskedpayload == null ) { + unmaskedpayload = ByteBuffer.allocate( b.remaining() ); + b.mark(); + unmaskedpayload.put( b ); + b.reset(); + } else { + b.mark(); + unmaskedpayload.position( unmaskedpayload.limit() ); + unmaskedpayload.limit( unmaskedpayload.capacity() ); + + if( b.remaining() > unmaskedpayload.remaining() ) { + ByteBuffer tmp = ByteBuffer.allocate( b.remaining() + unmaskedpayload.capacity() ); + unmaskedpayload.flip(); + tmp.put( unmaskedpayload ); + tmp.put( b ); + unmaskedpayload = tmp; + + } else { + unmaskedpayload.put( b ); + } + unmaskedpayload.rewind(); + b.reset(); + } + fin = nextframe.isFin(); + } + + @Override + public String toString() { + return "Framedata{ optcode:" + getOpcode() + ", fin:" + isFin() + ", payloadlength:[pos:" + unmaskedpayload.position() + ", len:" + unmaskedpayload.remaining() + "], payload:" + Arrays.toString( Charsetfunctions.utf8Bytes( new String( unmaskedpayload.array() ) ) ) + "}"; + } + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshake.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshake.java new file mode 100644 index 00000000..918d2218 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshake.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ClientHandshake extends Handshakedata { + /**returns the HTTP Request-URI as defined by http://tools.ietf.org/html/rfc2616#section-5.1.2*/ + public String getResourceDescriptor(); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshakeBuilder.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshakeBuilder.java new file mode 100644 index 00000000..88ac4f27 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ClientHandshakeBuilder.java @@ -0,0 +1,5 @@ +package org.java_websocket.handshake; + +public interface ClientHandshakeBuilder extends HandshakeBuilder, ClientHandshake { + public void setResourceDescriptor( String resourceDescriptor ); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeBuilder.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeBuilder.java new file mode 100644 index 00000000..8a6236ca --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeBuilder.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface HandshakeBuilder extends Handshakedata { + public abstract void setContent( byte[] content ); + public abstract void put( String name, String value ); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Client.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Client.java new file mode 100644 index 00000000..15715e37 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Client.java @@ -0,0 +1,18 @@ +package org.java_websocket.handshake; + +public class HandshakeImpl1Client extends HandshakedataImpl1 implements ClientHandshakeBuilder { + private String resourceDescriptor = "*"; + + public HandshakeImpl1Client() { + } + + public void setResourceDescriptor( String resourceDescriptor ) throws IllegalArgumentException { + if(resourceDescriptor==null) + throw new IllegalArgumentException( "http resource descriptor must not be null" ); + this.resourceDescriptor = resourceDescriptor; + } + + public String getResourceDescriptor() { + return resourceDescriptor; + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Server.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Server.java new file mode 100644 index 00000000..7063b892 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakeImpl1Server.java @@ -0,0 +1,29 @@ +package org.java_websocket.handshake; + +public class HandshakeImpl1Server extends HandshakedataImpl1 implements ServerHandshakeBuilder { + private short httpstatus; + private String httpstatusmessage; + + public HandshakeImpl1Server() { + } + + @Override + public String getHttpStatusMessage() { + return httpstatusmessage; + } + + @Override + public short getHttpStatus() { + return httpstatus; + } + + public void setHttpStatusMessage( String message ) { + this.httpstatusmessage = message; + } + + public void setHttpStatus( short status ) { + httpstatus = status; + } + + +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/Handshakedata.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/Handshakedata.java new file mode 100644 index 00000000..577d6ce1 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/Handshakedata.java @@ -0,0 +1,10 @@ +package org.java_websocket.handshake; + +import java.util.Iterator; + +public interface Handshakedata { + public Iterator iterateHttpFields(); + public String getFieldValue( String name ); + public boolean hasFieldValue( String name ); + public byte[] getContent(); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakedataImpl1.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakedataImpl1.java new file mode 100644 index 00000000..d4d9555c --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/HandshakedataImpl1.java @@ -0,0 +1,60 @@ +package org.java_websocket.handshake; + +import java.util.Collections; +import java.util.Iterator; +import java.util.TreeMap; + +public class HandshakedataImpl1 implements HandshakeBuilder { + private byte[] content; + private TreeMap map; + + public HandshakedataImpl1() { + map = new TreeMap( String.CASE_INSENSITIVE_ORDER ); + } + + /*public HandshakedataImpl1( Handshakedata h ) { + httpstatusmessage = h.getHttpStatusMessage(); + resourcedescriptor = h.getResourceDescriptor(); + content = h.getContent(); + map = new LinkedHashMap(); + Iterator it = h.iterateHttpFields(); + while ( it.hasNext() ) { + String key = (String) it.next(); + map.put( key, h.getFieldValue( key ) ); + } + }*/ + + @Override + public Iterator iterateHttpFields() { + return Collections.unmodifiableSet( map.keySet() ).iterator();// Safety first + } + + @Override + public String getFieldValue( String name ) { + String s = map.get( name ); + if ( s == null ) { + return ""; + } + return s; + } + + @Override + public byte[] getContent() { + return content; + } + + @Override + public void setContent( byte[] content ) { + this.content = content; + } + + @Override + public void put( String name, String value ) { + map.put( name, value ); + } + + @Override + public boolean hasFieldValue( String name ) { + return map.containsKey( name ); + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshake.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshake.java new file mode 100644 index 00000000..880e9b2d --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshake.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ServerHandshake extends Handshakedata { + public short getHttpStatus(); + public String getHttpStatusMessage(); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshakeBuilder.java b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshakeBuilder.java new file mode 100644 index 00000000..d518dfb1 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/handshake/ServerHandshakeBuilder.java @@ -0,0 +1,6 @@ +package org.java_websocket.handshake; + +public interface ServerHandshakeBuilder extends HandshakeBuilder, ServerHandshake { + public void setHttpStatus( short status ); + public void setHttpStatusMessage( String message ); +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java b/src/third_party/Java-WebSocket/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java new file mode 100644 index 00000000..b871260f --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/server/DefaultSSLWebSocketServerFactory.java @@ -0,0 +1,51 @@ +package org.java_websocket.server; +import java.io.IOException; +import java.net.Socket; +import java.nio.channels.ByteChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLEngine; + +import org.java_websocket.SSLSocketChannel2; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; + + +public class DefaultSSLWebSocketServerFactory implements WebSocketServer.WebSocketServerFactory { + protected SSLContext sslcontext; + protected ExecutorService exec; + + public DefaultSSLWebSocketServerFactory( SSLContext sslContext ) { + this( sslContext, Executors.newSingleThreadScheduledExecutor() ); + } + + public DefaultSSLWebSocketServerFactory( SSLContext sslContext , ExecutorService exec ) { + if( sslContext == null || exec == null ) + throw new IllegalArgumentException(); + this.sslcontext = sslContext; + this.exec = exec; + } + + @Override + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException { + SSLEngine e = sslcontext.createSSLEngine(); + e.setUseClientMode( false ); + return new SSLSocketChannel2( channel, e, exec, key ); + } + + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket c ) { + return new WebSocketImpl( a, d ); + } + + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { + return new WebSocketImpl( a, d ); + } +} \ No newline at end of file diff --git a/src/third_party/Java-WebSocket/org/java_websocket/server/DefaultWebSocketServerFactory.java b/src/third_party/Java-WebSocket/org/java_websocket/server/DefaultWebSocketServerFactory.java new file mode 100644 index 00000000..3b89cdc2 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/server/DefaultWebSocketServerFactory.java @@ -0,0 +1,26 @@ +package org.java_websocket.server; + +import java.net.Socket; +import java.nio.channels.SelectionKey; +import java.nio.channels.SocketChannel; +import java.util.List; + +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.drafts.Draft; +import org.java_websocket.server.WebSocketServer.WebSocketServerFactory; + +public class DefaultWebSocketServerFactory implements WebSocketServerFactory { + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ) { + return new WebSocketImpl( a, d ); + } + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, List d, Socket s ) { + return new WebSocketImpl( a, d ); + } + @Override + public SocketChannel wrapChannel( SocketChannel channel, SelectionKey key ) { + return (SocketChannel) channel; + } +} \ No newline at end of file diff --git a/src/third_party/Java-WebSocket/org/java_websocket/server/WebSocketServer.java b/src/third_party/Java-WebSocket/org/java_websocket/server/WebSocketServer.java new file mode 100644 index 00000000..a45f7e13 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/server/WebSocketServer.java @@ -0,0 +1,736 @@ +package org.java_websocket.server; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.UnknownHostException; +import java.nio.ByteBuffer; +import java.nio.channels.ByteChannel; +import java.nio.channels.CancelledKeyException; +import java.nio.channels.ClosedByInterruptException; +import java.nio.channels.SelectableChannel; +import java.nio.channels.SelectionKey; +import java.nio.channels.Selector; +import java.nio.channels.ServerSocketChannel; +import java.nio.channels.SocketChannel; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.java_websocket.SocketChannelIOHelper; +import org.java_websocket.WebSocket; +import org.java_websocket.WebSocketAdapter; +import org.java_websocket.WebSocketFactory; +import org.java_websocket.WebSocketImpl; +import org.java_websocket.WrappedByteChannel; +import org.java_websocket.drafts.Draft; +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; +import org.java_websocket.framing.Framedata; +import org.java_websocket.handshake.ClientHandshake; +import org.java_websocket.handshake.Handshakedata; +import org.java_websocket.handshake.ServerHandshakeBuilder; + +/** + * WebSocketServer is an abstract class that only takes care of the + * HTTP handshake portion of WebSockets. It's up to a subclass to add + * functionality/purpose to the server. + * + */ +public abstract class WebSocketServer extends WebSocketAdapter implements Runnable { + + public static int DECODERS = Runtime.getRuntime().availableProcessors(); + + /** + * Holds the list of active WebSocket connections. "Active" means WebSocket + * handshake is complete and socket can be written to, or read from. + */ + private final Collection connections; + /** + * The port number that this WebSocket server should listen on. Default is + * WebSocket.DEFAULT_PORT. + */ + private final InetSocketAddress address; + /** + * The socket channel for this WebSocket server. + */ + private ServerSocketChannel server; + /** + * The 'Selector' used to get event keys from the underlying socket. + */ + private Selector selector; + /** + * The Draft of the WebSocket protocol the Server is adhering to. + */ + private List drafts; + + private Thread selectorthread; + + private volatile AtomicBoolean isclosed = new AtomicBoolean( false ); + + private List decoders; + + private List iqueue; + private BlockingQueue buffers; + private int queueinvokes = 0; + private AtomicInteger queuesize = new AtomicInteger( 0 ); + + private WebSocketServerFactory wsf = new DefaultWebSocketServerFactory(); + + /** + * Creates a WebSocketServer that will attempt to + * listen on port WebSocket.DEFAULT_PORT. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer() throws UnknownHostException { + this( new InetSocketAddress( WebSocket.DEFAULT_PORT ), DECODERS, null ); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given address. + * + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address ) { + this( address, DECODERS, null ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , int decoders ) { + this( address, decoders, null ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , List drafts ) { + this( address, DECODERS, drafts ); + } + + /** + * @see #WebSocketServer(InetSocketAddress, int, List, Collection) more details here + */ + public WebSocketServer( InetSocketAddress address , int decodercount , List drafts ) { + this( address, decodercount, drafts, new HashSet() ); + } + + /** + * Creates a WebSocketServer that will attempt to bind/listen on the given address, + * and comply with Draft version draft. + * + * @param address + * The address (host:port) this server should listen on. + * @param decodercount + * The number of {@link WebSocketWorker}s that will be used to process the incoming network data. By default this will be Runtime.getRuntime().availableProcessors() + * @param drafts + * The versions of the WebSocket protocol that this server + * instance should comply to. Clients that use an other protocol version will be rejected. + * + * @param connectionscontainer + * Allows to specify a collection that will be used to store the websockets in.
+ * If you plan to often iterate through the currently connected websockets you may want to use a collection that does not require synchronization like a {@link CopyOnWriteArraySet}. In that case make sure that you overload {@link #removeConnection(WebSocket)} and {@link #addConnection(WebSocket)}.
+ * By default a {@link HashSet} will be used. + * + * @see #removeConnection(WebSocket) for more control over syncronized operation + * @see more about drafts + */ + public WebSocketServer( InetSocketAddress address , int decodercount , List drafts , Collection connectionscontainer ) { + if( address == null || decodercount < 1 || connectionscontainer == null ) { + throw new IllegalArgumentException( "address and connectionscontainer must not be null and you need at least 1 decoder" ); + } + + if( drafts == null ) + this.drafts = Collections.emptyList(); + else + this.drafts = drafts; + + this.address = address; + this.connections = connectionscontainer; + + iqueue = new LinkedList(); + + decoders = new ArrayList( decodercount ); + buffers = new LinkedBlockingQueue(); + for( int i = 0 ; i < decodercount ; i++ ) { + WebSocketWorker ex = new WebSocketWorker(); + decoders.add( ex ); + ex.start(); + } + } + + /** + * Starts the server selectorthread that binds to the currently set port number and + * listeners for WebSocket connection requests. Creates a fixed thread pool with the size {@link WebSocketServer#DECODERS}
+ * May only be called once. + * + * Alternatively you can call {@link WebSocketServer#run()} directly. + * + * @throws IllegalStateException + */ + public void start() { + if( selectorthread != null ) + throw new IllegalStateException( getClass().getName() + " can only be started once." ); + new Thread( this ).start();; + } + + /** + * Closes all connected clients sockets, then closes the underlying + * ServerSocketChannel, effectively killing the server socket selectorthread, + * freeing the port the server was bound to and stops all internal workerthreads. + * + * If this method is called before the server is started it will never start. + * + * @param timeout + * Specifies how many milliseconds the overall close handshaking may take altogether before the connections are closed without proper close handshaking.
+ * + * @throws IOException + * When {@link ServerSocketChannel}.close throws an IOException + * @throws InterruptedException + */ + public void stop( int timeout ) throws InterruptedException { + if( !isclosed.compareAndSet( false, true ) ) { // this also makes sure that no further connections will be added to this.connections + return; + } + + List socketsToClose = null; + + // copy the connections in a list (prevent callback deadlocks) + synchronized ( connections ) { + socketsToClose = new ArrayList( connections ); + } + + for( WebSocket ws : socketsToClose ) { + ws.close( CloseFrame.GOING_AWAY ); + } + + synchronized ( this ) { + if( selectorthread != null ) { + if( Thread.currentThread() != selectorthread ) { + + } + if( selectorthread != Thread.currentThread() ) { + if( socketsToClose.size() > 0 ) + selectorthread.join( timeout );// isclosed will tell the selectorthread to go down after the last connection was closed + selectorthread.interrupt();// in case the selectorthread did not terminate in time we send the interrupt + selectorthread.join(); + } + } + } + } + public void stop() throws IOException , InterruptedException { + stop( 0 ); + } + + /** + * Returns a WebSocket[] of currently connected clients. + * Its iterators will be failfast and its not judicious + * to modify it. + * + * @return The currently connected clients. + */ + public Collection connections() { + return this.connections; + } + + public InetSocketAddress getAddress() { + return this.address; + } + + /** + * Gets the port number that this server listens on. + * + * @return The port number. + */ + public int getPort() { + int port = getAddress().getPort(); + if( port == 0 && server != null ) { + port = server.socket().getLocalPort(); + } + return port; + } + + public List getDraft() { + return Collections.unmodifiableList( drafts ); + } + + // Runnable IMPLEMENTATION ///////////////////////////////////////////////// + public void run() { + synchronized ( this ) { + if( selectorthread != null ) + throw new IllegalStateException( getClass().getName() + " can only be started once." ); + selectorthread = Thread.currentThread(); + if( isclosed.get() ) { + return; + } + } + selectorthread.setName( "WebsocketSelector" + selectorthread.getId() ); + try { + server = ServerSocketChannel.open(); + server.configureBlocking( false ); + ServerSocket socket = server.socket(); + socket.setReceiveBufferSize( WebSocketImpl.RCVBUF ); + socket.bind( address ); + selector = Selector.open(); + server.register( selector, server.validOps() ); + } catch ( IOException ex ) { + handleFatal( null, ex ); + return; + } + try { + while ( !selectorthread.isInterrupted() ) { + SelectionKey key = null; + WebSocketImpl conn = null; + try { + selector.select(); + Set keys = selector.selectedKeys(); + Iterator i = keys.iterator(); + + while ( i.hasNext() ) { + key = i.next(); + + if( !key.isValid() ) { + // Object o = key.attachment(); + continue; + } + + if( key.isAcceptable() ) { + if( !onConnect( key ) ) { + key.cancel(); + continue; + } + + SocketChannel channel = server.accept(); + channel.configureBlocking( false ); + WebSocketImpl w = wsf.createWebSocket( this, drafts, channel.socket() ); + w.key = channel.register( selector, SelectionKey.OP_READ, w ); + w.channel = wsf.wrapChannel( channel, w.key ); + i.remove(); + allocateBuffers( w ); + continue; + } + + if( key.isReadable() ) { + conn = (WebSocketImpl) key.attachment(); + ByteBuffer buf = takeBuffer(); + try { + if( SocketChannelIOHelper.read( buf, conn, conn.channel ) ) { + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + i.remove(); + if( conn.channel instanceof WrappedByteChannel ) { + if( ( (WrappedByteChannel) conn.channel ).isNeedRead() ) { + iqueue.add( conn ); + } + } + } else + pushBuffer( buf ); + } else { + pushBuffer( buf ); + } + } catch ( IOException e ) { + pushBuffer( buf ); + throw e; + } + } + if( key.isWritable() ) { + conn = (WebSocketImpl) key.attachment(); + if( SocketChannelIOHelper.batch( conn, conn.channel ) ) { + if( key.isValid() ) + key.interestOps( SelectionKey.OP_READ ); + } + } + } + while ( !iqueue.isEmpty() ) { + conn = iqueue.remove( 0 ); + WrappedByteChannel c = ( (WrappedByteChannel) conn.channel ); + ByteBuffer buf = takeBuffer(); + try { + if( SocketChannelIOHelper.readMore( buf, conn, c ) ) + iqueue.add( conn ); + if( buf.hasRemaining() ) { + conn.inQueue.put( buf ); + queue( conn ); + } else { + pushBuffer( buf ); + } + } catch ( IOException e ) { + pushBuffer( buf ); + throw e; + } + + } + } catch ( CancelledKeyException e ) { + // an other thread may cancel the key + } catch ( ClosedByInterruptException e ) { + return; // do the same stuff as when InterruptedException is thrown + } catch ( IOException ex ) { + if( key != null ) + key.cancel(); + handleIOException( key, conn, ex ); + } catch ( InterruptedException e ) { + return;// FIXME controlled shutdown (e.g. take care of buffermanagement) + } + } + + } catch ( RuntimeException e ) { + // should hopefully never occur + handleFatal( null, e ); + } finally { + if( decoders != null ) { + for( WebSocketWorker w : decoders ) { + w.interrupt(); + } + } + if( server != null ) { + try { + server.close(); + } catch ( IOException e ) { + onError( null, e ); + } + } + } + } + protected void allocateBuffers( WebSocket c ) throws InterruptedException { + if( queuesize.get() >= 2 * decoders.size() + 1 ) { + return; + } + queuesize.incrementAndGet(); + buffers.put( createBuffer() ); + } + + protected void releaseBuffers( WebSocket c ) throws InterruptedException { + // queuesize.decrementAndGet(); + // takeBuffer(); + } + + public ByteBuffer createBuffer() { + return ByteBuffer.allocate( WebSocketImpl.RCVBUF ); + } + + private void queue( WebSocketImpl ws ) throws InterruptedException { + if( ws.workerThread == null ) { + ws.workerThread = decoders.get( queueinvokes % decoders.size() ); + queueinvokes++; + } + ws.workerThread.put( ws ); + } + + private ByteBuffer takeBuffer() throws InterruptedException { + return buffers.take(); + } + + private void pushBuffer( ByteBuffer buf ) throws InterruptedException { + if( buffers.size() > queuesize.intValue() ) + return; + buffers.put( buf ); + } + + private void handleIOException( SelectionKey key, WebSocket conn, IOException ex ) { + // onWebsocketError( conn, ex );// conn may be null here + if( conn != null ) { + conn.closeConnection( CloseFrame.ABNORMAL_CLOSE, ex.getMessage() ); + } else if( key != null ) { + SelectableChannel channel = key.channel(); + if( channel != null && channel.isOpen() ) { // this could be the case if the IOException ex is a SSLException + try { + channel.close(); + } catch ( IOException e ) { + // there is nothing that must be done here + } + if( WebSocketImpl.DEBUG ) + System.out.println( "Connection closed because of" + ex ); + } + } + } + + private void handleFatal( WebSocket conn, Exception e ) { + onError( conn, e ); + try { + stop(); + } catch ( IOException e1 ) { + onError( null, e1 ); + } catch ( InterruptedException e1 ) { + Thread.currentThread().interrupt(); + onError( null, e1 ); + } + } + + /** + * Gets the XML string that should be returned if a client requests a Flash + * security policy. + * + * The default implementation allows access from all remote domains, but + * only on the port that this WebSocketServer is listening on. + * + * This is specifically implemented for gitime's WebSocket client for Flash: + * http://github.com/gimite/web-socket-js + * + * @return An XML String that comforms to Flash's security policy. You MUST + * not include the null char at the end, it is appended automatically. + */ + protected String getFlashSecurityPolicy() { + return ""; + } + + @Override + public final void onWebsocketMessage( WebSocket conn, String message ) { + onMessage( conn, message ); + } + + @Override + @Deprecated + public/*final*/void onWebsocketMessageFragment( WebSocket conn, Framedata frame ) {// onFragment should be overloaded instead + onFragment( conn, frame ); + } + + @Override + public final void onWebsocketMessage( WebSocket conn, ByteBuffer blob ) { + onMessage( conn, blob ); + } + + @Override + public final void onWebsocketOpen( WebSocket conn, Handshakedata handshake ) { + if( addConnection( conn ) ) { + onOpen( conn, (ClientHandshake) handshake ); + } + } + + @Override + public final void onWebsocketClose( WebSocket conn, int code, String reason, boolean remote ) { + selector.wakeup(); + try { + if( removeConnection( conn ) ) { + onClose( conn, code, reason, remote ); + } + } finally { + try { + releaseBuffers( conn ); + } catch ( InterruptedException e ) { + Thread.currentThread().interrupt(); + } + } + + } + + /** + * This method performs remove operations on the connection and therefore also gives control over whether the operation shall be synchronized + *

+ * {@link #WebSocketServer(InetSocketAddress, int, List, Collection)} allows to specify a collection which will be used to store current connections in.
+ * Depending on the type on the connection, modifications of that collection may have to be synchronized. + **/ + protected boolean removeConnection( WebSocket ws ) { + boolean removed; + synchronized ( connections ) { + removed = this.connections.remove( ws ); + assert ( removed ); + } + if( isclosed.get() && connections.size() == 0 ) { + selectorthread.interrupt(); + } + return removed; + } + @Override + public ServerHandshakeBuilder onWebsocketHandshakeReceivedAsServer( WebSocket conn, Draft draft, ClientHandshake request ) throws InvalidDataException { + return super.onWebsocketHandshakeReceivedAsServer( conn, draft, request ); + } + + /** @see #removeConnection(WebSocket) */ + protected boolean addConnection( WebSocket ws ) { + if( !isclosed.get() ) { + synchronized ( connections ) { + boolean succ = this.connections.add( ws ); + assert ( succ ); + return succ; + } + } else { + // This case will happen when a new connection gets ready while the server is already stopping. + ws.close( CloseFrame.GOING_AWAY ); + return true;// for consistency sake we will make sure that both onOpen will be called + } + } + /** + * @param conn + * may be null if the error does not belong to a single connection + */ + @Override + public final void onWebsocketError( WebSocket conn, Exception ex ) { + onError( conn, ex ); + } + + @Override + public final void onWriteDemand( WebSocket w ) { + WebSocketImpl conn = (WebSocketImpl) w; + try { + conn.key.interestOps( SelectionKey.OP_READ | SelectionKey.OP_WRITE ); + } catch ( CancelledKeyException e ) { + // the thread which cancels key is responsible for possible cleanup + conn.outQueue.clear(); + } + selector.wakeup(); + } + + @Override + public void onWebsocketCloseInitiated( WebSocket conn, int code, String reason ) { + onCloseInitiated( conn, code, reason ); + } + + @Override + public void onWebsocketClosing( WebSocket conn, int code, String reason, boolean remote ) { + onClosing( conn, code, reason, remote ); + + } + + public void onCloseInitiated( WebSocket conn, int code, String reason ) { + } + + public void onClosing( WebSocket conn, int code, String reason, boolean remote ) { + + } + + public final void setWebSocketFactory( WebSocketServerFactory wsf ) { + this.wsf = wsf; + } + + public final WebSocketFactory getWebSocketFactory() { + return wsf; + } + + /** + * Returns whether a new connection shall be accepted or not.
+ * Therefore method is well suited to implement some kind of connection limitation.
+ * + * @see {@link #onOpen(WebSocket, ClientHandshake)}, {@link #onWebsocketHandshakeReceivedAsServer(WebSocket, Draft, ClientHandshake)} + **/ + protected boolean onConnect( SelectionKey key ) { + return true; + } + + private Socket getSocket( WebSocket conn ) { + WebSocketImpl impl = (WebSocketImpl) conn; + return ( (SocketChannel) impl.key.channel() ).socket(); + } + + @Override + public InetSocketAddress getLocalSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getLocalSocketAddress(); + } + + @Override + public InetSocketAddress getRemoteSocketAddress( WebSocket conn ) { + return (InetSocketAddress) getSocket( conn ).getRemoteSocketAddress(); + } + + /** Called after an opening handshake has been performed and the given websocket is ready to be written on. */ + public abstract void onOpen( WebSocket conn, ClientHandshake handshake ); + /** + * Called after the websocket connection has been closed. + * + * @param code + * The codes can be looked up here: {@link CloseFrame} + * @param reason + * Additional information string + * @param remote + * Returns whether or not the closing of the connection was initiated by the remote host. + **/ + public abstract void onClose( WebSocket conn, int code, String reason, boolean remote ); + /** + * Callback for string messages received from the remote host + * + * @see #onMessage(WebSocket, ByteBuffer) + **/ + public abstract void onMessage( WebSocket conn, String message ); + /** + * Called when errors occurs. If an error causes the websocket connection to fail {@link #onClose(WebSocket, int, String, boolean)} will be called additionally.
+ * This method will be called primarily because of IO or protocol errors.
+ * If the given exception is an RuntimeException that probably means that you encountered a bug.
+ * + * @param con + * Can be null if there error does not belong to one specific websocket. For example if the servers port could not be bound. + **/ + public abstract void onError( WebSocket conn, Exception ex ); + /** + * Callback for binary messages received from the remote host + * + * @see #onMessage(WebSocket, String) + **/ + public void onMessage( WebSocket conn, ByteBuffer message ) { + } + + /** + * @see WebSocket#sendFragmentedFrame(org.java_websocket.framing.Framedata.Opcode, ByteBuffer, boolean) + */ + public void onFragment( WebSocket conn, Framedata fragment ) { + } + + public class WebSocketWorker extends Thread { + + private BlockingQueue iqueue; + + public WebSocketWorker() { + iqueue = new LinkedBlockingQueue(); + setName( "WebSocketWorker-" + getId() ); + setUncaughtExceptionHandler( new UncaughtExceptionHandler() { + @Override + public void uncaughtException( Thread t, Throwable e ) { + getDefaultUncaughtExceptionHandler().uncaughtException( t, e ); + } + } ); + } + + public void put( WebSocketImpl ws ) throws InterruptedException { + iqueue.put( ws ); + } + + @Override + public void run() { + WebSocketImpl ws = null; + try { + while ( true ) { + ByteBuffer buf = null; + ws = iqueue.take(); + buf = ws.inQueue.poll(); + assert ( buf != null ); + try { + ws.decode( buf ); + } finally { + pushBuffer( buf ); + } + } + } catch ( InterruptedException e ) { + } catch ( RuntimeException e ) { + handleFatal( ws, e ); + } + } + } + + public interface WebSocketServerFactory extends WebSocketFactory { + @Override + public WebSocketImpl createWebSocket( WebSocketAdapter a, Draft d, Socket s ); + + public WebSocketImpl createWebSocket( WebSocketAdapter a, List drafts, Socket s ); + + /** + * Allows to wrap the Socketchannel( key.channel() ) to insert a protocol layer( like ssl or proxy authentication) beyond the ws layer. + * + * @param key + * a SelectionKey of an open SocketChannel. + * @return The channel on which the read and write operations will be performed.
+ */ + public ByteChannel wrapChannel( SocketChannel channel, SelectionKey key ) throws IOException; + } +} diff --git a/src/third_party/Java-WebSocket/org/java_websocket/util/Base64.java b/src/third_party/Java-WebSocket/org/java_websocket/util/Base64.java new file mode 100644 index 00000000..38d06ae2 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/util/Base64.java @@ -0,0 +1,2065 @@ +package org.java_websocket.util; + +/** + *

Encodes and decodes to and from Base64 notation.

+ *

Homepage: http://iharder.net/base64.

+ * + *

Example:

+ * + * String encoded = Base64.encode( myByteArray ); + *
+ * byte[] myByteArray = Base64.decode( encoded ); + * + *

The options parameter, which appears in a few places, is used to pass + * several pieces of information to the encoder. In the "higher level" methods such as + * encodeBytes( bytes, options ) the options parameter can be used to indicate such + * things as first gzipping the bytes before encoding them, not inserting linefeeds, + * and encoding using the URL-safe and Ordered dialects.

+ * + *

Note, according to RFC3548, + * Section 2.1, implementations should not add line feeds unless explicitly told + * to do so. I've got Base64 set to this behavior now, although earlier versions + * broke lines by default.

+ * + *

The constants defined in Base64 can be OR-ed together to combine options, so you + * might make a call like this:

+ * + * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); + *

to compress the data before encoding it and then making the output have newline characters.

+ *

Also...

+ * String encoded = Base64.encodeBytes( crazyString.getBytes() ); + * + * + * + *

+ * Change Log: + *

+ *
    + *
  • v2.3.7 - Fixed subtle bug when base 64 input stream contained the + * value 01111111, which is an invalid base 64 character but should not + * throw an ArrayIndexOutOfBoundsException either. Led to discovery of + * mishandling (or potential for better handling) of other bad input + * characters. You should now get an IOException if you try decoding + * something that has bad characters in it.
  • + *
  • v2.3.6 - Fixed bug when breaking lines and the final byte of the encoded + * string ended in the last column; the buffer was not properly shrunk and + * contained an extra (null) byte that made it into the string.
  • + *
  • v2.3.5 - Fixed bug in {@link #encodeFromFile} where estimated buffer size + * was wrong for files of size 31, 34, and 37 bytes.
  • + *
  • v2.3.4 - Fixed bug when working with gzipped streams whereby flushing + * the Base64.OutputStream closed the Base64 encoding (by padding with equals + * signs) too soon. Also added an option to suppress the automatic decoding + * of gzipped streams. Also added experimental support for specifying a + * class loader when using the + * {@link #decodeToObject(java.lang.String, int, java.lang.ClassLoader)} + * method.
  • + *
  • v2.3.3 - Changed default char encoding to US-ASCII which reduces the internal Java + * footprint with its CharEncoders and so forth. Fixed some javadocs that were + * inconsistent. Removed imports and specified things like java.io.IOException + * explicitly inline.
  • + *
  • v2.3.2 - Reduced memory footprint! Finally refined the "guessing" of how big the + * final encoded data will be so that the code doesn't have to create two output + * arrays: an oversized initial one and then a final, exact-sized one. Big win + * when using the {@link #encodeBytesToBytes(byte[])} family of methods (and not + * using the gzip options which uses a different mechanism with streams and stuff).
  • + *
  • v2.3.1 - Added {@link #encodeBytesToBytes(byte[], int, int, int)} and some + * similar helper methods to be more efficient with memory by not returning a + * String but just a byte array.
  • + *
  • v2.3 - This is not a drop-in replacement! This is two years of comments + * and bug fixes queued up and finally executed. Thanks to everyone who sent + * me stuff, and I'm sorry I wasn't able to distribute your fixes to everyone else. + * Much bad coding was cleaned up including throwing exceptions where necessary + * instead of returning null values or something similar. Here are some changes + * that may affect you: + *
      + *
    • Does not break lines, by default. This is to keep in compliance with + * RFC3548.
    • + *
    • Throws exceptions instead of returning null values. Because some operations + * (especially those that may permit the GZIP option) use IO streams, there + * is a possiblity of an java.io.IOException being thrown. After some discussion and + * thought, I've changed the behavior of the methods to throw java.io.IOExceptions + * rather than return null if ever there's an error. I think this is more + * appropriate, though it will require some changes to your code. Sorry, + * it should have been done this way to begin with.
    • + *
    • Removed all references to System.out, System.err, and the like. + * Shame on me. All I can say is sorry they were ever there.
    • + *
    • Throws NullPointerExceptions and IllegalArgumentExceptions as needed + * such as when passed arrays are null or offsets are invalid.
    • + *
    • Cleaned up as much javadoc as I could to avoid any javadoc warnings. + * This was especially annoying before for people who were thorough in their + * own projects and then had gobs of javadoc warnings on this file.
    • + *
    + *
  • v2.2.1 - Fixed bug using URL_SAFE and ORDERED encodings. Fixed bug + * when using very small files (~< 40 bytes).
  • + *
  • v2.2 - Added some helper methods for encoding/decoding directly from + * one file to the next. Also added a main() method to support command line + * encoding/decoding from one file to the next. Also added these Base64 dialects: + *
      + *
    1. The default is RFC3548 format.
    2. + *
    3. Calling Base64.setFormat(Base64.BASE64_FORMAT.URLSAFE_FORMAT) generates + * URL and file name friendly format as described in Section 4 of RFC3548. + * http://www.faqs.org/rfcs/rfc3548.html
    4. + *
    5. Calling Base64.setFormat(Base64.BASE64_FORMAT.ORDERED_FORMAT) generates + * URL and file name friendly format that preserves lexical ordering as described + * in http://www.faqs.org/qa/rfcc-1940.html
    6. + *
    + * Special thanks to Jim Kellerman at http://www.powerset.com/ + * for contributing the new Base64 dialects. + *
  • + * + *
  • v2.1 - Cleaned up javadoc comments and unused variables and methods. Added + * some convenience methods for reading and writing to and from files.
  • + *
  • v2.0.2 - Now specifies UTF-8 encoding in places where the code fails on systems + * with other encodings (like EBCDIC).
  • + *
  • v2.0.1 - Fixed an error when decoding a single byte, that is, when the + * encoded data was a single byte.
  • + *
  • v2.0 - I got rid of methods that used booleans to set options. + * Now everything is more consolidated and cleaner. The code now detects + * when data that's being decoded is gzip-compressed and will decompress it + * automatically. Generally things are cleaner. You'll probably have to + * change some method calls that you were making to support the new + * options format (ints that you "OR" together).
  • + *
  • v1.5.1 - Fixed bug when decompressing and decoding to a + * byte[] using decode( String s, boolean gzipCompressed ). + * Added the ability to "suspend" encoding in the Output Stream so + * you can turn on and off the encoding if you need to embed base64 + * data in an otherwise "normal" stream (like an XML file).
  • + *
  • v1.5 - Output stream pases on flush() command but doesn't do anything itself. + * This helps when using GZIP streams. + * Added the ability to GZip-compress objects before encoding them.
  • + *
  • v1.4 - Added helper methods to read/write files.
  • + *
  • v1.3.6 - Fixed OutputStream.flush() so that 'position' is reset.
  • + *
  • v1.3.5 - Added flag to turn on and off line breaks. Fixed bug in input stream + * where last buffer being read, if not completely full, was not returned.
  • + *
  • v1.3.4 - Fixed when "improperly padded stream" error was thrown at the wrong time.
  • + *
  • v1.3.3 - Fixed I/O streams which were totally messed up.
  • + *
+ * + *

+ * I am placing this code in the Public Domain. Do with it as you will. + * This software comes with no guarantees or warranties but with + * plenty of well-wishing instead! + * Please visit http://iharder.net/base64 + * periodically to check for updates or to contribute improvements. + *

+ * + * @author Robert Harder + * @author rob@iharder.net + * @version 2.3.7 + */ +public class Base64 +{ + +/* ******** P U B L I C F I E L D S ******** */ + + + /** No options specified. Value is zero. */ + public final static int NO_OPTIONS = 0; + + /** Specify encoding in first bit. Value is one. */ + public final static int ENCODE = 1; + + + /** Specify decoding in first bit. Value is zero. */ + public final static int DECODE = 0; + + + /** Specify that data should be gzip-compressed in second bit. Value is two. */ + public final static int GZIP = 2; + + /** Specify that gzipped data should not be automatically gunzipped. */ + public final static int DONT_GUNZIP = 4; + + + /** Do break lines when encoding. Value is 8. */ + public final static int DO_BREAK_LINES = 8; + + /** + * Encode using Base64-like encoding that is URL- and Filename-safe as described + * in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * It is important to note that data encoded this way is not officially valid Base64, + * or at the very least should not be called Base64 without also specifying that is + * was encoded using the URL- and Filename-safe dialect. + */ + public final static int URL_SAFE = 16; + + + /** + * Encode using the special "ordered" dialect of Base64 described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + public final static int ORDERED = 32; + + +/* ******** P R I V A T E F I E L D S ******** */ + + + /** Maximum line length (76) of Base64 output. */ + private final static int MAX_LINE_LENGTH = 76; + + + /** The equals sign (=) as a byte. */ + private final static byte EQUALS_SIGN = (byte)'='; + + + /** The new line character (\n) as a byte. */ + private final static byte NEW_LINE = (byte)'\n'; + + + /** Preferred encoding. */ + private final static String PREFERRED_ENCODING = "US-ASCII"; + + + private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding + private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding + + +/* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ + + /** The 64 valid Base64 values. */ + /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ + private final static byte[] _STANDARD_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' + }; + + + /** + * Translates a Base64 value to either its 6-bit reconstruction value + * or a negative number indicating some other meaning. + **/ + private final static byte[] _STANDARD_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + 62, // Plus sign at decimal 43 + -9,-9,-9, // Decimal 44 - 46 + 63, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ + + /** + * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: + * http://www.faqs.org/rfcs/rfc3548.html. + * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." + */ + private final static byte[] _URL_SAFE_ALPHABET = { + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', + (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' + }; + + /** + * Used in decoding URL- and Filename-safe dialects of Base64. + */ + private final static byte[] _URL_SAFE_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 62, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' + 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 63, // Underscore at decimal 95 + -9, // Decimal 96 + 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' + 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + + +/* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ + + /** + * I don't get the point of this technique, but someone requested it, + * and it is described here: + * http://www.faqs.org/qa/rfcc-1940.html. + */ + private final static byte[] _ORDERED_ALPHABET = { + (byte)'-', + (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', + (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', + (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', + (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', + (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', + (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', + (byte)'_', + (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', + (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', + (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', + (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' + }; + + /** + * Used in decoding the "ordered" dialect of Base64. + */ + private final static byte[] _ORDERED_DECODABET = { + -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 + -5,-5, // Whitespace: Tab and Linefeed + -9,-9, // Decimal 11 - 12 + -5, // Whitespace: Carriage Return + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 + -9,-9,-9,-9,-9, // Decimal 27 - 31 + -5, // Whitespace: Space + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 + -9, // Plus sign at decimal 43 + -9, // Decimal 44 + 0, // Minus sign at decimal 45 + -9, // Decimal 46 + -9, // Slash at decimal 47 + 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine + -9,-9,-9, // Decimal 58 - 60 + -1, // Equals sign at decimal 61 + -9,-9,-9, // Decimal 62 - 64 + 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' + 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' + -9,-9,-9,-9, // Decimal 91 - 94 + 37, // Underscore at decimal 95 + -9, // Decimal 96 + 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' + 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' + -9,-9,-9,-9,-9 // Decimal 123 - 127 + ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 + -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 + }; + + +/* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ + + + /** + * Returns one of the _SOMETHING_ALPHABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URLSAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getAlphabet( int options ) { + if ((options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_ALPHABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_ALPHABET; + } else { + return _STANDARD_ALPHABET; + } + } // end getAlphabet + + + /** + * Returns one of the _SOMETHING_DECODABET byte arrays depending on + * the options specified. + * It's possible, though silly, to specify ORDERED and URL_SAFE + * in which case one of them will be picked, though there is + * no guarantee as to which one will be picked. + */ + private final static byte[] getDecodabet( int options ) { + if( (options & URL_SAFE) == URL_SAFE) { + return _URL_SAFE_DECODABET; + } else if ((options & ORDERED) == ORDERED) { + return _ORDERED_DECODABET; + } else { + return _STANDARD_DECODABET; + } + } // end getAlphabet + + + + /** Defeats instantiation. */ + private Base64(){} + + + + +/* ******** E N C O D I N G M E T H O D S ******** */ + + + /** + * Encodes up to the first three bytes of array threeBytes + * and returns a four-byte array in Base64 notation. + * The actual number of significant bytes in your array is + * given by numSigBytes. + * The array threeBytes needs only be as big as + * numSigBytes. + * Code can reuse a byte array by passing a four-byte array as b4. + * + * @param b4 A reusable byte array to reduce array instantiation + * @param threeBytes the array to convert + * @param numSigBytes the number of significant bytes in your array + * @return four byte array in Base64 notation. + * @since 1.5.1 + */ + private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { + encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); + return b4; + } // end encode3to4 + + + /** + *

Encodes up to three bytes of the array source + * and writes the resulting four Base64 bytes to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 3 for + * the source array or destOffset + 4 for + * the destination array. + * The actual number of significant bytes in your array is + * given by numSigBytes.

+ *

This is the lowest level of the encoding methods with + * all possible parameters.

+ * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param numSigBytes the number of significant bytes in your array + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @return the destination array + * @since 1.3 + */ + private static byte[] encode3to4( + byte[] source, int srcOffset, int numSigBytes, + byte[] destination, int destOffset, int options ) { + + byte[] ALPHABET = getAlphabet( options ); + + // 1 2 3 + // 01234567890123456789012345678901 Bit position + // --------000000001111111122222222 Array position from threeBytes + // --------| || || || | Six bit groups to index ALPHABET + // >>18 >>12 >> 6 >> 0 Right shift necessary + // 0x3f 0x3f 0x3f Additional AND + + // Create buffer with zero-padding if there are only one or two + // significant bytes passed in the array. + // We have to shift left 24 in order to flush out the 1's that appear + // when Java treats a value as negative that is cast from a byte to an int. + int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) + | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) + | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); + + switch( numSigBytes ) + { + case 3: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; + return destination; + + case 2: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + case 1: + destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; + destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; + destination[ destOffset + 2 ] = EQUALS_SIGN; + destination[ destOffset + 3 ] = EQUALS_SIGN; + return destination; + + default: + return destination; + } // end switch + } // end encode3to4 + + + + /** + * Performs Base64 encoding on the raw ByteBuffer, + * writing it to the encoded ByteBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + encoded.put(enc4); + } // end input remaining + } + + + /** + * Performs Base64 encoding on the raw ByteBuffer, + * writing it to the encoded CharBuffer. + * This is an experimental feature. Currently it does not + * pass along any options (such as {@link #DO_BREAK_LINES} + * or {@link #GZIP}. + * + * @param raw input buffer + * @param encoded output buffer + * @since 2.3 + */ + public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ + byte[] raw3 = new byte[3]; + byte[] enc4 = new byte[4]; + + while( raw.hasRemaining() ){ + int rem = Math.min(3,raw.remaining()); + raw.get(raw3,0,rem); + Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); + for( int i = 0; i < 4; i++ ){ + encoded.put( (char)(enc4[i] & 0xFF) ); + } + } // end input remaining + } + + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + *

As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * The object is not GZip-compressed before being encoded. + * + * @param serializableObject The object to encode + * @return The Base64-encoded object + * @throws java.io.IOException if there is an error + * @throws NullPointerException if serializedObject is null + * @since 1.4 + */ + public static String encodeObject( java.io.Serializable serializableObject ) + throws java.io.IOException { + return encodeObject( serializableObject, NO_OPTIONS ); + } // end encodeObject + + + + /** + * Serializes an object and returns the Base64-encoded + * version of that serialized object. + * + *

As of v 2.3, if the object + * cannot be serialized or there is another error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * The object is not GZip-compressed before being encoded. + *

+ * Example options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     * 
+ *

+ * Example: encodeObject( myObj, Base64.GZIP ) or + *

+ * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * @param serializableObject The object to encode + * @param options Specified options + * @return The Base64-encoded object + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @since 2.0 + */ + public static String encodeObject( java.io.Serializable serializableObject, int options ) + throws java.io.IOException { + + if( serializableObject == null ){ + throw new NullPointerException( "Cannot serialize a null object." ); + } // end if: null + + // Streams + java.io.ByteArrayOutputStream baos = null; + java.io.OutputStream b64os = null; + java.util.zip.GZIPOutputStream gzos = null; + java.io.ObjectOutputStream oos = null; + + + try { + // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + if( (options & GZIP) != 0 ){ + // Gzip + gzos = new java.util.zip.GZIPOutputStream(b64os); + oos = new java.io.ObjectOutputStream( gzos ); + } else { + // Not gzipped + oos = new java.io.ObjectOutputStream( b64os ); + } + oos.writeObject( serializableObject ); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ oos.close(); } catch( Exception e ){} + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + // Return value according to relevant encoding. + try { + return new String( baos.toByteArray(), PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue){ + // Fall back to some Java default + return new String( baos.toByteArray() ); + } // end catch + + } // end encode + + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + * @param source The data to convert + * @return The data in Base64-encoded form + * @throws NullPointerException if source array is null + * @since 1.4 + */ + public static String encodeBytes( byte[] source ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

+ * Example options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * + * @param source The data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { + return encodeBytes( source, 0, source.length, options ); + } // end encodeBytes + + + /** + * Encodes a byte array into Base64 notation. + * Does not GZip-compress data. + * + *

As of v 2.3, if there is an error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @return The Base64-encoded data as a String + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 1.4 + */ + public static String encodeBytes( byte[] source, int off, int len ) { + // Since we're not going to have the GZIP encoding turned on, + // we're not going to have an java.io.IOException thrown, so + // we should not force the user to have to catch it. + String encoded = null; + try { + encoded = encodeBytes( source, off, len, NO_OPTIONS ); + } catch (java.io.IOException ex) { + assert false : ex.getMessage(); + } // end catch + assert encoded != null; + return encoded; + } // end encodeBytes + + + + /** + * Encodes a byte array into Base64 notation. + *

+ * Example options:

+     *   GZIP: gzip-compresses object before encoding it.
+     *   DO_BREAK_LINES: break lines at 76 characters
+     *     Note: Technically, this makes your encoding non-compliant.
+     * 
+ *

+ * Example: encodeBytes( myData, Base64.GZIP ) or + *

+ * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) + * + * + *

As of v 2.3, if there is an error with the GZIP stream, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned a null value, but + * in retrospect that's a pretty poor way to handle it.

+ * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.0 + */ + public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + byte[] encoded = encodeBytesToBytes( source, off, len, options ); + + // Return value according to relevant encoding. + try { + return new String( encoded, PREFERRED_ENCODING ); + } // end try + catch (java.io.UnsupportedEncodingException uue) { + return new String( encoded ); + } // end catch + + } // end encodeBytes + + + + + /** + * Similar to {@link #encodeBytes(byte[])} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @return The Base64-encoded data as a byte[] (of ASCII characters) + * @throws NullPointerException if source array is null + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source ) { + byte[] encoded = null; + try { + encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); + } catch( java.io.IOException ex ) { + assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); + } + return encoded; + } + + + /** + * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns + * a byte array instead of instantiating a String. This is more efficient + * if you're working with I/O streams and have large data sets to encode. + * + * + * @param source The data to convert + * @param off Offset in array where conversion should begin + * @param len Length of data to convert + * @param options Specified options + * @return The Base64-encoded data as a String + * @see Base64#GZIP + * @see Base64#DO_BREAK_LINES + * @throws java.io.IOException if there is an error + * @throws NullPointerException if source array is null + * @throws IllegalArgumentException if source array, offset, or length are invalid + * @since 2.3.1 + */ + public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { + + if( source == null ){ + throw new NullPointerException( "Cannot serialize a null array." ); + } // end if: null + + if( off < 0 ){ + throw new IllegalArgumentException( "Cannot have negative offset: " + off ); + } // end if: off < 0 + + if( len < 0 ){ + throw new IllegalArgumentException( "Cannot have length offset: " + len ); + } // end if: len < 0 + + if( off + len > source.length ){ + throw new IllegalArgumentException( + String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); + } // end if: off < 0 + + + + // Compress? + if( (options & GZIP) != 0 ) { + java.io.ByteArrayOutputStream baos = null; + java.util.zip.GZIPOutputStream gzos = null; + Base64.OutputStream b64os = null; + + try { + // GZip -> Base64 -> ByteArray + baos = new java.io.ByteArrayOutputStream(); + b64os = new Base64.OutputStream( baos, ENCODE | options ); + gzos = new java.util.zip.GZIPOutputStream( b64os ); + + gzos.write( source, off, len ); + gzos.close(); + } // end try + catch( java.io.IOException e ) { + // Catch it and then throw it immediately so that + // the finally{} block is called for cleanup. + throw e; + } // end catch + finally { + try{ gzos.close(); } catch( Exception e ){} + try{ b64os.close(); } catch( Exception e ){} + try{ baos.close(); } catch( Exception e ){} + } // end finally + + return baos.toByteArray(); + } // end if: compress + + // Else, don't compress. Better not to use streams at all then. + else { + boolean breakLines = (options & DO_BREAK_LINES) != 0; + + //int len43 = len * 4 / 3; + //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 + // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding + // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines + // Try to determine more precisely how big the array needs to be. + // If we get it right, we don't have to do an array copy, and + // we save a bunch of memory. + int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding + if( breakLines ){ + encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters + } + byte[] outBuff = new byte[ encLen ]; + + + int d = 0; + int e = 0; + int len2 = len - 2; + int lineLength = 0; + for( ; d < len2; d+=3, e+=4 ) { + encode3to4( source, d+off, 3, outBuff, e, options ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) + { + outBuff[e+4] = NEW_LINE; + e++; + lineLength = 0; + } // end if: end of line + } // en dfor: each piece of array + + if( d < len ) { + encode3to4( source, d+off, len - d, outBuff, e, options ); + e += 4; + } // end if: some padding needed + + + // Only resize array if we didn't guess it right. + if( e <= outBuff.length - 1 ){ + // If breaking lines and the last byte falls right at + // the line length (76 bytes per line), there will be + // one extra byte, and the array will need to be resized. + // Not too bad of an estimate on array size, I'd say. + byte[] finalOut = new byte[e]; + System.arraycopy(outBuff,0, finalOut,0,e); + //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); + return finalOut; + } else { + //System.err.println("No need to resize array."); + return outBuff; + } + + } // end else: don't compress + + } // end encodeBytesToBytes + + + + + +/* ******** D E C O D I N G M E T H O D S ******** */ + + + /** + * Decodes four bytes from array source + * and writes the resulting bytes (up to three of them) + * to destination. + * The source and destination arrays can be manipulated + * anywhere along their length by specifying + * srcOffset and destOffset. + * This method does not check to make sure your arrays + * are large enough to accomodate srcOffset + 4 for + * the source array or destOffset + 3 for + * the destination array. + * This method returns the actual number of bytes that + * were converted from the Base64 encoding. + *

This is the lowest level of the decoding methods with + * all possible parameters.

+ * + * + * @param source the array to convert + * @param srcOffset the index where conversion begins + * @param destination the array to hold the conversion + * @param destOffset the index where output will be put + * @param options alphabet type is pulled from this (standard, url-safe, ordered) + * @return the number of decoded bytes converted + * @throws NullPointerException if source or destination arrays are null + * @throws IllegalArgumentException if srcOffset or destOffset are invalid + * or there is not enough room in the array. + * @since 1.3 + */ + private static int decode4to3( + byte[] source, int srcOffset, + byte[] destination, int destOffset, int options ) { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Source array was null." ); + } // end if + if( destination == null ){ + throw new NullPointerException( "Destination array was null." ); + } // end if + if( srcOffset < 0 || srcOffset + 3 >= source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); + } // end if + if( destOffset < 0 || destOffset +2 >= destination.length ){ + throw new IllegalArgumentException( String.format( + "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); + } // end if + + + byte[] DECODABET = getDecodabet( options ); + + // Example: Dk== + if( source[ srcOffset + 2] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + return 1; + } + + // Example: DkL= + else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); + + destination[ destOffset ] = (byte)( outBuff >>> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); + return 2; + } + + // Example: DkLE + else { + // Two ways to do the same thing. Don't know which way I like best. + //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) + // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) + // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) + // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); + int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) + | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) + | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) + | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); + + + destination[ destOffset ] = (byte)( outBuff >> 16 ); + destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); + destination[ destOffset + 2 ] = (byte)( outBuff ); + + return 3; + } + } // end decodeToBytes + + + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. Ignores GUNZIP option, if + * it's set. This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @return decoded data + * @since 2.3.1 + */ + public static byte[] decode( byte[] source ) + throws java.io.IOException { + byte[] decoded = null; +// try { + decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); +// } catch( java.io.IOException ex ) { +// assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); +// } + return decoded; + } + + + + /** + * Low-level access to decoding ASCII characters in + * the form of a byte array. Ignores GUNZIP option, if + * it's set. This is not generally a recommended method, + * although it is used internally as part of the decoding process. + * Special case: if len = 0, an empty array is returned. Still, + * if you need more speed and reduced memory footprint (and aren't + * gzipping), consider this method. + * + * @param source The Base64 encoded data + * @param off The offset of where to begin decoding + * @param len The length of characters to decode + * @param options Can specify options such as alphabet type to use + * @return decoded data + * @throws java.io.IOException If bogus characters exist in source data + * @since 1.3 + */ + public static byte[] decode( byte[] source, int off, int len, int options ) + throws java.io.IOException { + + // Lots of error checking and exception throwing + if( source == null ){ + throw new NullPointerException( "Cannot decode null source array." ); + } // end if + if( off < 0 || off + len > source.length ){ + throw new IllegalArgumentException( String.format( + "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); + } // end if + + if( len == 0 ){ + return new byte[0]; + }else if( len < 4 ){ + throw new IllegalArgumentException( + "Base64-encoded string must have at least four characters, but length specified was " + len ); + } // end if + + byte[] DECODABET = getDecodabet( options ); + + int len34 = len * 3 / 4; // Estimate on array size + byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output + int outBuffPosn = 0; // Keep track of where we're writing + + byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space + int b4Posn = 0; // Keep track of four byte input buffer + int i = 0; // Source array counter + byte sbiDecode = 0; // Special value from DECODABET + + for( i = off; i < off+len; i++ ) { // Loop through source + + sbiDecode = DECODABET[ source[i]&0xFF ]; + + // White space, Equals sign, or legit Base64 character + // Note the values such as -5 and -9 in the + // DECODABETs at the top of the file. + if( sbiDecode >= WHITE_SPACE_ENC ) { + if( sbiDecode >= EQUALS_SIGN_ENC ) { + b4[ b4Posn++ ] = source[i]; // Save non-whitespace + if( b4Posn > 3 ) { // Time to decode? + outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); + b4Posn = 0; + + // If that was the equals sign, break out of 'for' loop + if( source[i] == EQUALS_SIGN ) { + break; + } // end if: equals sign + } // end if: quartet built + } // end if: equals sign or better + } // end if: white space, equals sign or better + else { + // There's a bad input character in the Base64 stream. + throw new java.io.IOException( String.format( + "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); + } // end else: + } // each input character + + byte[] out = new byte[ outBuffPosn ]; + System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); + return out; + } // end decode + + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @return the decoded data + * @throws java.io.IOException If there is a problem + * @since 1.4 + */ + public static byte[] decode( String s ) throws java.io.IOException { + return decode( s, NO_OPTIONS ); + } + + + + /** + * Decodes data from Base64 notation, automatically + * detecting gzip-compressed data and decompressing it. + * + * @param s the string to decode + * @param options encode options such as URL_SAFE + * @return the decoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if s is null + * @since 1.4 + */ + public static byte[] decode( String s, int options ) throws java.io.IOException { + + if( s == null ){ + throw new NullPointerException( "Input string was null." ); + } // end if + + byte[] bytes; + try { + bytes = s.getBytes( PREFERRED_ENCODING ); + } // end try + catch( java.io.UnsupportedEncodingException uee ) { + bytes = s.getBytes(); + } // end catch + // + + // Decode + bytes = decode( bytes, 0, bytes.length, options ); + + // Check to see if it's gzip-compressed + // GZIP Magic Two-Byte Number: 0x8b1f (35615) + boolean dontGunzip = (options & DONT_GUNZIP) != 0; + if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { + + int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); + if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { + java.io.ByteArrayInputStream bais = null; + java.util.zip.GZIPInputStream gzis = null; + java.io.ByteArrayOutputStream baos = null; + byte[] buffer = new byte[2048]; + int length = 0; + + try { + baos = new java.io.ByteArrayOutputStream(); + bais = new java.io.ByteArrayInputStream( bytes ); + gzis = new java.util.zip.GZIPInputStream( bais ); + + while( ( length = gzis.read( buffer ) ) >= 0 ) { + baos.write(buffer,0,length); + } // end while: reading input + + // No error? Get new bytes. + bytes = baos.toByteArray(); + + } // end try + catch( java.io.IOException e ) { + e.printStackTrace(); + // Just return originally-decoded bytes + } // end catch + finally { + try{ baos.close(); } catch( Exception e ){} + try{ gzis.close(); } catch( Exception e ){} + try{ bais.close(); } catch( Exception e ){} + } // end finally + + } // end if: gzipped + } // end if: bytes.length >= 2 + + return bytes; + } // end decode + + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * + * @param encodedObject The Base64 data to decode + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 1.5 + */ + public static Object decodeToObject( String encodedObject ) + throws java.io.IOException, java.lang.ClassNotFoundException { + return decodeToObject(encodedObject,NO_OPTIONS,null); + } + + + /** + * Attempts to decode Base64 data and deserialize a Java + * Object within. Returns null if there was an error. + * If loader is not null, it will be the class loader + * used when deserializing. + * + * @param encodedObject The Base64 data to decode + * @param options Various parameters related to decoding + * @param loader Optional class loader to use in deserializing classes. + * @return The decoded and deserialized object + * @throws NullPointerException if encodedObject is null + * @throws java.io.IOException if there is a general error + * @throws ClassNotFoundException if the decoded object is of a + * class that cannot be found by the JVM + * @since 2.3.4 + */ + public static Object decodeToObject( + String encodedObject, int options, final ClassLoader loader ) + throws java.io.IOException, java.lang.ClassNotFoundException { + + // Decode and gunzip if necessary + byte[] objBytes = decode( encodedObject, options ); + + java.io.ByteArrayInputStream bais = null; + java.io.ObjectInputStream ois = null; + Object obj = null; + + try { + bais = new java.io.ByteArrayInputStream( objBytes ); + + // If no custom class loader is provided, use Java's builtin OIS. + if( loader == null ){ + ois = new java.io.ObjectInputStream( bais ); + } // end if: no loader provided + + // Else make a customized object input stream that uses + // the provided class loader. + else { + ois = new java.io.ObjectInputStream(bais){ + @Override + public Class resolveClass(java.io.ObjectStreamClass streamClass) + throws java.io.IOException, ClassNotFoundException { + Class c = Class.forName(streamClass.getName(), false, loader); + if( c == null ){ + return super.resolveClass(streamClass); + } else { + return c; // Class loader knows of this class. + } // end else: not null + } // end resolveClass + }; // end ois + } // end else: no custom class loader + + obj = ois.readObject(); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + catch( java.lang.ClassNotFoundException e ) { + throw e; // Catch and throw in order to execute finally{} + } // end catch + finally { + try{ bais.close(); } catch( Exception e ){} + try{ ois.close(); } catch( Exception e ){} + } // end finally + + return obj; + } // end decodeObject + + + + /** + * Convenience method for encoding data to a file. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param dataToEncode byte array of data to encode in base64 form + * @param filename Filename for saving encoded data + * @throws java.io.IOException if there is an error + * @throws NullPointerException if dataToEncode is null + * @since 2.1 + */ + public static void encodeToFile( byte[] dataToEncode, String filename ) + throws java.io.IOException { + + if( dataToEncode == null ){ + throw new NullPointerException( "Data to encode was null." ); + } // end iff + + Base64.OutputStream bos = null; + try { + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.ENCODE ); + bos.write( dataToEncode ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end encodeToFile + + + /** + * Convenience method for decoding data to a file. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param dataToDecode Base64-encoded data as a string + * @param filename Filename for saving decoded data + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static void decodeToFile( String dataToDecode, String filename ) + throws java.io.IOException { + + Base64.OutputStream bos = null; + try{ + bos = new Base64.OutputStream( + new java.io.FileOutputStream( filename ), Base64.DECODE ); + bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and throw to execute finally{} block + } // end catch: java.io.IOException + finally { + try{ bos.close(); } catch( Exception e ){} + } // end finally + + } // end decodeToFile + + + + + /** + * Convenience method for reading a base64-encoded + * file and decoding it. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param filename Filename for reading encoded data + * @return decoded byte array + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static byte[] decodeFromFile( String filename ) + throws java.io.IOException { + + byte[] decodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = null; + int length = 0; + int numBytes = 0; + + // Check for size of file + if( file.length() > Integer.MAX_VALUE ) + { + throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); + } // end if: file too big for int index + buffer = new byte[ (int)file.length() ]; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.DECODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + decodedData = new byte[ length ]; + System.arraycopy( buffer, 0, decodedData, 0, length ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return decodedData; + } // end decodeFromFile + + + + /** + * Convenience method for reading a binary file + * and base64-encoding it. + * + *

As of v 2.3, if there is a error, + * the method will throw an java.io.IOException. This is new to v2.3! + * In earlier versions, it just returned false, but + * in retrospect that's a pretty poor way to handle it.

+ * + * @param filename Filename for reading binary data + * @return base64-encoded string + * @throws java.io.IOException if there is an error + * @since 2.1 + */ + public static String encodeFromFile( String filename ) + throws java.io.IOException { + + String encodedData = null; + Base64.InputStream bis = null; + try + { + // Set up some useful variables + java.io.File file = new java.io.File( filename ); + byte[] buffer = new byte[ Math.max((int)(file.length() * 1.4+1),40) ]; // Need max() for math on small files (v2.2.1); Need +1 for a few corner cases (v2.3.5) + int length = 0; + int numBytes = 0; + + // Open a stream + bis = new Base64.InputStream( + new java.io.BufferedInputStream( + new java.io.FileInputStream( file ) ), Base64.ENCODE ); + + // Read until done + while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { + length += numBytes; + } // end while + + // Save in a variable to return + encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); + + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch: java.io.IOException + finally { + try{ bis.close(); } catch( Exception e) {} + } // end finally + + return encodedData; + } // end encodeFromFile + + /** + * Reads infile and encodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void encodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + String encoded = Base64.encodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end encodeFileToFile + + + /** + * Reads infile and decodes it to outfile. + * + * @param infile Input file + * @param outfile Output file + * @throws java.io.IOException if there is an error + * @since 2.2 + */ + public static void decodeFileToFile( String infile, String outfile ) + throws java.io.IOException { + + byte[] decoded = Base64.decodeFromFile( infile ); + java.io.OutputStream out = null; + try{ + out = new java.io.BufferedOutputStream( + new java.io.FileOutputStream( outfile ) ); + out.write( decoded ); + } // end try + catch( java.io.IOException e ) { + throw e; // Catch and release to execute finally{} + } // end catch + finally { + try { out.close(); } + catch( Exception ex ){} + } // end finally + } // end decodeFileToFile + + + /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.InputStream} will read data from another + * java.io.InputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class InputStream extends java.io.FilterInputStream { + + private boolean encode; // Encoding or decoding + private int position; // Current position in the buffer + private byte[] buffer; // Small buffer holding converted data + private int bufferLength; // Length of buffer (3 or 4) + private int numSigBytes; // Number of meaningful bytes in the buffer + private int lineLength; + private boolean breakLines; // Break lines at less than 80 characters + private int options; // Record options used to create the stream. + private byte[] decodabet; // Local copies to avoid extra method calls + + + /** + * Constructs a {@link Base64.InputStream} in DECODE mode. + * + * @param in the java.io.InputStream from which to read data. + * @since 1.3 + */ + public InputStream( java.io.InputStream in ) { + this( in, DECODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.InputStream} in + * either ENCODE or DECODE mode. + *

+ * Valid options:

+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DO_BREAK_LINES: break lines at 76 characters
+         *     (only meaningful when encoding)
+         * 
+ *

+ * Example: new Base64.InputStream( in, Base64.DECODE ) + * + * + * @param in the java.io.InputStream from which to read data. + * @param options Specified options + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 2.0 + */ + public InputStream( java.io.InputStream in, int options ) { + + super( in ); + this.options = options; // Record for later + this.breakLines = (options & DO_BREAK_LINES) > 0; + this.encode = (options & ENCODE) > 0; + this.bufferLength = encode ? 4 : 3; + this.buffer = new byte[ bufferLength ]; + this.position = -1; + this.lineLength = 0; + this.decodabet = getDecodabet(options); + } // end constructor + + /** + * Reads enough of the input stream to convert + * to/from Base64 and returns the next byte. + * + * @return next byte + * @since 1.3 + */ + @Override + public int read() throws java.io.IOException { + + // Do we need to get data? + if( position < 0 ) { + if( encode ) { + byte[] b3 = new byte[3]; + int numBinaryBytes = 0; + for( int i = 0; i < 3; i++ ) { + int b = in.read(); + + // If end of stream, b is -1. + if( b >= 0 ) { + b3[i] = (byte)b; + numBinaryBytes++; + } else { + break; // out of for loop + } // end else: end of stream + + } // end for: each needed input byte + + if( numBinaryBytes > 0 ) { + encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); + position = 0; + numSigBytes = 4; + } // end if: got data + else { + return -1; // Must be end of stream + } // end else + } // end if: encoding + + // Else decoding + else { + byte[] b4 = new byte[4]; + int i = 0; + for( i = 0; i < 4; i++ ) { + // Read four "meaningful" bytes: + int b = 0; + do{ b = in.read(); } + while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); + + if( b < 0 ) { + break; // Reads a -1 if end of stream + } // end if: end of stream + + b4[i] = (byte)b; + } // end for: each needed input byte + + if( i == 4 ) { + numSigBytes = decode4to3( b4, 0, buffer, 0, options ); + position = 0; + } // end if: got four characters + else if( i == 0 ){ + return -1; + } // end else if: also padded correctly + else { + // Must have broken out from above. + throw new java.io.IOException( "Improperly padded Base64 input." ); + } // end + + } // end else: decode + } // end else: get data + + // Got data? + if( position >= 0 ) { + // End of relevant data? + if( /*!encode &&*/ position >= numSigBytes ){ + return -1; + } // end if: got data + + if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { + lineLength = 0; + return '\n'; + } // end if + else { + lineLength++; // This isn't important when decoding + // but throwing an extra "if" seems + // just as wasteful. + + int b = buffer[ position++ ]; + + if( position >= bufferLength ) { + position = -1; + } // end if: end + + return b & 0xFF; // This is how you "cast" a byte that's + // intended to be unsigned. + } // end else + } // end if: position >= 0 + + // Else error + else { + throw new java.io.IOException( "Error in Base64 code reading stream." ); + } // end else + } // end read + + + /** + * Calls {@link #read()} repeatedly until the end of stream + * is reached or len bytes are read. + * Returns number of bytes read into array or -1 if + * end of stream is encountered. + * + * @param dest array to hold values + * @param off offset for array + * @param len max number of bytes to read into array + * @return bytes read into array or -1 if end of stream is encountered. + * @since 1.3 + */ + @Override + public int read( byte[] dest, int off, int len ) + throws java.io.IOException { + int i; + int b; + for( i = 0; i < len; i++ ) { + b = read(); + + if( b >= 0 ) { + dest[off + i] = (byte) b; + } + else if( i == 0 ) { + return -1; + } + else { + break; // Out of 'for' loop + } // Out of 'for' loop + } // end for: each byte read + return i; + } // end read + + } // end inner class InputStream + + + + + + + /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ + + + + /** + * A {@link Base64.OutputStream} will write data to another + * java.io.OutputStream, given in the constructor, + * and encode/decode to/from Base64 notation on the fly. + * + * @see Base64 + * @since 1.3 + */ + public static class OutputStream extends java.io.FilterOutputStream { + + private boolean encode; + private int position; + private byte[] buffer; + private int bufferLength; + private int lineLength; + private boolean breakLines; + private byte[] b4; // Scratch used in a few places + private boolean suspendEncoding; + private int options; // Record for later + private byte[] decodabet; // Local copies to avoid extra method calls + + /** + * Constructs a {@link Base64.OutputStream} in ENCODE mode. + * + * @param out the java.io.OutputStream to which data will be written. + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out ) { + this( out, ENCODE ); + } // end constructor + + + /** + * Constructs a {@link Base64.OutputStream} in + * either ENCODE or DECODE mode. + *

+ * Valid options:

+         *   ENCODE or DECODE: Encode or Decode as data is read.
+         *   DO_BREAK_LINES: don't break lines at 76 characters
+         *     (only meaningful when encoding)
+         * 
+ *

+ * Example: new Base64.OutputStream( out, Base64.ENCODE ) + * + * @param out the java.io.OutputStream to which data will be written. + * @param options Specified options. + * @see Base64#ENCODE + * @see Base64#DECODE + * @see Base64#DO_BREAK_LINES + * @since 1.3 + */ + public OutputStream( java.io.OutputStream out, int options ) { + super( out ); + this.breakLines = (options & DO_BREAK_LINES) != 0; + this.encode = (options & ENCODE) != 0; + this.bufferLength = encode ? 3 : 4; + this.buffer = new byte[ bufferLength ]; + this.position = 0; + this.lineLength = 0; + this.suspendEncoding = false; + this.b4 = new byte[4]; + this.options = options; + this.decodabet = getDecodabet(options); + } // end constructor + + + /** + * Writes the byte to the output stream after + * converting to/from Base64 notation. + * When encoding, bytes are buffered three + * at a time before the output stream actually + * gets a write() call. + * When decoding, bytes are buffered four + * at a time. + * + * @param theByte the byte to write + * @since 1.3 + */ + @Override + public void write(int theByte) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theByte ); + return; + } // end if: supsended + + // Encode? + if( encode ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to encode. + + this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); + + lineLength += 4; + if( breakLines && lineLength >= MAX_LINE_LENGTH ) { + this.out.write( NEW_LINE ); + lineLength = 0; + } // end if: end of line + + position = 0; + } // end if: enough to output + } // end if: encoding + + // Else, Decoding + else { + // Meaningful Base64 character? + if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { + buffer[ position++ ] = (byte)theByte; + if( position >= bufferLength ) { // Enough to output. + + int len = Base64.decode4to3( buffer, 0, b4, 0, options ); + out.write( b4, 0, len ); + position = 0; + } // end if: enough to output + } // end if: meaningful base64 character + else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { + throw new java.io.IOException( "Invalid character in Base64 data." ); + } // end else: not white space either + } // end else: decoding + } // end write + + + + /** + * Calls {@link #write(int)} repeatedly until len + * bytes are written. + * + * @param theBytes array from which to read bytes + * @param off offset for array + * @param len max number of bytes to read into array + * @since 1.3 + */ + @Override + public void write( byte[] theBytes, int off, int len ) + throws java.io.IOException { + // Encoding suspended? + if( suspendEncoding ) { + this.out.write( theBytes, off, len ); + return; + } // end if: supsended + + for( int i = 0; i < len; i++ ) { + write( theBytes[ off + i ] ); + } // end for: each byte written + + } // end write + + + + /** + * Method added by PHIL. [Thanks, PHIL. -Rob] + * This pads the buffer without closing the stream. + * @throws java.io.IOException if there's an error. + */ + public void flushBase64() throws java.io.IOException { + if( position > 0 ) { + if( encode ) { + out.write( encode3to4( b4, buffer, position, options ) ); + position = 0; + } // end if: encoding + else { + throw new java.io.IOException( "Base64 input not properly padded." ); + } // end else: decoding + } // end if: buffer partially full + + } // end flush + + + /** + * Flushes and closes (I think, in the superclass) the stream. + * + * @since 1.3 + */ + @Override + public void close() throws java.io.IOException { + // 1. Ensure that pending characters are written + flushBase64(); + + // 2. Actually close the stream + // Base class both flushes and closes. + super.close(); + + buffer = null; + out = null; + } // end close + + + + /** + * Suspends encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @throws java.io.IOException if there's an error flushing + * @since 1.5.1 + */ + public void suspendEncoding() throws java.io.IOException { + flushBase64(); + this.suspendEncoding = true; + } // end suspendEncoding + + + /** + * Resumes encoding of the stream. + * May be helpful if you need to embed a piece of + * base64-encoded data in a stream. + * + * @since 1.5.1 + */ + public void resumeEncoding() { + this.suspendEncoding = false; + } // end resumeEncoding + + + + } // end inner class OutputStream + + +} // end class Base64 diff --git a/src/third_party/Java-WebSocket/org/java_websocket/util/Charsetfunctions.java b/src/third_party/Java-WebSocket/org/java_websocket/util/Charsetfunctions.java new file mode 100644 index 00000000..bd8ad299 --- /dev/null +++ b/src/third_party/Java-WebSocket/org/java_websocket/util/Charsetfunctions.java @@ -0,0 +1,90 @@ +package org.java_websocket.util; + +import java.io.UnsupportedEncodingException; +import java.nio.ByteBuffer; +import java.nio.charset.CharacterCodingException; +import java.nio.charset.Charset; +import java.nio.charset.CharsetDecoder; +import java.nio.charset.CodingErrorAction; + +import org.java_websocket.exceptions.InvalidDataException; +import org.java_websocket.framing.CloseFrame; + +public class Charsetfunctions { + + public static CodingErrorAction codingErrorAction = CodingErrorAction.REPORT; + + /* + * @return UTF-8 encoding in bytes + */ + public static byte[] utf8Bytes( String s ) { + try { + return s.getBytes( "UTF8" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + /* + * @return ASCII encoding in bytes + */ + public static byte[] asciiBytes( String s ) { + try { + return s.getBytes( "ASCII" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + public static String stringAscii( byte[] bytes ) { + return stringAscii( bytes, 0, bytes.length ); + } + + public static String stringAscii( byte[] bytes, int offset, int length ){ + try { + return new String( bytes, offset, length, "ASCII" ); + } catch ( UnsupportedEncodingException e ) { + throw new RuntimeException( e ); + } + } + + public static String stringUtf8( byte[] bytes ) throws InvalidDataException { + return stringUtf8( ByteBuffer.wrap( bytes ) ); + } + + /*public static String stringUtf8( byte[] bytes, int off, int length ) throws InvalidDataException { + CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); + decode.onMalformedInput( codingErrorAction ); + decode.onUnmappableCharacter( codingErrorAction ); + //decode.replaceWith( "X" ); + String s; + try { + s = decode.decode( ByteBuffer.wrap( bytes, off, length ) ).toString(); + } catch ( CharacterCodingException e ) { + throw new InvalidDataException( CloseFrame.NO_UTF8, e ); + } + return s; + }*/ + + public static String stringUtf8( ByteBuffer bytes ) throws InvalidDataException { + CharsetDecoder decode = Charset.forName( "UTF8" ).newDecoder(); + decode.onMalformedInput( codingErrorAction ); + decode.onUnmappableCharacter( codingErrorAction ); + // decode.replaceWith( "X" ); + String s; + try { + bytes.mark(); + s = decode.decode( bytes ).toString(); + bytes.reset(); + } catch ( CharacterCodingException e ) { + throw new InvalidDataException( CloseFrame.NO_UTF8, e ); + } + return s; + } + + public static void main( String[] args ) throws InvalidDataException { + stringUtf8( utf8Bytes( "\0" ) ); + stringAscii( asciiBytes( "\0" ) ); + } + +}