diff -r 000000000000 -r 6474c204b198 mobile/android/thirdparty/com/codebutler/android_websockets/WebSocketClient.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mobile/android/thirdparty/com/codebutler/android_websockets/WebSocketClient.java Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,252 @@ +// This file was copied from: +// https://github.com/koush/android-websockets/blob/master/src/com/codebutler/android_websockets/WebSocketClient.java + +package com.codebutler.android_websockets; + +import android.os.Handler; +import android.os.HandlerThread; +import android.text.TextUtils; +import android.util.Base64; +import android.util.Log; +import org.apache.http.*; +import org.apache.http.client.HttpResponseException; +import org.apache.http.message.BasicLineParser; +import org.apache.http.message.BasicNameValuePair; + +import javax.net.SocketFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManager; +import java.io.EOFException; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.net.Socket; +import java.net.URI; +import java.security.KeyManagementException; +import java.security.NoSuchAlgorithmException; +import java.util.List; + +public class WebSocketClient { + private static final String TAG = "WebSocketClient"; + + private URI mURI; + private Listener mListener; + private Socket mSocket; + private Thread mThread; + private HandlerThread mHandlerThread; + private Handler mHandler; + private List mExtraHeaders; + private HybiParser mParser; + private boolean mConnected; + + private final Object mSendLock = new Object(); + + private static TrustManager[] sTrustManagers; + + public static void setTrustManagers(TrustManager[] tm) { + sTrustManagers = tm; + } + + public WebSocketClient(URI uri, Listener listener, List extraHeaders) { + mURI = uri; + mListener = listener; + mExtraHeaders = extraHeaders; + mConnected = false; + mParser = new HybiParser(this); + + mHandlerThread = new HandlerThread("websocket-thread"); + mHandlerThread.start(); + mHandler = new Handler(mHandlerThread.getLooper()); + } + + public Listener getListener() { + return mListener; + } + + public void connect() { + if (mThread != null && mThread.isAlive()) { + return; + } + + mThread = new Thread(new Runnable() { + @Override + public void run() { + try { + int port = (mURI.getPort() != -1) ? mURI.getPort() : ((mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? 443 : 80); + + String path = TextUtils.isEmpty(mURI.getPath()) ? "/" : mURI.getPath(); + if (!TextUtils.isEmpty(mURI.getQuery())) { + path += "?" + mURI.getQuery(); + } + + String originScheme = mURI.getScheme().equals("wss") ? "https" : "http"; + URI origin = new URI(originScheme, "//" + mURI.getHost(), null); + + SocketFactory factory = (mURI.getScheme().equals("wss") || mURI.getScheme().equals("https")) ? getSSLSocketFactory() : SocketFactory.getDefault(); + mSocket = factory.createSocket(mURI.getHost(), port); + + PrintWriter out = new PrintWriter(mSocket.getOutputStream()); + out.print("GET " + path + " HTTP/1.1\r\n"); + out.print("Upgrade: websocket\r\n"); + out.print("Connection: Upgrade\r\n"); + out.print("Host: " + mURI.getHost() + "\r\n"); + out.print("Origin: " + origin.toString() + "\r\n"); + out.print("Sec-WebSocket-Key: " + createSecret() + "\r\n"); + out.print("Sec-WebSocket-Version: 13\r\n"); + if (mExtraHeaders != null) { + for (NameValuePair pair : mExtraHeaders) { + out.print(String.format("%s: %s\r\n", pair.getName(), pair.getValue())); + } + } + out.print("\r\n"); + out.flush(); + + HybiParser.HappyDataInputStream stream = new HybiParser.HappyDataInputStream(mSocket.getInputStream()); + + // Read HTTP response status line. + StatusLine statusLine = parseStatusLine(readLine(stream)); + if (statusLine == null) { + throw new HttpException("Received no reply from server."); + } else if (statusLine.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) { + throw new HttpResponseException(statusLine.getStatusCode(), statusLine.getReasonPhrase()); + } + + // Read HTTP response headers. + String line; + while (!TextUtils.isEmpty(line = readLine(stream))) { + Header header = parseHeader(line); + if (header.getName().equals("Sec-WebSocket-Accept")) { + // FIXME: Verify the response... + } + } + + mListener.onConnect(); + + mConnected = true; + + // Now decode websocket frames. + mParser.start(stream); + + } catch (EOFException ex) { + Log.d(TAG, "WebSocket EOF!", ex); + mListener.onDisconnect(0, "EOF"); + mConnected = false; + + } catch (SSLException ex) { + // Connection reset by peer + Log.d(TAG, "Websocket SSL error!", ex); + mListener.onDisconnect(0, "SSL"); + mConnected = false; + + } catch (Exception ex) { + mListener.onError(ex); + } + } + }); + mThread.start(); + } + + public void disconnect() { + if (mSocket != null) { + mHandler.post(new Runnable() { + @Override + public void run() { + if (mSocket != null) { + try { + mSocket.close(); + } catch (IOException ex) { + Log.d(TAG, "Error while disconnecting", ex); + mListener.onError(ex); + } + mSocket = null; + } + mConnected = false; + } + }); + } + } + + public void send(String data) { + sendFrame(mParser.frame(data)); + } + + public void send(byte[] data) { + sendFrame(mParser.frame(data)); + } + + public boolean isConnected() { + return mConnected; + } + + private StatusLine parseStatusLine(String line) { + if (TextUtils.isEmpty(line)) { + return null; + } + return BasicLineParser.parseStatusLine(line, new BasicLineParser()); + } + + private Header parseHeader(String line) { + return BasicLineParser.parseHeader(line, new BasicLineParser()); + } + + // Can't use BufferedReader because it buffers past the HTTP data. + private String readLine(HybiParser.HappyDataInputStream reader) throws IOException { + int readChar = reader.read(); + if (readChar == -1) { + return null; + } + StringBuilder string = new StringBuilder(""); + while (readChar != '\n') { + if (readChar != '\r') { + string.append((char) readChar); + } + + readChar = reader.read(); + if (readChar == -1) { + return null; + } + } + return string.toString(); + } + + private String createSecret() { + byte[] nonce = new byte[16]; + for (int i = 0; i < 16; i++) { + nonce[i] = (byte) (Math.random() * 256); + } + return Base64.encodeToString(nonce, Base64.DEFAULT).trim(); + } + + void sendFrame(final byte[] frame) { + mHandler.post(new Runnable() { + @Override + public void run() { + try { + synchronized (mSendLock) { + OutputStream outputStream = mSocket.getOutputStream(); + outputStream.write(frame); + outputStream.flush(); + } + } catch (IOException e) { + mListener.onError(e); + } + } + }); + } + + public interface Listener { + public void onConnect(); + public void onMessage(String message); + public void onMessage(byte[] data); + public void onDisconnect(int code, String reason); + public void onError(Exception error); + } + + private SSLSocketFactory getSSLSocketFactory() throws NoSuchAlgorithmException, KeyManagementException { + SSLContext context = SSLContext.getInstance("TLS"); + context.init(null, sTrustManagers, null); + return context.getSocketFactory(); + } +}