Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 //
2 // HybiParser.java: draft-ietf-hybi-thewebsocketprotocol-13 parser
3 //
4 // Based on code from the faye project.
5 // https://github.com/faye/faye-websocket-node
6 // Copyright (c) 2009-2012 James Coglan
7 //
8 // Ported from Javascript to Java by Eric Butler <eric@codebutler.com>
9 //
10 // (The MIT License)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 //
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 //
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
31 package com.codebutler.android_websockets;
33 import android.util.Log;
35 import java.io.*;
36 import java.util.Arrays;
37 import java.util.List;
39 public class HybiParser {
40 private static final String TAG = "HybiParser";
42 private WebSocketClient mClient;
44 private boolean mMasking = true;
46 private int mStage;
48 private boolean mFinal;
49 private boolean mMasked;
50 private int mOpcode;
51 private int mLengthSize;
52 private int mLength;
53 private int mMode;
55 private byte[] mMask = new byte[0];
56 private byte[] mPayload = new byte[0];
58 private boolean mClosed = false;
60 private ByteArrayOutputStream mBuffer = new ByteArrayOutputStream();
62 private static final int BYTE = 255;
63 private static final int FIN = 128;
64 private static final int MASK = 128;
65 private static final int RSV1 = 64;
66 private static final int RSV2 = 32;
67 private static final int RSV3 = 16;
68 private static final int OPCODE = 15;
69 private static final int LENGTH = 127;
71 private static final int MODE_TEXT = 1;
72 private static final int MODE_BINARY = 2;
74 private static final int OP_CONTINUATION = 0;
75 private static final int OP_TEXT = 1;
76 private static final int OP_BINARY = 2;
77 private static final int OP_CLOSE = 8;
78 private static final int OP_PING = 9;
79 private static final int OP_PONG = 10;
81 private static final List<Integer> OPCODES = Arrays.asList(
82 OP_CONTINUATION,
83 OP_TEXT,
84 OP_BINARY,
85 OP_CLOSE,
86 OP_PING,
87 OP_PONG
88 );
90 private static final List<Integer> FRAGMENTED_OPCODES = Arrays.asList(
91 OP_CONTINUATION, OP_TEXT, OP_BINARY
92 );
94 public HybiParser(WebSocketClient client) {
95 mClient = client;
96 }
98 private static byte[] mask(byte[] payload, byte[] mask, int offset) {
99 if (mask.length == 0) return payload;
101 for (int i = 0; i < payload.length - offset; i++) {
102 payload[offset + i] = (byte) (payload[offset + i] ^ mask[i % 4]);
103 }
104 return payload;
105 }
107 public void start(HappyDataInputStream stream) throws IOException {
108 while (true) {
109 if (stream.available() == -1) break;
110 switch (mStage) {
111 case 0:
112 parseOpcode(stream.readByte());
113 break;
114 case 1:
115 parseLength(stream.readByte());
116 break;
117 case 2:
118 parseExtendedLength(stream.readBytes(mLengthSize));
119 break;
120 case 3:
121 mMask = stream.readBytes(4);
122 mStage = 4;
123 break;
124 case 4:
125 mPayload = stream.readBytes(mLength);
126 emitFrame();
127 mStage = 0;
128 break;
129 }
130 }
131 mClient.getListener().onDisconnect(0, "EOF");
132 }
134 private void parseOpcode(byte data) throws ProtocolError {
135 boolean rsv1 = (data & RSV1) == RSV1;
136 boolean rsv2 = (data & RSV2) == RSV2;
137 boolean rsv3 = (data & RSV3) == RSV3;
139 if (rsv1 || rsv2 || rsv3) {
140 throw new ProtocolError("RSV not zero");
141 }
143 mFinal = (data & FIN) == FIN;
144 mOpcode = (data & OPCODE);
145 mMask = new byte[0];
146 mPayload = new byte[0];
148 if (!OPCODES.contains(mOpcode)) {
149 throw new ProtocolError("Bad opcode");
150 }
152 if (!FRAGMENTED_OPCODES.contains(mOpcode) && !mFinal) {
153 throw new ProtocolError("Expected non-final packet");
154 }
156 mStage = 1;
157 }
159 private void parseLength(byte data) {
160 mMasked = (data & MASK) == MASK;
161 mLength = (data & LENGTH);
163 if (mLength >= 0 && mLength <= 125) {
164 mStage = mMasked ? 3 : 4;
165 } else {
166 mLengthSize = (mLength == 126) ? 2 : 8;
167 mStage = 2;
168 }
169 }
171 private void parseExtendedLength(byte[] buffer) throws ProtocolError {
172 mLength = getInteger(buffer);
173 mStage = mMasked ? 3 : 4;
174 }
176 public byte[] frame(String data) {
177 return frame(data, OP_TEXT, -1);
178 }
180 public byte[] frame(byte[] data) {
181 return frame(data, OP_BINARY, -1);
182 }
184 private byte[] frame(byte[] data, int opcode, int errorCode) {
185 return frame((Object)data, opcode, errorCode);
186 }
188 private byte[] frame(String data, int opcode, int errorCode) {
189 return frame((Object)data, opcode, errorCode);
190 }
192 private byte[] frame(Object data, int opcode, int errorCode) {
193 if (mClosed) return null;
195 Log.d(TAG, "Creating frame for: " + data + " op: " + opcode + " err: " + errorCode);
197 byte[] buffer = (data instanceof String) ? decode((String) data) : (byte[]) data;
198 int insert = (errorCode > 0) ? 2 : 0;
199 int length = buffer.length + insert;
200 int header = (length <= 125) ? 2 : (length <= 65535 ? 4 : 10);
201 int offset = header + (mMasking ? 4 : 0);
202 int masked = mMasking ? MASK : 0;
203 byte[] frame = new byte[length + offset];
205 frame[0] = (byte) ((byte)FIN | (byte)opcode);
207 if (length <= 125) {
208 frame[1] = (byte) (masked | length);
209 } else if (length <= 65535) {
210 frame[1] = (byte) (masked | 126);
211 frame[2] = (byte) Math.floor(length / 256);
212 frame[3] = (byte) (length & BYTE);
213 } else {
214 frame[1] = (byte) (masked | 127);
215 frame[2] = (byte) (((int) Math.floor(length / Math.pow(2, 56))) & BYTE);
216 frame[3] = (byte) (((int) Math.floor(length / Math.pow(2, 48))) & BYTE);
217 frame[4] = (byte) (((int) Math.floor(length / Math.pow(2, 40))) & BYTE);
218 frame[5] = (byte) (((int) Math.floor(length / Math.pow(2, 32))) & BYTE);
219 frame[6] = (byte) (((int) Math.floor(length / Math.pow(2, 24))) & BYTE);
220 frame[7] = (byte) (((int) Math.floor(length / Math.pow(2, 16))) & BYTE);
221 frame[8] = (byte) (((int) Math.floor(length / Math.pow(2, 8))) & BYTE);
222 frame[9] = (byte) (length & BYTE);
223 }
225 if (errorCode > 0) {
226 frame[offset] = (byte) (((int) Math.floor(errorCode / 256)) & BYTE);
227 frame[offset+1] = (byte) (errorCode & BYTE);
228 }
229 System.arraycopy(buffer, 0, frame, offset + insert, buffer.length);
231 if (mMasking) {
232 byte[] mask = {
233 (byte) Math.floor(Math.random() * 256), (byte) Math.floor(Math.random() * 256),
234 (byte) Math.floor(Math.random() * 256), (byte) Math.floor(Math.random() * 256)
235 };
236 System.arraycopy(mask, 0, frame, header, mask.length);
237 mask(frame, mask, offset);
238 }
240 return frame;
241 }
243 public void ping(String message) {
244 mClient.send(frame(message, OP_PING, -1));
245 }
247 public void close(int code, String reason) {
248 if (mClosed) return;
249 mClient.send(frame(reason, OP_CLOSE, code));
250 mClosed = true;
251 }
253 private void emitFrame() throws IOException {
254 byte[] payload = mask(mPayload, mMask, 0);
255 int opcode = mOpcode;
257 if (opcode == OP_CONTINUATION) {
258 if (mMode == 0) {
259 throw new ProtocolError("Mode was not set.");
260 }
261 mBuffer.write(payload);
262 if (mFinal) {
263 byte[] message = mBuffer.toByteArray();
264 if (mMode == MODE_TEXT) {
265 mClient.getListener().onMessage(encode(message));
266 } else {
267 mClient.getListener().onMessage(message);
268 }
269 reset();
270 }
272 } else if (opcode == OP_TEXT) {
273 if (mFinal) {
274 String messageText = encode(payload);
275 mClient.getListener().onMessage(messageText);
276 } else {
277 mMode = MODE_TEXT;
278 mBuffer.write(payload);
279 }
281 } else if (opcode == OP_BINARY) {
282 if (mFinal) {
283 mClient.getListener().onMessage(payload);
284 } else {
285 mMode = MODE_BINARY;
286 mBuffer.write(payload);
287 }
289 } else if (opcode == OP_CLOSE) {
290 int code = (payload.length >= 2) ? 256 * payload[0] + payload[1] : 0;
291 String reason = (payload.length > 2) ? encode(slice(payload, 2)) : null;
292 Log.d(TAG, "Got close op! " + code + " " + reason);
293 mClient.getListener().onDisconnect(code, reason);
295 } else if (opcode == OP_PING) {
296 if (payload.length > 125) { throw new ProtocolError("Ping payload too large"); }
297 Log.d(TAG, "Sending pong!!");
298 mClient.sendFrame(frame(payload, OP_PONG, -1));
300 } else if (opcode == OP_PONG) {
301 String message = encode(payload);
302 // FIXME: Fire callback...
303 Log.d(TAG, "Got pong! " + message);
304 }
305 }
307 private void reset() {
308 mMode = 0;
309 mBuffer.reset();
310 }
312 private String encode(byte[] buffer) {
313 try {
314 return new String(buffer, "UTF-8");
315 } catch (UnsupportedEncodingException e) {
316 throw new RuntimeException(e);
317 }
318 }
320 private byte[] decode(String string) {
321 try {
322 return (string).getBytes("UTF-8");
323 } catch (UnsupportedEncodingException e) {
324 throw new RuntimeException(e);
325 }
326 }
328 private int getInteger(byte[] bytes) throws ProtocolError {
329 long i = byteArrayToLong(bytes, 0, bytes.length);
330 if (i < 0 || i > Integer.MAX_VALUE) {
331 throw new ProtocolError("Bad integer: " + i);
332 }
333 return (int) i;
334 }
336 /**
337 * Copied from AOSP Arrays.java.
338 */
339 /**
340 * Copies elements from {@code original} into a new array, from indexes start (inclusive) to
341 * end (exclusive). The original order of elements is preserved.
342 * If {@code end} is greater than {@code original.length}, the result is padded
343 * with the value {@code (byte) 0}.
344 *
345 * @param original the original array
346 * @param start the start index, inclusive
347 * @param end the end index, exclusive
348 * @return the new array
349 * @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
350 * @throws IllegalArgumentException if {@code start > end}
351 * @throws NullPointerException if {@code original == null}
352 * @since 1.6
353 */
354 private static byte[] copyOfRange(byte[] original, int start, int end) {
355 if (start > end) {
356 throw new IllegalArgumentException();
357 }
358 int originalLength = original.length;
359 if (start < 0 || start > originalLength) {
360 throw new ArrayIndexOutOfBoundsException();
361 }
362 int resultLength = end - start;
363 int copyLength = Math.min(resultLength, originalLength - start);
364 byte[] result = new byte[resultLength];
365 System.arraycopy(original, start, result, 0, copyLength);
366 return result;
367 }
369 private byte[] slice(byte[] array, int start) {
370 return copyOfRange(array, start, array.length);
371 }
373 public static class ProtocolError extends IOException {
374 public ProtocolError(String detailMessage) {
375 super(detailMessage);
376 }
377 }
379 private static long byteArrayToLong(byte[] b, int offset, int length) {
380 if (b.length < length)
381 throw new IllegalArgumentException("length must be less than or equal to b.length");
383 long value = 0;
384 for (int i = 0; i < length; i++) {
385 int shift = (length - 1 - i) * 8;
386 value += (b[i + offset] & 0x000000FF) << shift;
387 }
388 return value;
389 }
391 public static class HappyDataInputStream extends DataInputStream {
392 public HappyDataInputStream(InputStream in) {
393 super(in);
394 }
396 public byte[] readBytes(int length) throws IOException {
397 byte[] buffer = new byte[length];
399 int total = 0;
401 while (total < length) {
402 int count = read(buffer, total, length - total);
403 if (count == -1) {
404 break;
405 }
406 total += count;
407 }
409 if (total != length) {
410 throw new IOException(String.format("Read wrong number of bytes. Got: %s, Expected: %s.", total, length));
411 }
413 return buffer;
414 }
415 }
416 }