├── .gitignore ├── LICENSE ├── README ├── assets └── www │ ├── index.html │ └── js │ └── websocket.js └── src └── com └── strumsoft └── websocket └── phonegap ├── Base64.java ├── WebSocket.java └── WebSocketFactory.java /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | bin 3 | gen 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 STRUMSOFT (http://www.strumsoft.com) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Note: this plugin will be added to Phonegap-android core very soon. :) Here is the forked project: https://github.com/anismiles/phonegap-android 2 | 3 | 4 | Websocket Android Plugin with Phonegap integration 5 | -------------------------------------------------- 6 | 7 | This is a Java library that implements Websockt API (Draft-75/76) for Android platform. Library uses java.nio.* packages for 8 | efficient non-blocking evented behavior. It easily gets integrated with Phonegap framework too. 9 | 10 | Usage (native Android) 11 | ---------------------- 12 | 13 | 1. Copy Java source into your source folder. 14 | 2. Create a class, say WebSocketListener extending WebSocket (refer: com.strumsoft.websocket.WebSocket) 15 | 3. Implement following methods 16 | 1. onOpen 17 | 2. onClose 18 | 3. onMessage 19 | 4. onReconnect (WebSocketListener will try to reconnect to the server in case of connection failure) 20 | 21 | Usage (Phonegap) 22 | ---------------- 23 | 24 | 1. Copy Java source into your source folder. 25 | 2. Copy websocket.js in your assets/www/js folder 26 | 3. Attach com.strumsoft.websocket.phonegap.WebSocketFactory to WebView, like this: 27 | 28 | @Override 29 | public void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | super.loadUrl(" file:///android_asset/www/index.html "); 32 | 33 | // attach websocket factory 34 | appView.addJavascriptInterface(new WebSocketFactory(appView), "WebSocketFactory"); 35 | } 36 | 37 | 4. In your page, create a new WebSocket, and overload its method 'onmessage', 'onopen', 'onclose', like this: 38 | 39 | // new socket 40 | var socket = new WebSocket('ws://192.168.1.153:8081'); 41 | 42 | // push a message after the connection is established. 43 | socket.onopen = function() { 44 | socket.send('{ "type": "join", "game_id": "game/6"}') 45 | }; 46 | 47 | // alerts message pushed from server 48 | socket.onmessage = function(msg) { 49 | alert(JSON.stringify(msg.data)); 50 | }; 51 | 52 | // alert close event 53 | socket.onclose = function() { 54 | alert('closed'); 55 | }; 56 | 57 | 58 | ps: It doesn't support 'onerror' event, and various states as defined by WebSocket APIs yet. I am working on it. By the way, if you like the project, join the force. -------------------------------------------------------------------------------- /assets/www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | websocket-android-phonegap 5 | 6 | 7 | 8 | 9 | WebSocket test page! 10 |
11 | [code]
12 | 	// new socket
13 | 	var socket = new WebSocket('ws://122.168.196.27:8081/');
14 | 			
15 | 	// push a message after the connection is established.
16 | 	socket.onopen = function() {
17 | 		socket.send('Hello World')
18 | 	};
19 | 			
20 | 	// alerts message pushed from server
21 | 	socket.onmessage = function(msg) {
22 | 		alert(JSON.stringify(msg));
23 | 	};
24 | 			
25 | 	// alert close event
26 | 	socket.onclose = function() {
27 | 		alert('closed');
28 | 	};
29 | 
30 | [/code]
31 |   
32 | 33 | 34 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /assets/www/js/websocket.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) 3 | * Copyright (c) 2010 Strumsoft (https://strumsoft.com) 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation 7 | * files (the "Software"), to deal in the Software without 8 | * restriction, including without limitation the rights to use, 9 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following 12 | * conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | * OTHER DEALINGS IN THE SOFTWARE. 25 | * 26 | */ 27 | (function() { 28 | 29 | // window object 30 | var global = window; 31 | 32 | // WebSocket Object. All listener methods are cleaned up! 33 | var WebSocket = global.WebSocket = function(url) { 34 | // get a new websocket object from factory (check com.strumsoft.websocket.WebSocketFactory.java) 35 | this.socket = WebSocketFactory.getInstance(url); 36 | // store in registry 37 | if(this.socket) { 38 | WebSocket.store[this.socket.getId()] = this; 39 | } else { 40 | throw new Error('Websocket instantiation failed! Address might be wrong.'); 41 | } 42 | }; 43 | 44 | // private property 45 | WebSocket._keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 46 | 47 | // private method for decoding base64 48 | WebSocket._decode = function (input) { 49 | var output = ""; 50 | var chr1, chr2, chr3; 51 | var enc1, enc2, enc3, enc4; 52 | var i = 0; 53 | 54 | input = input.replace(/[^A-Za-z0-9\+\/\=]/g, ""); 55 | 56 | while (i < input.length) { 57 | 58 | enc1 = this._keyStr.indexOf(input.charAt(i++)); 59 | enc2 = this._keyStr.indexOf(input.charAt(i++)); 60 | enc3 = this._keyStr.indexOf(input.charAt(i++)); 61 | enc4 = this._keyStr.indexOf(input.charAt(i++)); 62 | 63 | chr1 = (enc1 << 2) | (enc2 >> 4); 64 | chr2 = ((enc2 & 15) << 4) | (enc3 >> 2); 65 | chr3 = ((enc3 & 3) << 6) | enc4; 66 | 67 | output = output + String.fromCharCode(chr1); 68 | 69 | if (enc3 != 64) { 70 | output = output + String.fromCharCode(chr2); 71 | } 72 | if (enc4 != 64) { 73 | output = output + String.fromCharCode(chr3); 74 | } 75 | 76 | } 77 | 78 | output = this._utf8_decode(output); 79 | 80 | return output; 81 | 82 | } 83 | 84 | // private method for UTF-8 decoding 85 | WebSocket._utf8_decode = function (utftext) { 86 | var string = ""; 87 | var i = 0; 88 | var c = c1 = c2 = 0; 89 | 90 | while ( i < utftext.length ) { 91 | 92 | c = utftext.charCodeAt(i); 93 | 94 | if (c < 128) { 95 | string += String.fromCharCode(c); 96 | i++; 97 | } 98 | else if((c > 191) && (c < 224)) { 99 | c2 = utftext.charCodeAt(i+1); 100 | string += String.fromCharCode(((c & 31) << 6) | (c2 & 63)); 101 | i += 2; 102 | } 103 | else { 104 | c2 = utftext.charCodeAt(i+1); 105 | c3 = utftext.charCodeAt(i+2); 106 | string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63)); 107 | i += 3; 108 | } 109 | 110 | } 111 | 112 | return string; 113 | } 114 | 115 | // storage to hold websocket object for later invokation of event methods 116 | WebSocket.store = {}; 117 | 118 | // static event methods to call event methods on target websocket objects 119 | WebSocket.onmessage = function (evt) { 120 | WebSocket.store[evt._target]['onmessage'].call(global, this._decode(evt._data)); 121 | } 122 | 123 | WebSocket.onopen = function (evt) { 124 | WebSocket.store[evt._target]['onopen'].call(global, evt); 125 | } 126 | 127 | WebSocket.onclose = function (evt) { 128 | WebSocket.store[evt._target]['onclose'].call(global, evt); 129 | } 130 | 131 | WebSocket.onerror = function (evt) { 132 | WebSocket.store[evt._target]['onerror'].call(global, evt); 133 | } 134 | 135 | // instance event methods 136 | WebSocket.prototype.send = function(data) { 137 | this.socket.send(data); 138 | } 139 | 140 | WebSocket.prototype.close = function() { 141 | this.socket.close(); 142 | } 143 | 144 | WebSocket.prototype.getReadyState = function() { 145 | this.socket.getReadyState(); 146 | } 147 | ///////////// Must be overloaded 148 | WebSocket.prototype.onopen = function(){ 149 | throw new Error('onopen not implemented.'); 150 | }; 151 | 152 | // alerts message pushed from server 153 | WebSocket.prototype.onmessage = function(msg){ 154 | throw new Error('onmessage not implemented.'); 155 | }; 156 | 157 | // alerts message pushed from server 158 | WebSocket.prototype.onerror = function(msg){ 159 | throw new Error('onerror not implemented.'); 160 | }; 161 | 162 | // alert close event 163 | WebSocket.prototype.onclose = function(){ 164 | throw new Error('onclose not implemented.'); 165 | }; 166 | })(); 167 | -------------------------------------------------------------------------------- /src/com/strumsoft/websocket/phonegap/Base64.java: -------------------------------------------------------------------------------- 1 | /** 2 | *

Encodes and decodes to and from Base64 notation.

3 | *

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

4 | * 5 | *

Example:

6 | * 7 | * String encoded = Base64.encode( myByteArray ); 8 | *
9 | * byte[] myByteArray = Base64.decode( encoded ); 10 | * 11 | *

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

16 | * 17 | *

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

21 | * 22 | *

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

24 | * 25 | * String encoded = Base64.encodeBytes( mybytes, Base64.GZIP | Base64.DO_BREAK_LINES ); 26 | *

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

27 | *

Also...

28 | * String encoded = Base64.encodeBytes( crazyString.getBytes() ); 29 | * 30 | * 31 | * 32 | *

33 | * Change Log: 34 | *

35 | * 135 | * 136 | *

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

143 | * 144 | * @author Robert Harder 145 | * @author rob@iharder.net 146 | * @version 2.3.7 147 | */ 148 | package com.strumsoft.phonegap.websocket; 149 | 150 | public class Base64 151 | { 152 | 153 | /* ******** P U B L I C F I E L D S ******** */ 154 | 155 | 156 | /** No options specified. Value is zero. */ 157 | public final static int NO_OPTIONS = 0; 158 | 159 | /** Specify encoding in first bit. Value is one. */ 160 | public final static int ENCODE = 1; 161 | 162 | 163 | /** Specify decoding in first bit. Value is zero. */ 164 | public final static int DECODE = 0; 165 | 166 | 167 | /** Specify that data should be gzip-compressed in second bit. Value is two. */ 168 | public final static int GZIP = 2; 169 | 170 | /** Specify that gzipped data should not be automatically gunzipped. */ 171 | public final static int DONT_GUNZIP = 4; 172 | 173 | 174 | /** Do break lines when encoding. Value is 8. */ 175 | public final static int DO_BREAK_LINES = 8; 176 | 177 | /** 178 | * Encode using Base64-like encoding that is URL- and Filename-safe as described 179 | * in Section 4 of RFC3548: 180 | * http://www.faqs.org/rfcs/rfc3548.html. 181 | * It is important to note that data encoded this way is not officially valid Base64, 182 | * or at the very least should not be called Base64 without also specifying that is 183 | * was encoded using the URL- and Filename-safe dialect. 184 | */ 185 | public final static int URL_SAFE = 16; 186 | 187 | 188 | /** 189 | * Encode using the special "ordered" dialect of Base64 described here: 190 | * http://www.faqs.org/qa/rfcc-1940.html. 191 | */ 192 | public final static int ORDERED = 32; 193 | 194 | 195 | /* ******** P R I V A T E F I E L D S ******** */ 196 | 197 | 198 | /** Maximum line length (76) of Base64 output. */ 199 | private final static int MAX_LINE_LENGTH = 76; 200 | 201 | 202 | /** The equals sign (=) as a byte. */ 203 | private final static byte EQUALS_SIGN = (byte)'='; 204 | 205 | 206 | /** The new line character (\n) as a byte. */ 207 | private final static byte NEW_LINE = (byte)'\n'; 208 | 209 | 210 | /** Preferred encoding. */ 211 | private final static String PREFERRED_ENCODING = "US-ASCII"; 212 | 213 | 214 | private final static byte WHITE_SPACE_ENC = -5; // Indicates white space in encoding 215 | private final static byte EQUALS_SIGN_ENC = -1; // Indicates equals sign in encoding 216 | 217 | 218 | /* ******** S T A N D A R D B A S E 6 4 A L P H A B E T ******** */ 219 | 220 | /** The 64 valid Base64 values. */ 221 | /* Host platform me be something funny like EBCDIC, so we hardcode these values. */ 222 | private final static byte[] _STANDARD_ALPHABET = { 223 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 224 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 225 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 226 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 227 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 228 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 229 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 230 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 231 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 232 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'+', (byte)'/' 233 | }; 234 | 235 | 236 | /** 237 | * Translates a Base64 value to either its 6-bit reconstruction value 238 | * or a negative number indicating some other meaning. 239 | **/ 240 | private final static byte[] _STANDARD_DECODABET = { 241 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 242 | -5,-5, // Whitespace: Tab and Linefeed 243 | -9,-9, // Decimal 11 - 12 244 | -5, // Whitespace: Carriage Return 245 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 246 | -9,-9,-9,-9,-9, // Decimal 27 - 31 247 | -5, // Whitespace: Space 248 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 249 | 62, // Plus sign at decimal 43 250 | -9,-9,-9, // Decimal 44 - 46 251 | 63, // Slash at decimal 47 252 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 253 | -9,-9,-9, // Decimal 58 - 60 254 | -1, // Equals sign at decimal 61 255 | -9,-9,-9, // Decimal 62 - 64 256 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 257 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 258 | -9,-9,-9,-9,-9,-9, // Decimal 91 - 96 259 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 260 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 261 | -9,-9,-9,-9,-9 // Decimal 123 - 127 262 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 263 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 264 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 265 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 266 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 267 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 268 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 269 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 270 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 271 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 272 | }; 273 | 274 | 275 | /* ******** U R L S A F E B A S E 6 4 A L P H A B E T ******** */ 276 | 277 | /** 278 | * Used in the URL- and Filename-safe dialect described in Section 4 of RFC3548: 279 | * http://www.faqs.org/rfcs/rfc3548.html. 280 | * Notice that the last two bytes become "hyphen" and "underscore" instead of "plus" and "slash." 281 | */ 282 | private final static byte[] _URL_SAFE_ALPHABET = { 283 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 284 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 285 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 286 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 287 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 288 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 289 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 290 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z', 291 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', (byte)'5', 292 | (byte)'6', (byte)'7', (byte)'8', (byte)'9', (byte)'-', (byte)'_' 293 | }; 294 | 295 | /** 296 | * Used in decoding URL- and Filename-safe dialects of Base64. 297 | */ 298 | private final static byte[] _URL_SAFE_DECODABET = { 299 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 300 | -5,-5, // Whitespace: Tab and Linefeed 301 | -9,-9, // Decimal 11 - 12 302 | -5, // Whitespace: Carriage Return 303 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 304 | -9,-9,-9,-9,-9, // Decimal 27 - 31 305 | -5, // Whitespace: Space 306 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 307 | -9, // Plus sign at decimal 43 308 | -9, // Decimal 44 309 | 62, // Minus sign at decimal 45 310 | -9, // Decimal 46 311 | -9, // Slash at decimal 47 312 | 52,53,54,55,56,57,58,59,60,61, // Numbers zero through nine 313 | -9,-9,-9, // Decimal 58 - 60 314 | -1, // Equals sign at decimal 61 315 | -9,-9,-9, // Decimal 62 - 64 316 | 0,1,2,3,4,5,6,7,8,9,10,11,12,13, // Letters 'A' through 'N' 317 | 14,15,16,17,18,19,20,21,22,23,24,25, // Letters 'O' through 'Z' 318 | -9,-9,-9,-9, // Decimal 91 - 94 319 | 63, // Underscore at decimal 95 320 | -9, // Decimal 96 321 | 26,27,28,29,30,31,32,33,34,35,36,37,38, // Letters 'a' through 'm' 322 | 39,40,41,42,43,44,45,46,47,48,49,50,51, // Letters 'n' through 'z' 323 | -9,-9,-9,-9,-9 // Decimal 123 - 127 324 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 325 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 326 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 327 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 328 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 329 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 330 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 331 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 332 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 333 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 334 | }; 335 | 336 | 337 | 338 | /* ******** O R D E R E D B A S E 6 4 A L P H A B E T ******** */ 339 | 340 | /** 341 | * I don't get the point of this technique, but someone requested it, 342 | * and it is described here: 343 | * http://www.faqs.org/qa/rfcc-1940.html. 344 | */ 345 | private final static byte[] _ORDERED_ALPHABET = { 346 | (byte)'-', 347 | (byte)'0', (byte)'1', (byte)'2', (byte)'3', (byte)'4', 348 | (byte)'5', (byte)'6', (byte)'7', (byte)'8', (byte)'9', 349 | (byte)'A', (byte)'B', (byte)'C', (byte)'D', (byte)'E', (byte)'F', (byte)'G', 350 | (byte)'H', (byte)'I', (byte)'J', (byte)'K', (byte)'L', (byte)'M', (byte)'N', 351 | (byte)'O', (byte)'P', (byte)'Q', (byte)'R', (byte)'S', (byte)'T', (byte)'U', 352 | (byte)'V', (byte)'W', (byte)'X', (byte)'Y', (byte)'Z', 353 | (byte)'_', 354 | (byte)'a', (byte)'b', (byte)'c', (byte)'d', (byte)'e', (byte)'f', (byte)'g', 355 | (byte)'h', (byte)'i', (byte)'j', (byte)'k', (byte)'l', (byte)'m', (byte)'n', 356 | (byte)'o', (byte)'p', (byte)'q', (byte)'r', (byte)'s', (byte)'t', (byte)'u', 357 | (byte)'v', (byte)'w', (byte)'x', (byte)'y', (byte)'z' 358 | }; 359 | 360 | /** 361 | * Used in decoding the "ordered" dialect of Base64. 362 | */ 363 | private final static byte[] _ORDERED_DECODABET = { 364 | -9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 0 - 8 365 | -5,-5, // Whitespace: Tab and Linefeed 366 | -9,-9, // Decimal 11 - 12 367 | -5, // Whitespace: Carriage Return 368 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 14 - 26 369 | -9,-9,-9,-9,-9, // Decimal 27 - 31 370 | -5, // Whitespace: Space 371 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 33 - 42 372 | -9, // Plus sign at decimal 43 373 | -9, // Decimal 44 374 | 0, // Minus sign at decimal 45 375 | -9, // Decimal 46 376 | -9, // Slash at decimal 47 377 | 1,2,3,4,5,6,7,8,9,10, // Numbers zero through nine 378 | -9,-9,-9, // Decimal 58 - 60 379 | -1, // Equals sign at decimal 61 380 | -9,-9,-9, // Decimal 62 - 64 381 | 11,12,13,14,15,16,17,18,19,20,21,22,23, // Letters 'A' through 'M' 382 | 24,25,26,27,28,29,30,31,32,33,34,35,36, // Letters 'N' through 'Z' 383 | -9,-9,-9,-9, // Decimal 91 - 94 384 | 37, // Underscore at decimal 95 385 | -9, // Decimal 96 386 | 38,39,40,41,42,43,44,45,46,47,48,49,50, // Letters 'a' through 'm' 387 | 51,52,53,54,55,56,57,58,59,60,61,62,63, // Letters 'n' through 'z' 388 | -9,-9,-9,-9,-9 // Decimal 123 - 127 389 | ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 390 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 391 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 392 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 393 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 394 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 395 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 396 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 397 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 398 | -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 399 | }; 400 | 401 | 402 | /* ******** D E T E R M I N E W H I C H A L H A B E T ******** */ 403 | 404 | 405 | /** 406 | * Returns one of the _SOMETHING_ALPHABET byte arrays depending on 407 | * the options specified. 408 | * It's possible, though silly, to specify ORDERED and URLSAFE 409 | * in which case one of them will be picked, though there is 410 | * no guarantee as to which one will be picked. 411 | */ 412 | private final static byte[] getAlphabet( int options ) { 413 | if ((options & URL_SAFE) == URL_SAFE) { 414 | return _URL_SAFE_ALPHABET; 415 | } else if ((options & ORDERED) == ORDERED) { 416 | return _ORDERED_ALPHABET; 417 | } else { 418 | return _STANDARD_ALPHABET; 419 | } 420 | } // end getAlphabet 421 | 422 | 423 | /** 424 | * Returns one of the _SOMETHING_DECODABET byte arrays depending on 425 | * the options specified. 426 | * It's possible, though silly, to specify ORDERED and URL_SAFE 427 | * in which case one of them will be picked, though there is 428 | * no guarantee as to which one will be picked. 429 | */ 430 | private final static byte[] getDecodabet( int options ) { 431 | if( (options & URL_SAFE) == URL_SAFE) { 432 | return _URL_SAFE_DECODABET; 433 | } else if ((options & ORDERED) == ORDERED) { 434 | return _ORDERED_DECODABET; 435 | } else { 436 | return _STANDARD_DECODABET; 437 | } 438 | } // end getAlphabet 439 | 440 | 441 | 442 | /** Defeats instantiation. */ 443 | private Base64(){} 444 | 445 | 446 | 447 | 448 | /* ******** E N C O D I N G M E T H O D S ******** */ 449 | 450 | 451 | /** 452 | * Encodes up to the first three bytes of array threeBytes 453 | * and returns a four-byte array in Base64 notation. 454 | * The actual number of significant bytes in your array is 455 | * given by numSigBytes. 456 | * The array threeBytes needs only be as big as 457 | * numSigBytes. 458 | * Code can reuse a byte array by passing a four-byte array as b4. 459 | * 460 | * @param b4 A reusable byte array to reduce array instantiation 461 | * @param threeBytes the array to convert 462 | * @param numSigBytes the number of significant bytes in your array 463 | * @return four byte array in Base64 notation. 464 | * @since 1.5.1 465 | */ 466 | private static byte[] encode3to4( byte[] b4, byte[] threeBytes, int numSigBytes, int options ) { 467 | encode3to4( threeBytes, 0, numSigBytes, b4, 0, options ); 468 | return b4; 469 | } // end encode3to4 470 | 471 | 472 | /** 473 | *

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

484 | *

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

486 | * 487 | * @param source the array to convert 488 | * @param srcOffset the index where conversion begins 489 | * @param numSigBytes the number of significant bytes in your array 490 | * @param destination the array to hold the conversion 491 | * @param destOffset the index where output will be put 492 | * @return the destination array 493 | * @since 1.3 494 | */ 495 | private static byte[] encode3to4( 496 | byte[] source, int srcOffset, int numSigBytes, 497 | byte[] destination, int destOffset, int options ) { 498 | 499 | byte[] ALPHABET = getAlphabet( options ); 500 | 501 | // 1 2 3 502 | // 01234567890123456789012345678901 Bit position 503 | // --------000000001111111122222222 Array position from threeBytes 504 | // --------| || || || | Six bit groups to index ALPHABET 505 | // >>18 >>12 >> 6 >> 0 Right shift necessary 506 | // 0x3f 0x3f 0x3f Additional AND 507 | 508 | // Create buffer with zero-padding if there are only one or two 509 | // significant bytes passed in the array. 510 | // We have to shift left 24 in order to flush out the 1's that appear 511 | // when Java treats a value as negative that is cast from a byte to an int. 512 | int inBuff = ( numSigBytes > 0 ? ((source[ srcOffset ] << 24) >>> 8) : 0 ) 513 | | ( numSigBytes > 1 ? ((source[ srcOffset + 1 ] << 24) >>> 16) : 0 ) 514 | | ( numSigBytes > 2 ? ((source[ srcOffset + 2 ] << 24) >>> 24) : 0 ); 515 | 516 | switch( numSigBytes ) 517 | { 518 | case 3: 519 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 520 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 521 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 522 | destination[ destOffset + 3 ] = ALPHABET[ (inBuff ) & 0x3f ]; 523 | return destination; 524 | 525 | case 2: 526 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 527 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 528 | destination[ destOffset + 2 ] = ALPHABET[ (inBuff >>> 6) & 0x3f ]; 529 | destination[ destOffset + 3 ] = EQUALS_SIGN; 530 | return destination; 531 | 532 | case 1: 533 | destination[ destOffset ] = ALPHABET[ (inBuff >>> 18) ]; 534 | destination[ destOffset + 1 ] = ALPHABET[ (inBuff >>> 12) & 0x3f ]; 535 | destination[ destOffset + 2 ] = EQUALS_SIGN; 536 | destination[ destOffset + 3 ] = EQUALS_SIGN; 537 | return destination; 538 | 539 | default: 540 | return destination; 541 | } // end switch 542 | } // end encode3to4 543 | 544 | 545 | 546 | /** 547 | * Performs Base64 encoding on the raw ByteBuffer, 548 | * writing it to the encoded ByteBuffer. 549 | * This is an experimental feature. Currently it does not 550 | * pass along any options (such as {@link #DO_BREAK_LINES} 551 | * or {@link #GZIP}. 552 | * 553 | * @param raw input buffer 554 | * @param encoded output buffer 555 | * @since 2.3 556 | */ 557 | public static void encode( java.nio.ByteBuffer raw, java.nio.ByteBuffer encoded ){ 558 | byte[] raw3 = new byte[3]; 559 | byte[] enc4 = new byte[4]; 560 | 561 | while( raw.hasRemaining() ){ 562 | int rem = Math.min(3,raw.remaining()); 563 | raw.get(raw3,0,rem); 564 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); 565 | encoded.put(enc4); 566 | } // end input remaining 567 | } 568 | 569 | 570 | /** 571 | * Performs Base64 encoding on the raw ByteBuffer, 572 | * writing it to the encoded CharBuffer. 573 | * This is an experimental feature. Currently it does not 574 | * pass along any options (such as {@link #DO_BREAK_LINES} 575 | * or {@link #GZIP}. 576 | * 577 | * @param raw input buffer 578 | * @param encoded output buffer 579 | * @since 2.3 580 | */ 581 | public static void encode( java.nio.ByteBuffer raw, java.nio.CharBuffer encoded ){ 582 | byte[] raw3 = new byte[3]; 583 | byte[] enc4 = new byte[4]; 584 | 585 | while( raw.hasRemaining() ){ 586 | int rem = Math.min(3,raw.remaining()); 587 | raw.get(raw3,0,rem); 588 | Base64.encode3to4(enc4, raw3, rem, Base64.NO_OPTIONS ); 589 | for( int i = 0; i < 4; i++ ){ 590 | encoded.put( (char)(enc4[i] & 0xFF) ); 591 | } 592 | } // end input remaining 593 | } 594 | 595 | 596 | 597 | 598 | /** 599 | * Serializes an object and returns the Base64-encoded 600 | * version of that serialized object. 601 | * 602 | *

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

607 | * 608 | * The object is not GZip-compressed before being encoded. 609 | * 610 | * @param serializableObject The object to encode 611 | * @return The Base64-encoded object 612 | * @throws java.io.IOException if there is an error 613 | * @throws NullPointerException if serializedObject is null 614 | * @since 1.4 615 | */ 616 | public static String encodeObject( java.io.Serializable serializableObject ) 617 | throws java.io.IOException { 618 | return encodeObject( serializableObject, NO_OPTIONS ); 619 | } // end encodeObject 620 | 621 | 622 | 623 | /** 624 | * Serializes an object and returns the Base64-encoded 625 | * version of that serialized object. 626 | * 627 | *

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

632 | * 633 | * The object is not GZip-compressed before being encoded. 634 | *

635 | * Example options:

 636 |      *   GZIP: gzip-compresses object before encoding it.
 637 |      *   DO_BREAK_LINES: break lines at 76 characters
 638 |      * 
639 | *

640 | * Example: encodeObject( myObj, Base64.GZIP ) or 641 | *

642 | * Example: encodeObject( myObj, Base64.GZIP | Base64.DO_BREAK_LINES ) 643 | * 644 | * @param serializableObject The object to encode 645 | * @param options Specified options 646 | * @return The Base64-encoded object 647 | * @see Base64#GZIP 648 | * @see Base64#DO_BREAK_LINES 649 | * @throws java.io.IOException if there is an error 650 | * @since 2.0 651 | */ 652 | public static String encodeObject( java.io.Serializable serializableObject, int options ) 653 | throws java.io.IOException { 654 | 655 | if( serializableObject == null ){ 656 | throw new NullPointerException( "Cannot serialize a null object." ); 657 | } // end if: null 658 | 659 | // Streams 660 | java.io.ByteArrayOutputStream baos = null; 661 | java.io.OutputStream b64os = null; 662 | java.util.zip.GZIPOutputStream gzos = null; 663 | java.io.ObjectOutputStream oos = null; 664 | 665 | 666 | try { 667 | // ObjectOutputStream -> (GZIP) -> Base64 -> ByteArrayOutputStream 668 | baos = new java.io.ByteArrayOutputStream(); 669 | b64os = new Base64.OutputStream( baos, ENCODE | options ); 670 | if( (options & GZIP) != 0 ){ 671 | // Gzip 672 | gzos = new java.util.zip.GZIPOutputStream(b64os); 673 | oos = new java.io.ObjectOutputStream( gzos ); 674 | } else { 675 | // Not gzipped 676 | oos = new java.io.ObjectOutputStream( b64os ); 677 | } 678 | oos.writeObject( serializableObject ); 679 | } // end try 680 | catch( java.io.IOException e ) { 681 | // Catch it and then throw it immediately so that 682 | // the finally{} block is called for cleanup. 683 | throw e; 684 | } // end catch 685 | finally { 686 | try{ oos.close(); } catch( Exception e ){} 687 | try{ gzos.close(); } catch( Exception e ){} 688 | try{ b64os.close(); } catch( Exception e ){} 689 | try{ baos.close(); } catch( Exception e ){} 690 | } // end finally 691 | 692 | // Return value according to relevant encoding. 693 | try { 694 | return new String( baos.toByteArray(), PREFERRED_ENCODING ); 695 | } // end try 696 | catch (java.io.UnsupportedEncodingException uue){ 697 | // Fall back to some Java default 698 | return new String( baos.toByteArray() ); 699 | } // end catch 700 | 701 | } // end encode 702 | 703 | 704 | 705 | /** 706 | * Encodes a byte array into Base64 notation. 707 | * Does not GZip-compress data. 708 | * 709 | * @param source The data to convert 710 | * @return The data in Base64-encoded form 711 | * @throws NullPointerException if source array is null 712 | * @since 1.4 713 | */ 714 | public static String encodeBytes( byte[] source ) { 715 | // Since we're not going to have the GZIP encoding turned on, 716 | // we're not going to have an java.io.IOException thrown, so 717 | // we should not force the user to have to catch it. 718 | String encoded = null; 719 | try { 720 | encoded = encodeBytes(source, 0, source.length, NO_OPTIONS); 721 | } catch (java.io.IOException ex) { 722 | assert false : ex.getMessage(); 723 | } // end catch 724 | assert encoded != null; 725 | return encoded; 726 | } // end encodeBytes 727 | 728 | 729 | 730 | /** 731 | * Encodes a byte array into Base64 notation. 732 | *

733 | * Example options:

 734 |      *   GZIP: gzip-compresses object before encoding it.
 735 |      *   DO_BREAK_LINES: break lines at 76 characters
 736 |      *     Note: Technically, this makes your encoding non-compliant.
 737 |      * 
738 | *

739 | * Example: encodeBytes( myData, Base64.GZIP ) or 740 | *

741 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 742 | * 743 | * 744 | *

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

748 | * 749 | * 750 | * @param source The data to convert 751 | * @param options Specified options 752 | * @return The Base64-encoded data as a String 753 | * @see Base64#GZIP 754 | * @see Base64#DO_BREAK_LINES 755 | * @throws java.io.IOException if there is an error 756 | * @throws NullPointerException if source array is null 757 | * @since 2.0 758 | */ 759 | public static String encodeBytes( byte[] source, int options ) throws java.io.IOException { 760 | return encodeBytes( source, 0, source.length, options ); 761 | } // end encodeBytes 762 | 763 | 764 | /** 765 | * Encodes a byte array into Base64 notation. 766 | * Does not GZip-compress data. 767 | * 768 | *

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

772 | * 773 | * 774 | * @param source The data to convert 775 | * @param off Offset in array where conversion should begin 776 | * @param len Length of data to convert 777 | * @return The Base64-encoded data as a String 778 | * @throws NullPointerException if source array is null 779 | * @throws IllegalArgumentException if source array, offset, or length are invalid 780 | * @since 1.4 781 | */ 782 | public static String encodeBytes( byte[] source, int off, int len ) { 783 | // Since we're not going to have the GZIP encoding turned on, 784 | // we're not going to have an java.io.IOException thrown, so 785 | // we should not force the user to have to catch it. 786 | String encoded = null; 787 | try { 788 | encoded = encodeBytes( source, off, len, NO_OPTIONS ); 789 | } catch (java.io.IOException ex) { 790 | assert false : ex.getMessage(); 791 | } // end catch 792 | assert encoded != null; 793 | return encoded; 794 | } // end encodeBytes 795 | 796 | 797 | 798 | /** 799 | * Encodes a byte array into Base64 notation. 800 | *

801 | * Example options:

 802 |      *   GZIP: gzip-compresses object before encoding it.
 803 |      *   DO_BREAK_LINES: break lines at 76 characters
 804 |      *     Note: Technically, this makes your encoding non-compliant.
 805 |      * 
806 | *

807 | * Example: encodeBytes( myData, Base64.GZIP ) or 808 | *

809 | * Example: encodeBytes( myData, Base64.GZIP | Base64.DO_BREAK_LINES ) 810 | * 811 | * 812 | *

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

816 | * 817 | * 818 | * @param source The data to convert 819 | * @param off Offset in array where conversion should begin 820 | * @param len Length of data to convert 821 | * @param options Specified options 822 | * @return The Base64-encoded data as a String 823 | * @see Base64#GZIP 824 | * @see Base64#DO_BREAK_LINES 825 | * @throws java.io.IOException if there is an error 826 | * @throws NullPointerException if source array is null 827 | * @throws IllegalArgumentException if source array, offset, or length are invalid 828 | * @since 2.0 829 | */ 830 | public static String encodeBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 831 | byte[] encoded = encodeBytesToBytes( source, off, len, options ); 832 | 833 | // Return value according to relevant encoding. 834 | try { 835 | return new String( encoded, PREFERRED_ENCODING ); 836 | } // end try 837 | catch (java.io.UnsupportedEncodingException uue) { 838 | return new String( encoded ); 839 | } // end catch 840 | 841 | } // end encodeBytes 842 | 843 | 844 | 845 | 846 | /** 847 | * Similar to {@link #encodeBytes(byte[])} but returns 848 | * a byte array instead of instantiating a String. This is more efficient 849 | * if you're working with I/O streams and have large data sets to encode. 850 | * 851 | * 852 | * @param source The data to convert 853 | * @return The Base64-encoded data as a byte[] (of ASCII characters) 854 | * @throws NullPointerException if source array is null 855 | * @since 2.3.1 856 | */ 857 | public static byte[] encodeBytesToBytes( byte[] source ) { 858 | byte[] encoded = null; 859 | try { 860 | encoded = encodeBytesToBytes( source, 0, source.length, Base64.NO_OPTIONS ); 861 | } catch( java.io.IOException ex ) { 862 | assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 863 | } 864 | return encoded; 865 | } 866 | 867 | 868 | /** 869 | * Similar to {@link #encodeBytes(byte[], int, int, int)} but returns 870 | * a byte array instead of instantiating a String. This is more efficient 871 | * if you're working with I/O streams and have large data sets to encode. 872 | * 873 | * 874 | * @param source The data to convert 875 | * @param off Offset in array where conversion should begin 876 | * @param len Length of data to convert 877 | * @param options Specified options 878 | * @return The Base64-encoded data as a String 879 | * @see Base64#GZIP 880 | * @see Base64#DO_BREAK_LINES 881 | * @throws java.io.IOException if there is an error 882 | * @throws NullPointerException if source array is null 883 | * @throws IllegalArgumentException if source array, offset, or length are invalid 884 | * @since 2.3.1 885 | */ 886 | public static byte[] encodeBytesToBytes( byte[] source, int off, int len, int options ) throws java.io.IOException { 887 | 888 | if( source == null ){ 889 | throw new NullPointerException( "Cannot serialize a null array." ); 890 | } // end if: null 891 | 892 | if( off < 0 ){ 893 | throw new IllegalArgumentException( "Cannot have negative offset: " + off ); 894 | } // end if: off < 0 895 | 896 | if( len < 0 ){ 897 | throw new IllegalArgumentException( "Cannot have length offset: " + len ); 898 | } // end if: len < 0 899 | 900 | if( off + len > source.length ){ 901 | throw new IllegalArgumentException( 902 | String.format( "Cannot have offset of %d and length of %d with array of length %d", off,len,source.length)); 903 | } // end if: off < 0 904 | 905 | 906 | 907 | // Compress? 908 | if( (options & GZIP) != 0 ) { 909 | java.io.ByteArrayOutputStream baos = null; 910 | java.util.zip.GZIPOutputStream gzos = null; 911 | Base64.OutputStream b64os = null; 912 | 913 | try { 914 | // GZip -> Base64 -> ByteArray 915 | baos = new java.io.ByteArrayOutputStream(); 916 | b64os = new Base64.OutputStream( baos, ENCODE | options ); 917 | gzos = new java.util.zip.GZIPOutputStream( b64os ); 918 | 919 | gzos.write( source, off, len ); 920 | gzos.close(); 921 | } // end try 922 | catch( java.io.IOException e ) { 923 | // Catch it and then throw it immediately so that 924 | // the finally{} block is called for cleanup. 925 | throw e; 926 | } // end catch 927 | finally { 928 | try{ gzos.close(); } catch( Exception e ){} 929 | try{ b64os.close(); } catch( Exception e ){} 930 | try{ baos.close(); } catch( Exception e ){} 931 | } // end finally 932 | 933 | return baos.toByteArray(); 934 | } // end if: compress 935 | 936 | // Else, don't compress. Better not to use streams at all then. 937 | else { 938 | boolean breakLines = (options & DO_BREAK_LINES) != 0; 939 | 940 | //int len43 = len * 4 / 3; 941 | //byte[] outBuff = new byte[ ( len43 ) // Main 4:3 942 | // + ( (len % 3) > 0 ? 4 : 0 ) // Account for padding 943 | // + (breakLines ? ( len43 / MAX_LINE_LENGTH ) : 0) ]; // New lines 944 | // Try to determine more precisely how big the array needs to be. 945 | // If we get it right, we don't have to do an array copy, and 946 | // we save a bunch of memory. 947 | int encLen = ( len / 3 ) * 4 + ( len % 3 > 0 ? 4 : 0 ); // Bytes needed for actual encoding 948 | if( breakLines ){ 949 | encLen += encLen / MAX_LINE_LENGTH; // Plus extra newline characters 950 | } 951 | byte[] outBuff = new byte[ encLen ]; 952 | 953 | 954 | int d = 0; 955 | int e = 0; 956 | int len2 = len - 2; 957 | int lineLength = 0; 958 | for( ; d < len2; d+=3, e+=4 ) { 959 | encode3to4( source, d+off, 3, outBuff, e, options ); 960 | 961 | lineLength += 4; 962 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) 963 | { 964 | outBuff[e+4] = NEW_LINE; 965 | e++; 966 | lineLength = 0; 967 | } // end if: end of line 968 | } // en dfor: each piece of array 969 | 970 | if( d < len ) { 971 | encode3to4( source, d+off, len - d, outBuff, e, options ); 972 | e += 4; 973 | } // end if: some padding needed 974 | 975 | 976 | // Only resize array if we didn't guess it right. 977 | if( e <= outBuff.length - 1 ){ 978 | // If breaking lines and the last byte falls right at 979 | // the line length (76 bytes per line), there will be 980 | // one extra byte, and the array will need to be resized. 981 | // Not too bad of an estimate on array size, I'd say. 982 | byte[] finalOut = new byte[e]; 983 | System.arraycopy(outBuff,0, finalOut,0,e); 984 | //System.err.println("Having to resize array from " + outBuff.length + " to " + e ); 985 | return finalOut; 986 | } else { 987 | //System.err.println("No need to resize array."); 988 | return outBuff; 989 | } 990 | 991 | } // end else: don't compress 992 | 993 | } // end encodeBytesToBytes 994 | 995 | 996 | 997 | 998 | 999 | /* ******** D E C O D I N G M E T H O D S ******** */ 1000 | 1001 | 1002 | /** 1003 | * Decodes four bytes from array source 1004 | * and writes the resulting bytes (up to three of them) 1005 | * to destination. 1006 | * The source and destination arrays can be manipulated 1007 | * anywhere along their length by specifying 1008 | * srcOffset and destOffset. 1009 | * This method does not check to make sure your arrays 1010 | * are large enough to accomodate srcOffset + 4 for 1011 | * the source array or destOffset + 3 for 1012 | * the destination array. 1013 | * This method returns the actual number of bytes that 1014 | * were converted from the Base64 encoding. 1015 | *

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

1017 | * 1018 | * 1019 | * @param source the array to convert 1020 | * @param srcOffset the index where conversion begins 1021 | * @param destination the array to hold the conversion 1022 | * @param destOffset the index where output will be put 1023 | * @param options alphabet type is pulled from this (standard, url-safe, ordered) 1024 | * @return the number of decoded bytes converted 1025 | * @throws NullPointerException if source or destination arrays are null 1026 | * @throws IllegalArgumentException if srcOffset or destOffset are invalid 1027 | * or there is not enough room in the array. 1028 | * @since 1.3 1029 | */ 1030 | private static int decode4to3( 1031 | byte[] source, int srcOffset, 1032 | byte[] destination, int destOffset, int options ) { 1033 | 1034 | // Lots of error checking and exception throwing 1035 | if( source == null ){ 1036 | throw new NullPointerException( "Source array was null." ); 1037 | } // end if 1038 | if( destination == null ){ 1039 | throw new NullPointerException( "Destination array was null." ); 1040 | } // end if 1041 | if( srcOffset < 0 || srcOffset + 3 >= source.length ){ 1042 | throw new IllegalArgumentException( String.format( 1043 | "Source array with length %d cannot have offset of %d and still process four bytes.", source.length, srcOffset ) ); 1044 | } // end if 1045 | if( destOffset < 0 || destOffset +2 >= destination.length ){ 1046 | throw new IllegalArgumentException( String.format( 1047 | "Destination array with length %d cannot have offset of %d and still store three bytes.", destination.length, destOffset ) ); 1048 | } // end if 1049 | 1050 | 1051 | byte[] DECODABET = getDecodabet( options ); 1052 | 1053 | // Example: Dk== 1054 | if( source[ srcOffset + 2] == EQUALS_SIGN ) { 1055 | // Two ways to do the same thing. Don't know which way I like best. 1056 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1057 | // | ( ( DECODABET[ source[ srcOffset + 1] ] << 24 ) >>> 12 ); 1058 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1059 | | ( ( DECODABET[ source[ srcOffset + 1] ] & 0xFF ) << 12 ); 1060 | 1061 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1062 | return 1; 1063 | } 1064 | 1065 | // Example: DkL= 1066 | else if( source[ srcOffset + 3 ] == EQUALS_SIGN ) { 1067 | // Two ways to do the same thing. Don't know which way I like best. 1068 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1069 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1070 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ); 1071 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1072 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1073 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6 ); 1074 | 1075 | destination[ destOffset ] = (byte)( outBuff >>> 16 ); 1076 | destination[ destOffset + 1 ] = (byte)( outBuff >>> 8 ); 1077 | return 2; 1078 | } 1079 | 1080 | // Example: DkLE 1081 | else { 1082 | // Two ways to do the same thing. Don't know which way I like best. 1083 | //int outBuff = ( ( DECODABET[ source[ srcOffset ] ] << 24 ) >>> 6 ) 1084 | // | ( ( DECODABET[ source[ srcOffset + 1 ] ] << 24 ) >>> 12 ) 1085 | // | ( ( DECODABET[ source[ srcOffset + 2 ] ] << 24 ) >>> 18 ) 1086 | // | ( ( DECODABET[ source[ srcOffset + 3 ] ] << 24 ) >>> 24 ); 1087 | int outBuff = ( ( DECODABET[ source[ srcOffset ] ] & 0xFF ) << 18 ) 1088 | | ( ( DECODABET[ source[ srcOffset + 1 ] ] & 0xFF ) << 12 ) 1089 | | ( ( DECODABET[ source[ srcOffset + 2 ] ] & 0xFF ) << 6) 1090 | | ( ( DECODABET[ source[ srcOffset + 3 ] ] & 0xFF ) ); 1091 | 1092 | 1093 | destination[ destOffset ] = (byte)( outBuff >> 16 ); 1094 | destination[ destOffset + 1 ] = (byte)( outBuff >> 8 ); 1095 | destination[ destOffset + 2 ] = (byte)( outBuff ); 1096 | 1097 | return 3; 1098 | } 1099 | } // end decodeToBytes 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | /** 1106 | * Low-level access to decoding ASCII characters in 1107 | * the form of a byte array. Ignores GUNZIP option, if 1108 | * it's set. This is not generally a recommended method, 1109 | * although it is used internally as part of the decoding process. 1110 | * Special case: if len = 0, an empty array is returned. Still, 1111 | * if you need more speed and reduced memory footprint (and aren't 1112 | * gzipping), consider this method. 1113 | * 1114 | * @param source The Base64 encoded data 1115 | * @return decoded data 1116 | * @since 2.3.1 1117 | */ 1118 | public static byte[] decode( byte[] source ) 1119 | throws java.io.IOException { 1120 | byte[] decoded = null; 1121 | // try { 1122 | decoded = decode( source, 0, source.length, Base64.NO_OPTIONS ); 1123 | // } catch( java.io.IOException ex ) { 1124 | // assert false : "IOExceptions only come from GZipping, which is turned off: " + ex.getMessage(); 1125 | // } 1126 | return decoded; 1127 | } 1128 | 1129 | 1130 | 1131 | /** 1132 | * Low-level access to decoding ASCII characters in 1133 | * the form of a byte array. Ignores GUNZIP option, if 1134 | * it's set. This is not generally a recommended method, 1135 | * although it is used internally as part of the decoding process. 1136 | * Special case: if len = 0, an empty array is returned. Still, 1137 | * if you need more speed and reduced memory footprint (and aren't 1138 | * gzipping), consider this method. 1139 | * 1140 | * @param source The Base64 encoded data 1141 | * @param off The offset of where to begin decoding 1142 | * @param len The length of characters to decode 1143 | * @param options Can specify options such as alphabet type to use 1144 | * @return decoded data 1145 | * @throws java.io.IOException If bogus characters exist in source data 1146 | * @since 1.3 1147 | */ 1148 | public static byte[] decode( byte[] source, int off, int len, int options ) 1149 | throws java.io.IOException { 1150 | 1151 | // Lots of error checking and exception throwing 1152 | if( source == null ){ 1153 | throw new NullPointerException( "Cannot decode null source array." ); 1154 | } // end if 1155 | if( off < 0 || off + len > source.length ){ 1156 | throw new IllegalArgumentException( String.format( 1157 | "Source array with length %d cannot have offset of %d and process %d bytes.", source.length, off, len ) ); 1158 | } // end if 1159 | 1160 | if( len == 0 ){ 1161 | return new byte[0]; 1162 | }else if( len < 4 ){ 1163 | throw new IllegalArgumentException( 1164 | "Base64-encoded string must have at least four characters, but length specified was " + len ); 1165 | } // end if 1166 | 1167 | byte[] DECODABET = getDecodabet( options ); 1168 | 1169 | int len34 = len * 3 / 4; // Estimate on array size 1170 | byte[] outBuff = new byte[ len34 ]; // Upper limit on size of output 1171 | int outBuffPosn = 0; // Keep track of where we're writing 1172 | 1173 | byte[] b4 = new byte[4]; // Four byte buffer from source, eliminating white space 1174 | int b4Posn = 0; // Keep track of four byte input buffer 1175 | int i = 0; // Source array counter 1176 | byte sbiDecode = 0; // Special value from DECODABET 1177 | 1178 | for( i = off; i < off+len; i++ ) { // Loop through source 1179 | 1180 | sbiDecode = DECODABET[ source[i]&0xFF ]; 1181 | 1182 | // White space, Equals sign, or legit Base64 character 1183 | // Note the values such as -5 and -9 in the 1184 | // DECODABETs at the top of the file. 1185 | if( sbiDecode >= WHITE_SPACE_ENC ) { 1186 | if( sbiDecode >= EQUALS_SIGN_ENC ) { 1187 | b4[ b4Posn++ ] = source[i]; // Save non-whitespace 1188 | if( b4Posn > 3 ) { // Time to decode? 1189 | outBuffPosn += decode4to3( b4, 0, outBuff, outBuffPosn, options ); 1190 | b4Posn = 0; 1191 | 1192 | // If that was the equals sign, break out of 'for' loop 1193 | if( source[i] == EQUALS_SIGN ) { 1194 | break; 1195 | } // end if: equals sign 1196 | } // end if: quartet built 1197 | } // end if: equals sign or better 1198 | } // end if: white space, equals sign or better 1199 | else { 1200 | // There's a bad input character in the Base64 stream. 1201 | throw new java.io.IOException( String.format( 1202 | "Bad Base64 input character decimal %d in array position %d", ((int)source[i])&0xFF, i ) ); 1203 | } // end else: 1204 | } // each input character 1205 | 1206 | byte[] out = new byte[ outBuffPosn ]; 1207 | System.arraycopy( outBuff, 0, out, 0, outBuffPosn ); 1208 | return out; 1209 | } // end decode 1210 | 1211 | 1212 | 1213 | 1214 | /** 1215 | * Decodes data from Base64 notation, automatically 1216 | * detecting gzip-compressed data and decompressing it. 1217 | * 1218 | * @param s the string to decode 1219 | * @return the decoded data 1220 | * @throws java.io.IOException If there is a problem 1221 | * @since 1.4 1222 | */ 1223 | public static byte[] decode( String s ) throws java.io.IOException { 1224 | return decode( s, NO_OPTIONS ); 1225 | } 1226 | 1227 | 1228 | 1229 | /** 1230 | * Decodes data from Base64 notation, automatically 1231 | * detecting gzip-compressed data and decompressing it. 1232 | * 1233 | * @param s the string to decode 1234 | * @param options encode options such as URL_SAFE 1235 | * @return the decoded data 1236 | * @throws java.io.IOException if there is an error 1237 | * @throws NullPointerException if s is null 1238 | * @since 1.4 1239 | */ 1240 | public static byte[] decode( String s, int options ) throws java.io.IOException { 1241 | 1242 | if( s == null ){ 1243 | throw new NullPointerException( "Input string was null." ); 1244 | } // end if 1245 | 1246 | byte[] bytes; 1247 | try { 1248 | bytes = s.getBytes( PREFERRED_ENCODING ); 1249 | } // end try 1250 | catch( java.io.UnsupportedEncodingException uee ) { 1251 | bytes = s.getBytes(); 1252 | } // end catch 1253 | // 1254 | 1255 | // Decode 1256 | bytes = decode( bytes, 0, bytes.length, options ); 1257 | 1258 | // Check to see if it's gzip-compressed 1259 | // GZIP Magic Two-Byte Number: 0x8b1f (35615) 1260 | boolean dontGunzip = (options & DONT_GUNZIP) != 0; 1261 | if( (bytes != null) && (bytes.length >= 4) && (!dontGunzip) ) { 1262 | 1263 | int head = ((int)bytes[0] & 0xff) | ((bytes[1] << 8) & 0xff00); 1264 | if( java.util.zip.GZIPInputStream.GZIP_MAGIC == head ) { 1265 | java.io.ByteArrayInputStream bais = null; 1266 | java.util.zip.GZIPInputStream gzis = null; 1267 | java.io.ByteArrayOutputStream baos = null; 1268 | byte[] buffer = new byte[2048]; 1269 | int length = 0; 1270 | 1271 | try { 1272 | baos = new java.io.ByteArrayOutputStream(); 1273 | bais = new java.io.ByteArrayInputStream( bytes ); 1274 | gzis = new java.util.zip.GZIPInputStream( bais ); 1275 | 1276 | while( ( length = gzis.read( buffer ) ) >= 0 ) { 1277 | baos.write(buffer,0,length); 1278 | } // end while: reading input 1279 | 1280 | // No error? Get new bytes. 1281 | bytes = baos.toByteArray(); 1282 | 1283 | } // end try 1284 | catch( java.io.IOException e ) { 1285 | e.printStackTrace(); 1286 | // Just return originally-decoded bytes 1287 | } // end catch 1288 | finally { 1289 | try{ baos.close(); } catch( Exception e ){} 1290 | try{ gzis.close(); } catch( Exception e ){} 1291 | try{ bais.close(); } catch( Exception e ){} 1292 | } // end finally 1293 | 1294 | } // end if: gzipped 1295 | } // end if: bytes.length >= 2 1296 | 1297 | return bytes; 1298 | } // end decode 1299 | 1300 | 1301 | 1302 | /** 1303 | * Attempts to decode Base64 data and deserialize a Java 1304 | * Object within. Returns null if there was an error. 1305 | * 1306 | * @param encodedObject The Base64 data to decode 1307 | * @return The decoded and deserialized object 1308 | * @throws NullPointerException if encodedObject is null 1309 | * @throws java.io.IOException if there is a general error 1310 | * @throws ClassNotFoundException if the decoded object is of a 1311 | * class that cannot be found by the JVM 1312 | * @since 1.5 1313 | */ 1314 | public static Object decodeToObject( String encodedObject ) 1315 | throws java.io.IOException, java.lang.ClassNotFoundException { 1316 | return decodeToObject(encodedObject,NO_OPTIONS,null); 1317 | } 1318 | 1319 | 1320 | /** 1321 | * Attempts to decode Base64 data and deserialize a Java 1322 | * Object within. Returns null if there was an error. 1323 | * If loader is not null, it will be the class loader 1324 | * used when deserializing. 1325 | * 1326 | * @param encodedObject The Base64 data to decode 1327 | * @param options Various parameters related to decoding 1328 | * @param loader Optional class loader to use in deserializing classes. 1329 | * @return The decoded and deserialized object 1330 | * @throws NullPointerException if encodedObject is null 1331 | * @throws java.io.IOException if there is a general error 1332 | * @throws ClassNotFoundException if the decoded object is of a 1333 | * class that cannot be found by the JVM 1334 | * @since 2.3.4 1335 | */ 1336 | public static Object decodeToObject( 1337 | String encodedObject, int options, final ClassLoader loader ) 1338 | throws java.io.IOException, java.lang.ClassNotFoundException { 1339 | 1340 | // Decode and gunzip if necessary 1341 | byte[] objBytes = decode( encodedObject, options ); 1342 | 1343 | java.io.ByteArrayInputStream bais = null; 1344 | java.io.ObjectInputStream ois = null; 1345 | Object obj = null; 1346 | 1347 | try { 1348 | bais = new java.io.ByteArrayInputStream( objBytes ); 1349 | 1350 | // If no custom class loader is provided, use Java's builtin OIS. 1351 | if( loader == null ){ 1352 | ois = new java.io.ObjectInputStream( bais ); 1353 | } // end if: no loader provided 1354 | 1355 | // Else make a customized object input stream that uses 1356 | // the provided class loader. 1357 | else { 1358 | ois = new java.io.ObjectInputStream(bais){ 1359 | @Override 1360 | public Class resolveClass(java.io.ObjectStreamClass streamClass) 1361 | throws java.io.IOException, ClassNotFoundException { 1362 | Class c = Class.forName(streamClass.getName(), false, loader); 1363 | if( c == null ){ 1364 | return super.resolveClass(streamClass); 1365 | } else { 1366 | return c; // Class loader knows of this class. 1367 | } // end else: not null 1368 | } // end resolveClass 1369 | }; // end ois 1370 | } // end else: no custom class loader 1371 | 1372 | obj = ois.readObject(); 1373 | } // end try 1374 | catch( java.io.IOException e ) { 1375 | throw e; // Catch and throw in order to execute finally{} 1376 | } // end catch 1377 | catch( java.lang.ClassNotFoundException e ) { 1378 | throw e; // Catch and throw in order to execute finally{} 1379 | } // end catch 1380 | finally { 1381 | try{ bais.close(); } catch( Exception e ){} 1382 | try{ ois.close(); } catch( Exception e ){} 1383 | } // end finally 1384 | 1385 | return obj; 1386 | } // end decodeObject 1387 | 1388 | 1389 | 1390 | /** 1391 | * Convenience method for encoding data to a file. 1392 | * 1393 | *

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

1397 | * 1398 | * @param dataToEncode byte array of data to encode in base64 form 1399 | * @param filename Filename for saving encoded data 1400 | * @throws java.io.IOException if there is an error 1401 | * @throws NullPointerException if dataToEncode is null 1402 | * @since 2.1 1403 | */ 1404 | public static void encodeToFile( byte[] dataToEncode, String filename ) 1405 | throws java.io.IOException { 1406 | 1407 | if( dataToEncode == null ){ 1408 | throw new NullPointerException( "Data to encode was null." ); 1409 | } // end iff 1410 | 1411 | Base64.OutputStream bos = null; 1412 | try { 1413 | bos = new Base64.OutputStream( 1414 | new java.io.FileOutputStream( filename ), Base64.ENCODE ); 1415 | bos.write( dataToEncode ); 1416 | } // end try 1417 | catch( java.io.IOException e ) { 1418 | throw e; // Catch and throw to execute finally{} block 1419 | } // end catch: java.io.IOException 1420 | finally { 1421 | try{ bos.close(); } catch( Exception e ){} 1422 | } // end finally 1423 | 1424 | } // end encodeToFile 1425 | 1426 | 1427 | /** 1428 | * Convenience method for decoding data to a file. 1429 | * 1430 | *

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

1434 | * 1435 | * @param dataToDecode Base64-encoded data as a string 1436 | * @param filename Filename for saving decoded data 1437 | * @throws java.io.IOException if there is an error 1438 | * @since 2.1 1439 | */ 1440 | public static void decodeToFile( String dataToDecode, String filename ) 1441 | throws java.io.IOException { 1442 | 1443 | Base64.OutputStream bos = null; 1444 | try{ 1445 | bos = new Base64.OutputStream( 1446 | new java.io.FileOutputStream( filename ), Base64.DECODE ); 1447 | bos.write( dataToDecode.getBytes( PREFERRED_ENCODING ) ); 1448 | } // end try 1449 | catch( java.io.IOException e ) { 1450 | throw e; // Catch and throw to execute finally{} block 1451 | } // end catch: java.io.IOException 1452 | finally { 1453 | try{ bos.close(); } catch( Exception e ){} 1454 | } // end finally 1455 | 1456 | } // end decodeToFile 1457 | 1458 | 1459 | 1460 | 1461 | /** 1462 | * Convenience method for reading a base64-encoded 1463 | * file and decoding it. 1464 | * 1465 | *

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

1469 | * 1470 | * @param filename Filename for reading encoded data 1471 | * @return decoded byte array 1472 | * @throws java.io.IOException if there is an error 1473 | * @since 2.1 1474 | */ 1475 | public static byte[] decodeFromFile( String filename ) 1476 | throws java.io.IOException { 1477 | 1478 | byte[] decodedData = null; 1479 | Base64.InputStream bis = null; 1480 | try 1481 | { 1482 | // Set up some useful variables 1483 | java.io.File file = new java.io.File( filename ); 1484 | byte[] buffer = null; 1485 | int length = 0; 1486 | int numBytes = 0; 1487 | 1488 | // Check for size of file 1489 | if( file.length() > Integer.MAX_VALUE ) 1490 | { 1491 | throw new java.io.IOException( "File is too big for this convenience method (" + file.length() + " bytes)." ); 1492 | } // end if: file too big for int index 1493 | buffer = new byte[ (int)file.length() ]; 1494 | 1495 | // Open a stream 1496 | bis = new Base64.InputStream( 1497 | new java.io.BufferedInputStream( 1498 | new java.io.FileInputStream( file ) ), Base64.DECODE ); 1499 | 1500 | // Read until done 1501 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1502 | length += numBytes; 1503 | } // end while 1504 | 1505 | // Save in a variable to return 1506 | decodedData = new byte[ length ]; 1507 | System.arraycopy( buffer, 0, decodedData, 0, length ); 1508 | 1509 | } // end try 1510 | catch( java.io.IOException e ) { 1511 | throw e; // Catch and release to execute finally{} 1512 | } // end catch: java.io.IOException 1513 | finally { 1514 | try{ bis.close(); } catch( Exception e) {} 1515 | } // end finally 1516 | 1517 | return decodedData; 1518 | } // end decodeFromFile 1519 | 1520 | 1521 | 1522 | /** 1523 | * Convenience method for reading a binary file 1524 | * and base64-encoding it. 1525 | * 1526 | *

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

1530 | * 1531 | * @param filename Filename for reading binary data 1532 | * @return base64-encoded string 1533 | * @throws java.io.IOException if there is an error 1534 | * @since 2.1 1535 | */ 1536 | public static String encodeFromFile( String filename ) 1537 | throws java.io.IOException { 1538 | 1539 | String encodedData = null; 1540 | Base64.InputStream bis = null; 1541 | try 1542 | { 1543 | // Set up some useful variables 1544 | java.io.File file = new java.io.File( filename ); 1545 | 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) 1546 | int length = 0; 1547 | int numBytes = 0; 1548 | 1549 | // Open a stream 1550 | bis = new Base64.InputStream( 1551 | new java.io.BufferedInputStream( 1552 | new java.io.FileInputStream( file ) ), Base64.ENCODE ); 1553 | 1554 | // Read until done 1555 | while( ( numBytes = bis.read( buffer, length, 4096 ) ) >= 0 ) { 1556 | length += numBytes; 1557 | } // end while 1558 | 1559 | // Save in a variable to return 1560 | encodedData = new String( buffer, 0, length, Base64.PREFERRED_ENCODING ); 1561 | 1562 | } // end try 1563 | catch( java.io.IOException e ) { 1564 | throw e; // Catch and release to execute finally{} 1565 | } // end catch: java.io.IOException 1566 | finally { 1567 | try{ bis.close(); } catch( Exception e) {} 1568 | } // end finally 1569 | 1570 | return encodedData; 1571 | } // end encodeFromFile 1572 | 1573 | /** 1574 | * Reads infile and encodes it to outfile. 1575 | * 1576 | * @param infile Input file 1577 | * @param outfile Output file 1578 | * @throws java.io.IOException if there is an error 1579 | * @since 2.2 1580 | */ 1581 | public static void encodeFileToFile( String infile, String outfile ) 1582 | throws java.io.IOException { 1583 | 1584 | String encoded = Base64.encodeFromFile( infile ); 1585 | java.io.OutputStream out = null; 1586 | try{ 1587 | out = new java.io.BufferedOutputStream( 1588 | new java.io.FileOutputStream( outfile ) ); 1589 | out.write( encoded.getBytes("US-ASCII") ); // Strict, 7-bit output. 1590 | } // end try 1591 | catch( java.io.IOException e ) { 1592 | throw e; // Catch and release to execute finally{} 1593 | } // end catch 1594 | finally { 1595 | try { out.close(); } 1596 | catch( Exception ex ){} 1597 | } // end finally 1598 | } // end encodeFileToFile 1599 | 1600 | 1601 | /** 1602 | * Reads infile and decodes it to outfile. 1603 | * 1604 | * @param infile Input file 1605 | * @param outfile Output file 1606 | * @throws java.io.IOException if there is an error 1607 | * @since 2.2 1608 | */ 1609 | public static void decodeFileToFile( String infile, String outfile ) 1610 | throws java.io.IOException { 1611 | 1612 | byte[] decoded = Base64.decodeFromFile( infile ); 1613 | java.io.OutputStream out = null; 1614 | try{ 1615 | out = new java.io.BufferedOutputStream( 1616 | new java.io.FileOutputStream( outfile ) ); 1617 | out.write( decoded ); 1618 | } // end try 1619 | catch( java.io.IOException e ) { 1620 | throw e; // Catch and release to execute finally{} 1621 | } // end catch 1622 | finally { 1623 | try { out.close(); } 1624 | catch( Exception ex ){} 1625 | } // end finally 1626 | } // end decodeFileToFile 1627 | 1628 | 1629 | /* ******** I N N E R C L A S S I N P U T S T R E A M ******** */ 1630 | 1631 | 1632 | 1633 | /** 1634 | * A {@link Base64.InputStream} will read data from another 1635 | * java.io.InputStream, given in the constructor, 1636 | * and encode/decode to/from Base64 notation on the fly. 1637 | * 1638 | * @see Base64 1639 | * @since 1.3 1640 | */ 1641 | public static class InputStream extends java.io.FilterInputStream { 1642 | 1643 | private boolean encode; // Encoding or decoding 1644 | private int position; // Current position in the buffer 1645 | private byte[] buffer; // Small buffer holding converted data 1646 | private int bufferLength; // Length of buffer (3 or 4) 1647 | private int numSigBytes; // Number of meaningful bytes in the buffer 1648 | private int lineLength; 1649 | private boolean breakLines; // Break lines at less than 80 characters 1650 | private int options; // Record options used to create the stream. 1651 | private byte[] decodabet; // Local copies to avoid extra method calls 1652 | 1653 | 1654 | /** 1655 | * Constructs a {@link Base64.InputStream} in DECODE mode. 1656 | * 1657 | * @param in the java.io.InputStream from which to read data. 1658 | * @since 1.3 1659 | */ 1660 | public InputStream( java.io.InputStream in ) { 1661 | this( in, DECODE ); 1662 | } // end constructor 1663 | 1664 | 1665 | /** 1666 | * Constructs a {@link Base64.InputStream} in 1667 | * either ENCODE or DECODE mode. 1668 | *

1669 | * Valid options:

1670 |          *   ENCODE or DECODE: Encode or Decode as data is read.
1671 |          *   DO_BREAK_LINES: break lines at 76 characters
1672 |          *     (only meaningful when encoding)
1673 |          * 
1674 | *

1675 | * Example: new Base64.InputStream( in, Base64.DECODE ) 1676 | * 1677 | * 1678 | * @param in the java.io.InputStream from which to read data. 1679 | * @param options Specified options 1680 | * @see Base64#ENCODE 1681 | * @see Base64#DECODE 1682 | * @see Base64#DO_BREAK_LINES 1683 | * @since 2.0 1684 | */ 1685 | public InputStream( java.io.InputStream in, int options ) { 1686 | 1687 | super( in ); 1688 | this.options = options; // Record for later 1689 | this.breakLines = (options & DO_BREAK_LINES) > 0; 1690 | this.encode = (options & ENCODE) > 0; 1691 | this.bufferLength = encode ? 4 : 3; 1692 | this.buffer = new byte[ bufferLength ]; 1693 | this.position = -1; 1694 | this.lineLength = 0; 1695 | this.decodabet = getDecodabet(options); 1696 | } // end constructor 1697 | 1698 | /** 1699 | * Reads enough of the input stream to convert 1700 | * to/from Base64 and returns the next byte. 1701 | * 1702 | * @return next byte 1703 | * @since 1.3 1704 | */ 1705 | @Override 1706 | public int read() throws java.io.IOException { 1707 | 1708 | // Do we need to get data? 1709 | if( position < 0 ) { 1710 | if( encode ) { 1711 | byte[] b3 = new byte[3]; 1712 | int numBinaryBytes = 0; 1713 | for( int i = 0; i < 3; i++ ) { 1714 | int b = in.read(); 1715 | 1716 | // If end of stream, b is -1. 1717 | if( b >= 0 ) { 1718 | b3[i] = (byte)b; 1719 | numBinaryBytes++; 1720 | } else { 1721 | break; // out of for loop 1722 | } // end else: end of stream 1723 | 1724 | } // end for: each needed input byte 1725 | 1726 | if( numBinaryBytes > 0 ) { 1727 | encode3to4( b3, 0, numBinaryBytes, buffer, 0, options ); 1728 | position = 0; 1729 | numSigBytes = 4; 1730 | } // end if: got data 1731 | else { 1732 | return -1; // Must be end of stream 1733 | } // end else 1734 | } // end if: encoding 1735 | 1736 | // Else decoding 1737 | else { 1738 | byte[] b4 = new byte[4]; 1739 | int i = 0; 1740 | for( i = 0; i < 4; i++ ) { 1741 | // Read four "meaningful" bytes: 1742 | int b = 0; 1743 | do{ b = in.read(); } 1744 | while( b >= 0 && decodabet[ b & 0x7f ] <= WHITE_SPACE_ENC ); 1745 | 1746 | if( b < 0 ) { 1747 | break; // Reads a -1 if end of stream 1748 | } // end if: end of stream 1749 | 1750 | b4[i] = (byte)b; 1751 | } // end for: each needed input byte 1752 | 1753 | if( i == 4 ) { 1754 | numSigBytes = decode4to3( b4, 0, buffer, 0, options ); 1755 | position = 0; 1756 | } // end if: got four characters 1757 | else if( i == 0 ){ 1758 | return -1; 1759 | } // end else if: also padded correctly 1760 | else { 1761 | // Must have broken out from above. 1762 | throw new java.io.IOException( "Improperly padded Base64 input." ); 1763 | } // end 1764 | 1765 | } // end else: decode 1766 | } // end else: get data 1767 | 1768 | // Got data? 1769 | if( position >= 0 ) { 1770 | // End of relevant data? 1771 | if( /*!encode &&*/ position >= numSigBytes ){ 1772 | return -1; 1773 | } // end if: got data 1774 | 1775 | if( encode && breakLines && lineLength >= MAX_LINE_LENGTH ) { 1776 | lineLength = 0; 1777 | return '\n'; 1778 | } // end if 1779 | else { 1780 | lineLength++; // This isn't important when decoding 1781 | // but throwing an extra "if" seems 1782 | // just as wasteful. 1783 | 1784 | int b = buffer[ position++ ]; 1785 | 1786 | if( position >= bufferLength ) { 1787 | position = -1; 1788 | } // end if: end 1789 | 1790 | return b & 0xFF; // This is how you "cast" a byte that's 1791 | // intended to be unsigned. 1792 | } // end else 1793 | } // end if: position >= 0 1794 | 1795 | // Else error 1796 | else { 1797 | throw new java.io.IOException( "Error in Base64 code reading stream." ); 1798 | } // end else 1799 | } // end read 1800 | 1801 | 1802 | /** 1803 | * Calls {@link #read()} repeatedly until the end of stream 1804 | * is reached or len bytes are read. 1805 | * Returns number of bytes read into array or -1 if 1806 | * end of stream is encountered. 1807 | * 1808 | * @param dest array to hold values 1809 | * @param off offset for array 1810 | * @param len max number of bytes to read into array 1811 | * @return bytes read into array or -1 if end of stream is encountered. 1812 | * @since 1.3 1813 | */ 1814 | @Override 1815 | public int read( byte[] dest, int off, int len ) 1816 | throws java.io.IOException { 1817 | int i; 1818 | int b; 1819 | for( i = 0; i < len; i++ ) { 1820 | b = read(); 1821 | 1822 | if( b >= 0 ) { 1823 | dest[off + i] = (byte) b; 1824 | } 1825 | else if( i == 0 ) { 1826 | return -1; 1827 | } 1828 | else { 1829 | break; // Out of 'for' loop 1830 | } // Out of 'for' loop 1831 | } // end for: each byte read 1832 | return i; 1833 | } // end read 1834 | 1835 | } // end inner class InputStream 1836 | 1837 | 1838 | 1839 | 1840 | 1841 | 1842 | /* ******** I N N E R C L A S S O U T P U T S T R E A M ******** */ 1843 | 1844 | 1845 | 1846 | /** 1847 | * A {@link Base64.OutputStream} will write data to another 1848 | * java.io.OutputStream, given in the constructor, 1849 | * and encode/decode to/from Base64 notation on the fly. 1850 | * 1851 | * @see Base64 1852 | * @since 1.3 1853 | */ 1854 | public static class OutputStream extends java.io.FilterOutputStream { 1855 | 1856 | private boolean encode; 1857 | private int position; 1858 | private byte[] buffer; 1859 | private int bufferLength; 1860 | private int lineLength; 1861 | private boolean breakLines; 1862 | private byte[] b4; // Scratch used in a few places 1863 | private boolean suspendEncoding; 1864 | private int options; // Record for later 1865 | private byte[] decodabet; // Local copies to avoid extra method calls 1866 | 1867 | /** 1868 | * Constructs a {@link Base64.OutputStream} in ENCODE mode. 1869 | * 1870 | * @param out the java.io.OutputStream to which data will be written. 1871 | * @since 1.3 1872 | */ 1873 | public OutputStream( java.io.OutputStream out ) { 1874 | this( out, ENCODE ); 1875 | } // end constructor 1876 | 1877 | 1878 | /** 1879 | * Constructs a {@link Base64.OutputStream} in 1880 | * either ENCODE or DECODE mode. 1881 | *

1882 | * Valid options:

1883 |          *   ENCODE or DECODE: Encode or Decode as data is read.
1884 |          *   DO_BREAK_LINES: don't break lines at 76 characters
1885 |          *     (only meaningful when encoding)
1886 |          * 
1887 | *

1888 | * Example: new Base64.OutputStream( out, Base64.ENCODE ) 1889 | * 1890 | * @param out the java.io.OutputStream to which data will be written. 1891 | * @param options Specified options. 1892 | * @see Base64#ENCODE 1893 | * @see Base64#DECODE 1894 | * @see Base64#DO_BREAK_LINES 1895 | * @since 1.3 1896 | */ 1897 | public OutputStream( java.io.OutputStream out, int options ) { 1898 | super( out ); 1899 | this.breakLines = (options & DO_BREAK_LINES) != 0; 1900 | this.encode = (options & ENCODE) != 0; 1901 | this.bufferLength = encode ? 3 : 4; 1902 | this.buffer = new byte[ bufferLength ]; 1903 | this.position = 0; 1904 | this.lineLength = 0; 1905 | this.suspendEncoding = false; 1906 | this.b4 = new byte[4]; 1907 | this.options = options; 1908 | this.decodabet = getDecodabet(options); 1909 | } // end constructor 1910 | 1911 | 1912 | /** 1913 | * Writes the byte to the output stream after 1914 | * converting to/from Base64 notation. 1915 | * When encoding, bytes are buffered three 1916 | * at a time before the output stream actually 1917 | * gets a write() call. 1918 | * When decoding, bytes are buffered four 1919 | * at a time. 1920 | * 1921 | * @param theByte the byte to write 1922 | * @since 1.3 1923 | */ 1924 | @Override 1925 | public void write(int theByte) 1926 | throws java.io.IOException { 1927 | // Encoding suspended? 1928 | if( suspendEncoding ) { 1929 | this.out.write( theByte ); 1930 | return; 1931 | } // end if: supsended 1932 | 1933 | // Encode? 1934 | if( encode ) { 1935 | buffer[ position++ ] = (byte)theByte; 1936 | if( position >= bufferLength ) { // Enough to encode. 1937 | 1938 | this.out.write( encode3to4( b4, buffer, bufferLength, options ) ); 1939 | 1940 | lineLength += 4; 1941 | if( breakLines && lineLength >= MAX_LINE_LENGTH ) { 1942 | this.out.write( NEW_LINE ); 1943 | lineLength = 0; 1944 | } // end if: end of line 1945 | 1946 | position = 0; 1947 | } // end if: enough to output 1948 | } // end if: encoding 1949 | 1950 | // Else, Decoding 1951 | else { 1952 | // Meaningful Base64 character? 1953 | if( decodabet[ theByte & 0x7f ] > WHITE_SPACE_ENC ) { 1954 | buffer[ position++ ] = (byte)theByte; 1955 | if( position >= bufferLength ) { // Enough to output. 1956 | 1957 | int len = Base64.decode4to3( buffer, 0, b4, 0, options ); 1958 | out.write( b4, 0, len ); 1959 | position = 0; 1960 | } // end if: enough to output 1961 | } // end if: meaningful base64 character 1962 | else if( decodabet[ theByte & 0x7f ] != WHITE_SPACE_ENC ) { 1963 | throw new java.io.IOException( "Invalid character in Base64 data." ); 1964 | } // end else: not white space either 1965 | } // end else: decoding 1966 | } // end write 1967 | 1968 | 1969 | 1970 | /** 1971 | * Calls {@link #write(int)} repeatedly until len 1972 | * bytes are written. 1973 | * 1974 | * @param theBytes array from which to read bytes 1975 | * @param off offset for array 1976 | * @param len max number of bytes to read into array 1977 | * @since 1.3 1978 | */ 1979 | @Override 1980 | public void write( byte[] theBytes, int off, int len ) 1981 | throws java.io.IOException { 1982 | // Encoding suspended? 1983 | if( suspendEncoding ) { 1984 | this.out.write( theBytes, off, len ); 1985 | return; 1986 | } // end if: supsended 1987 | 1988 | for( int i = 0; i < len; i++ ) { 1989 | write( theBytes[ off + i ] ); 1990 | } // end for: each byte written 1991 | 1992 | } // end write 1993 | 1994 | 1995 | 1996 | /** 1997 | * Method added by PHIL. [Thanks, PHIL. -Rob] 1998 | * This pads the buffer without closing the stream. 1999 | * @throws java.io.IOException if there's an error. 2000 | */ 2001 | public void flushBase64() throws java.io.IOException { 2002 | if( position > 0 ) { 2003 | if( encode ) { 2004 | out.write( encode3to4( b4, buffer, position, options ) ); 2005 | position = 0; 2006 | } // end if: encoding 2007 | else { 2008 | throw new java.io.IOException( "Base64 input not properly padded." ); 2009 | } // end else: decoding 2010 | } // end if: buffer partially full 2011 | 2012 | } // end flush 2013 | 2014 | 2015 | /** 2016 | * Flushes and closes (I think, in the superclass) the stream. 2017 | * 2018 | * @since 1.3 2019 | */ 2020 | @Override 2021 | public void close() throws java.io.IOException { 2022 | // 1. Ensure that pending characters are written 2023 | flushBase64(); 2024 | 2025 | // 2. Actually close the stream 2026 | // Base class both flushes and closes. 2027 | super.close(); 2028 | 2029 | buffer = null; 2030 | out = null; 2031 | } // end close 2032 | 2033 | 2034 | 2035 | /** 2036 | * Suspends encoding of the stream. 2037 | * May be helpful if you need to embed a piece of 2038 | * base64-encoded data in a stream. 2039 | * 2040 | * @throws java.io.IOException if there's an error flushing 2041 | * @since 1.5.1 2042 | */ 2043 | public void suspendEncoding() throws java.io.IOException { 2044 | flushBase64(); 2045 | this.suspendEncoding = true; 2046 | } // end suspendEncoding 2047 | 2048 | 2049 | /** 2050 | * Resumes encoding of the stream. 2051 | * May be helpful if you need to embed a piece of 2052 | * base64-encoded data in a stream. 2053 | * 2054 | * @since 1.5.1 2055 | */ 2056 | public void resumeEncoding() { 2057 | this.suspendEncoding = false; 2058 | } // end resumeEncoding 2059 | 2060 | 2061 | 2062 | } // end inner class OutputStream 2063 | 2064 | 2065 | } // end class Base64 2066 | -------------------------------------------------------------------------------- /src/com/strumsoft/websocket/phonegap/WebSocket.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Nathan Rajlich (https://github.com/TooTallNate) 3 | * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) 4 | * Copyright (c) 2010 Strumsoft (https://strumsoft.com) 5 | * 6 | * Permission is hereby granted, free of charge, to any person 7 | * obtaining a copy of this software and associated documentation 8 | * files (the "Software"), to deal in the Software without 9 | * restriction, including without limitation the rights to use, 10 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the 12 | * Software is furnished to do so, subject to the following 13 | * conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be 16 | * included in all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 19 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 20 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 21 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 22 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 23 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 24 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 25 | * OTHER DEALINGS IN THE SOFTWARE. 26 | */ 27 | package com.strumsoft.websocket.phonegap; 28 | 29 | import java.io.IOException; 30 | import java.io.UnsupportedEncodingException; 31 | import java.net.InetSocketAddress; 32 | import java.net.URI; 33 | import java.nio.ByteBuffer; 34 | import java.nio.channels.NotYetConnectedException; 35 | import java.nio.channels.SelectionKey; 36 | import java.nio.channels.Selector; 37 | import java.nio.channels.SocketChannel; 38 | import java.security.MessageDigest; 39 | import java.security.NoSuchAlgorithmException; 40 | import java.util.Iterator; 41 | import java.util.Random; 42 | import java.util.Set; 43 | import java.util.concurrent.BlockingQueue; 44 | import java.util.concurrent.LinkedBlockingQueue; 45 | 46 | import android.app.Activity; 47 | import android.content.Context; 48 | import android.os.Handler; 49 | import android.os.Message; 50 | import android.util.Log; 51 | import android.view.inputmethod.InputMethodManager; 52 | import android.webkit.WebView; 53 | 54 | /** 55 | * The WebSocket is an implementation of WebSocket Client API, and 56 | * expects a valid "ws://" URI to connect to. When connected, an instance 57 | * recieves important events related to the life of the connection, like 58 | * onOpen, onClose, onError and 59 | * onMessage. An instance can send messages to the server via the 60 | * send method. 61 | * 62 | * @author Animesh Kumar 63 | */ 64 | public class WebSocket implements Runnable { 65 | 66 | /** 67 | * Enum for WebSocket Draft 68 | */ 69 | public enum Draft { 70 | DRAFT75, DRAFT76 71 | } 72 | 73 | // //////////////// CONSTANT 74 | /** 75 | * The connection has not yet been established. 76 | */ 77 | public final static int WEBSOCKET_STATE_CONNECTING = 0; 78 | /** 79 | * The WebSocket connection is established and communication is possible. 80 | */ 81 | public final static int WEBSOCKET_STATE_OPEN = 1; 82 | /** 83 | * The connection is going through the closing handshake. 84 | */ 85 | public final static int WEBSOCKET_STATE_CLOSING = 2; 86 | /** 87 | * The connection has been closed or could not be opened. 88 | */ 89 | public final static int WEBSOCKET_STATE_CLOSED = 3; 90 | 91 | /** 92 | * An empty string 93 | */ 94 | private static String BLANK_MESSAGE = ""; 95 | /** 96 | * The javascript method name for onOpen event. 97 | */ 98 | private static String EVENT_ON_OPEN = "onopen"; 99 | /** 100 | * The javascript method name for onMessage event. 101 | */ 102 | private static String EVENT_ON_MESSAGE = "onmessage"; 103 | /** 104 | * The javascript method name for onClose event. 105 | */ 106 | private static String EVENT_ON_CLOSE = "onclose"; 107 | /** 108 | * The javascript method name for onError event. 109 | */ 110 | private static String EVENT_ON_ERROR = "onerror"; 111 | /** 112 | * The default port of WebSockets, as defined in the spec. 113 | */ 114 | public static final int DEFAULT_PORT = 80; 115 | /** 116 | * The WebSocket protocol expects UTF-8 encoded bytes. 117 | */ 118 | public static final String UTF8_CHARSET = "UTF-8"; 119 | /** 120 | * The byte representing Carriage Return, or \r 121 | */ 122 | public static final byte DATA_CR = (byte) 0x0D; 123 | /** 124 | * The byte representing Line Feed, or \n 125 | */ 126 | public static final byte DATA_LF = (byte) 0x0A; 127 | /** 128 | * The byte representing the beginning of a WebSocket text frame. 129 | */ 130 | public static final byte DATA_START_OF_FRAME = (byte) 0x00; 131 | /** 132 | * The byte representing the end of a WebSocket text frame. 133 | */ 134 | public static final byte DATA_END_OF_FRAME = (byte) 0xFF; 135 | 136 | // //////////////// INSTANCE Variables 137 | /** 138 | * The WebView instance from Phonegap DroidGap 139 | */ 140 | private final WebView appView; 141 | /** 142 | * The unique id for this instance (helps to bind this to javascript events) 143 | */ 144 | private String id; 145 | /** 146 | * The URI this client is supposed to connect to. 147 | */ 148 | private URI uri; 149 | /** 150 | * The port of the websocket server 151 | */ 152 | private int port; 153 | /** 154 | * The Draft of the WebSocket protocol the Client is adhering to. 155 | */ 156 | private Draft draft; 157 | /** 158 | * The SocketChannel instance to use for this server connection. 159 | * This is used to read and write data to. 160 | */ 161 | private SocketChannel socketChannel; 162 | /** 163 | * The 'Selector' used to get event keys from the underlying socket. 164 | */ 165 | private Selector selector; 166 | /** 167 | * Keeps track of whether or not the client thread should continue running. 168 | */ 169 | private boolean running; 170 | /** 171 | * Internally used to determine whether to recieve data as part of the 172 | * remote handshake, or as part of a text frame. 173 | */ 174 | private boolean handshakeComplete; 175 | /** 176 | * The 1-byte buffer reused throughout the WebSocket connection to read 177 | * data. 178 | */ 179 | private ByteBuffer buffer; 180 | /** 181 | * The bytes that make up the remote handshake. 182 | */ 183 | private ByteBuffer remoteHandshake; 184 | /** 185 | * The bytes that make up the current text frame being read. 186 | */ 187 | private ByteBuffer currentFrame; 188 | /** 189 | * Queue of buffers that need to be sent to the client. 190 | */ 191 | private BlockingQueue bufferQueue; 192 | /** 193 | * Lock object to ensure that data is sent from the bufferQueue in the 194 | * proper order 195 | */ 196 | private Object bufferQueueMutex = new Object(); 197 | /** 198 | * Number 1 used in handshake 199 | */ 200 | private int number1 = 0; 201 | /** 202 | * Number 2 used in handshake 203 | */ 204 | private int number2 = 0; 205 | /** 206 | * Key3 used in handshake 207 | */ 208 | private byte[] key3 = null; 209 | /** 210 | * The readyState attribute represents the state of the connection. 211 | */ 212 | private int readyState = WEBSOCKET_STATE_CONNECTING; 213 | 214 | private boolean keyboardIsShowing = false; 215 | 216 | private Handler handler = null; 217 | 218 | private final WebSocket instance; 219 | 220 | private ByteBuffer bigBuffer = ByteBuffer.allocate(1024 * 500); 221 | private byte[] tokenByteBuffer = new byte[1024 * 500]; 222 | private int tokenByteBufferCounter = 0; 223 | 224 | /** 225 | * Constructor. 226 | * 227 | * Note: this is protected because it's supposed to be instantiated from {@link WebSocketFactory} only. 228 | * 229 | * @param appView 230 | * {@link android.webkit.WebView} 231 | * @param uri 232 | * websocket server {@link URI} 233 | * @param draft 234 | * websocket server {@link Draft} implementation (75/76) 235 | * @param id 236 | * unique id for this instance 237 | */ 238 | protected WebSocket(Handler handler, WebView appView, URI uri, Draft draft, String id) { 239 | this.appView = appView; 240 | this.uri = uri; 241 | this.draft = draft; 242 | this.handler = handler; 243 | 244 | // port 245 | port = uri.getPort(); 246 | if (port == -1) { 247 | port = DEFAULT_PORT; 248 | } 249 | 250 | // Id 251 | this.id = id; 252 | 253 | this.bufferQueue = new LinkedBlockingQueue(); 254 | this.handshakeComplete = false; 255 | this.remoteHandshake = this.currentFrame = null; 256 | this.buffer = ByteBuffer.allocate(1); 257 | 258 | this.instance = this; 259 | } 260 | 261 | // ////////////////////////////////////////////////////////////////////////////////////// 262 | // /////////////////////////// WEB SOCKET API Methods 263 | // /////////////////////////////////// 264 | // ////////////////////////////////////////////////////////////////////////////////////// 265 | /** 266 | * Starts a new Thread and connects to server 267 | * 268 | * @throws IOException 269 | */ 270 | public Thread connect() throws IOException { 271 | this.running = true; 272 | this.readyState = WEBSOCKET_STATE_CONNECTING; 273 | // open socket 274 | socketChannel = SocketChannel.open(); 275 | socketChannel.configureBlocking(false); 276 | // set address 277 | socketChannel.connect(new InetSocketAddress(uri.getHost(), port)); 278 | // start a thread to make connection 279 | 280 | // More info: 281 | // http://groups.google.com/group/android-developers/browse_thread/thread/45a8b53e9bf60d82 282 | // http://stackoverflow.com/questions/2879455/android-2-2-and-bad-address-family-on-socket-connect 283 | System.setProperty("java.net.preferIPv4Stack", "true"); 284 | System.setProperty("java.net.preferIPv6Addresses", "false"); 285 | 286 | selector = Selector.open(); 287 | socketChannel.register(selector, SelectionKey.OP_CONNECT); 288 | Log.v("websocket", "Starting a new thread to manage data reading/writing"); 289 | 290 | Thread th = new Thread(this); 291 | th.start(); 292 | // return thread object for explicit closing, if needed 293 | return th; 294 | } 295 | 296 | public void run() { 297 | while (this.running) { 298 | try { 299 | _connect(); 300 | } catch (IOException e) { 301 | e.printStackTrace(); 302 | this.onError(e); 303 | } 304 | } 305 | } 306 | 307 | 308 | public void setKeyboardStatus(boolean status){ 309 | keyboardIsShowing = status; 310 | Log.d("websocket", "keyboardIsShowing: "+keyboardIsShowing); 311 | } 312 | /** 313 | * Closes connection with server 314 | */ 315 | public void close() { 316 | this.readyState = WebSocket.WEBSOCKET_STATE_CLOSING; 317 | 318 | // close socket channel 319 | try { 320 | this.socketChannel.close(); 321 | } catch (IOException e) { 322 | this.onError(e); 323 | } 324 | this.running = false; 325 | selector.wakeup(); 326 | 327 | // fire onClose method 328 | this.onClose(); 329 | 330 | this.readyState = WebSocket.WEBSOCKET_STATE_CLOSED; 331 | } 332 | 333 | /** 334 | * Sends text to server 335 | * 336 | * @param text 337 | * String to send to server 338 | */ 339 | public void send(final String text) { 340 | new Thread(new Runnable() { 341 | @Override 342 | public void run() { 343 | if (instance.readyState == WEBSOCKET_STATE_OPEN) { 344 | try { 345 | instance._send(text); 346 | } catch (Exception e) { 347 | instance.onError(e); 348 | } 349 | } else { 350 | instance.onError(new NotYetConnectedException()); 351 | } 352 | } 353 | }).start(); 354 | } 355 | 356 | /** 357 | * Called when an entire text frame has been received. 358 | * 359 | * @param msg 360 | * Message from websocket server 361 | */ 362 | public void onMessage(String msg) { 363 | final String data = msg; 364 | Log.v("websocket", "Received a message: " + msg); 365 | appView.post(new Runnable() { 366 | public void run() { 367 | if(keyboardIsShowing){ 368 | Message message = new Message(); 369 | message.obj = buildJavaScriptData(EVENT_ON_MESSAGE, data); 370 | message.what = 3; 371 | handler.sendMessage(message); 372 | } else { 373 | appView.loadUrl(buildJavaScriptData(EVENT_ON_MESSAGE, data)); 374 | } 375 | } 376 | }); 377 | } 378 | 379 | public void onOpen() { 380 | Log.v("websocket", "Connected!"); 381 | appView.post(new Runnable() { 382 | public void run() { 383 | appView.loadUrl(buildJavaScriptData(EVENT_ON_OPEN, BLANK_MESSAGE)); 384 | if(keyboardIsShowing){ 385 | handler.sendEmptyMessage(3); 386 | } 387 | } 388 | }); 389 | } 390 | 391 | public void onClose() { 392 | appView.post(new Runnable() { 393 | public void run() { 394 | appView.loadUrl(buildJavaScriptData(EVENT_ON_CLOSE, BLANK_MESSAGE)); 395 | if(keyboardIsShowing){ 396 | handler.sendEmptyMessage(3); 397 | } 398 | } 399 | }); 400 | } 401 | 402 | public void onError(Throwable t) { 403 | final String msg = t.getMessage(); 404 | Log.v("websocket", "Error: " + msg); 405 | t.printStackTrace(); 406 | appView.post(new Runnable() { 407 | public void run() { 408 | appView.loadUrl(buildJavaScriptData(EVENT_ON_ERROR, msg)); 409 | if(keyboardIsShowing){ 410 | handler.sendEmptyMessage(3); 411 | } 412 | } 413 | }); 414 | } 415 | 416 | public String getId() { 417 | return id; 418 | } 419 | 420 | /** 421 | * @return the readyState 422 | */ 423 | public int getReadyState() { 424 | return readyState; 425 | } 426 | 427 | /** 428 | * Builds text for javascript engine to invoke proper event method with 429 | * proper data. 430 | * 431 | * @param event 432 | * websocket event (onOpen, onMessage etc.) 433 | * @param msg 434 | * Text message received from websocket server 435 | * @return 436 | */ 437 | private String buildJavaScriptData(String event, String msg) { 438 | String b64EncodedMsg = "Error!"; 439 | try{ 440 | if(msg != null) { 441 | b64EncodedMsg = Base64.encodeBytes(msg.getBytes(UTF8_CHARSET)); 442 | } 443 | } catch(Exception e) { 444 | e.printStackTrace(); 445 | } 446 | String _d = "javascript:WebSocket." + event + "(" + "{" + "\"_target\":\"" + id + "\"," 447 | + "\"_data\":'" + b64EncodedMsg + "'" + "}" + ")"; 448 | return _d; 449 | } 450 | 451 | // ////////////////////////////////////////////////////////////////////////////////////// 452 | // /////////////////////////// WEB SOCKET Internal Methods 453 | // ////////////////////////////// 454 | // ////////////////////////////////////////////////////////////////////////////////////// 455 | 456 | private boolean _send(String text) throws IOException { 457 | if (!this.handshakeComplete) { 458 | throw new NotYetConnectedException(); 459 | } 460 | if (text == null) { 461 | throw new NullPointerException("Cannot send 'null' data to a WebSocket."); 462 | } 463 | 464 | // Get 'text' into a WebSocket "frame" of bytes 465 | byte[] textBytes = text.getBytes(UTF8_CHARSET.toString()); 466 | ByteBuffer b = ByteBuffer.allocate(textBytes.length + 2); 467 | b.put(DATA_START_OF_FRAME); 468 | b.put(textBytes); 469 | b.put(DATA_END_OF_FRAME); 470 | b.rewind(); 471 | 472 | // See if we have any backlog that needs to be sent first 473 | if (_write()) { 474 | // Write the ByteBuffer to the socket 475 | this.socketChannel.write(b); 476 | } 477 | 478 | // If we didn't get it all sent, add it to the buffer of buffers 479 | if (b.remaining() > 0) { 480 | if (!this.bufferQueue.offer(b)) { 481 | throw new IOException("Buffers are full, message could not be sent to" 482 | + this.socketChannel.socket().getRemoteSocketAddress()); 483 | } 484 | return false; 485 | } 486 | return true; 487 | } 488 | 489 | // actual connection logic 490 | private void _connect() throws IOException { 491 | // Continuous loop that is only supposed to end when "close" is called. 492 | 493 | selector.select(); 494 | Set keys = selector.selectedKeys(); 495 | Iterator i = keys.iterator(); 496 | 497 | while (i.hasNext()) { 498 | SelectionKey key = i.next(); 499 | i.remove(); 500 | if (key.isConnectable()) { 501 | if (socketChannel.isConnectionPending()) { 502 | socketChannel.finishConnect(); 503 | } 504 | socketChannel.register(selector, SelectionKey.OP_READ); 505 | _writeHandshake(); 506 | } 507 | if (key.isReadable()) { 508 | try { 509 | _read(); 510 | } catch (NoSuchAlgorithmException nsa) { 511 | this.onError(nsa); 512 | } 513 | } 514 | } 515 | 516 | } 517 | 518 | private void _writeHandshake() throws IOException { 519 | String path = this.uri.getPath(); 520 | if (path.indexOf("/") != 0) { 521 | path = "/" + path; 522 | } 523 | 524 | String host = uri.getHost() + (port != DEFAULT_PORT ? ":" + port : ""); 525 | String origin = "*"; // TODO: Make 'origin' configurable 526 | String request = "GET " + path + " HTTP/1.1\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" 527 | + "Host: " + host + "\r\n" + "Origin: " + origin + "\r\n"; 528 | 529 | // Add random keys for Draft76 530 | if (this.draft == Draft.DRAFT76) { 531 | request += "Sec-WebSocket-Key1: " + this._randomKey() + "\r\n"; 532 | request += "Sec-WebSocket-Key2: " + this._randomKey() + "\r\n"; 533 | request += "\r\n"; 534 | this.key3 = new byte[8]; 535 | (new Random()).nextBytes(this.key3); 536 | 537 | // Convert to bytes early so last eight bytes don't get jacked 538 | byte[] bRequest = request.getBytes(UTF8_CHARSET); 539 | 540 | byte[] bToSend = new byte[bRequest.length + 8]; 541 | 542 | // Copy in the Request bytes 543 | System.arraycopy(bRequest, 0, bToSend, 0, bRequest.length); 544 | 545 | // Now tack on key3 bytes 546 | System.arraycopy(this.key3, 0, bToSend, bRequest.length, this.key3.length); 547 | 548 | // Now we can send all keys as a single frame 549 | _write(bToSend); 550 | return; 551 | } 552 | 553 | request += "\r\n"; 554 | _write(request.getBytes(UTF8_CHARSET)); 555 | } 556 | 557 | private boolean _write() throws IOException { 558 | synchronized (this.bufferQueueMutex) { 559 | ByteBuffer buffer = this.bufferQueue.peek(); 560 | while (buffer != null) { 561 | this.socketChannel.write(buffer); 562 | if (buffer.remaining() > 0) { 563 | return false; // Didn't finish this buffer. There's more to 564 | // send. 565 | } else { 566 | this.bufferQueue.poll(); // Buffer finished. Remove it. 567 | buffer = this.bufferQueue.peek(); 568 | } 569 | } 570 | return true; 571 | } 572 | } 573 | 574 | private void _write(byte[] bytes) throws IOException { 575 | this.socketChannel.write(ByteBuffer.wrap(bytes)); 576 | } 577 | 578 | private void _read() throws IOException, NoSuchAlgorithmException { 579 | int bytesRead = -1; 580 | try { 581 | if (!handshakeComplete) { 582 | buffer.rewind(); 583 | bytesRead = socketChannel.read(this.buffer); 584 | buffer.rewind(); 585 | } else { 586 | bigBuffer.rewind(); 587 | bytesRead = socketChannel.read(this.bigBuffer); 588 | bigBuffer.rewind(); 589 | } 590 | 591 | } catch (Exception ex) { 592 | Log.v("websocket", "Could not read data from socket channel, ex=" + ex.toString()); 593 | } 594 | 595 | if (bytesRead == -1) { 596 | Log.v("websocket", "All Bytes readed"); 597 | close(); 598 | } else if (bytesRead > 0) { 599 | if (!this.handshakeComplete) { 600 | _readHandshake(); 601 | } else { 602 | _readFrame(bytesRead); 603 | } 604 | } 605 | } 606 | 607 | private void _readFrame(int bytesRead) throws UnsupportedEncodingException { 608 | byte[] data = bigBuffer.array(); 609 | 610 | int length = bytesRead; 611 | if (length > 2000) length = 2000; 612 | 613 | Log.v("websocket", "_readFrame - bytesRead: " + bytesRead + ", data: " + new String(data, 0, length)); 614 | 615 | // Get tokens 616 | for (int i=0; i < bytesRead; i++) { 617 | 618 | byte readByte = data[i]; 619 | 620 | // Token message is finished 621 | if (readByte == DATA_END_OF_FRAME) { 622 | 623 | // Make token public 624 | this.onMessage(new String(tokenByteBuffer, 0, tokenByteBufferCounter)); 625 | 626 | // Reset counter for byte buffer 627 | tokenByteBufferCounter = 0; 628 | 629 | // Bytes to read as token message, skip start frame byte 630 | } else if (readByte != DATA_START_OF_FRAME) { 631 | tokenByteBufferCounter++; 632 | 633 | // Array is out of bounds, make it greater 634 | if (tokenByteBufferCounter == tokenByteBuffer.length) { 635 | 636 | byte[] newTokenByteBuffer = new byte[tokenByteBuffer.length*2]; 637 | 638 | Log.v("websocket", "expand token byte buffer, new size=" + newTokenByteBuffer.length); 639 | 640 | // Copy old data 641 | for (int i2=0; i2= 20 && h[h.length - 20] == DATA_CR && h[h.length - 19] == DATA_LF 666 | && h[h.length - 18] == DATA_CR && h[h.length - 17] == DATA_LF)) { 667 | _readHandshake(new byte[] { h[h.length - 16], h[h.length - 15], h[h.length - 14], h[h.length - 13], 668 | h[h.length - 12], h[h.length - 11], h[h.length - 10], h[h.length - 9], h[h.length - 8], 669 | h[h.length - 7], h[h.length - 6], h[h.length - 5], h[h.length - 4], h[h.length - 3], 670 | h[h.length - 2], h[h.length - 1] }); 671 | 672 | // If the ByteBuffer contains 8 random bytes,ends with 673 | // 0x0D 0x0A 0x0D 0x0A (or two CRLFs), and the response 674 | // contains Sec-WebSocket-Key1 then the client 675 | // handshake is complete for Draft 76 Server. 676 | } else if ((h.length >= 12 && h[h.length - 12] == DATA_CR && h[h.length - 11] == DATA_LF 677 | && h[h.length - 10] == DATA_CR && h[h.length - 9] == DATA_LF) 678 | && new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec-WebSocket-Key1")) {// ************************ 679 | _readHandshake(new byte[] { h[h.length - 8], h[h.length - 7], h[h.length - 6], h[h.length - 5], 680 | h[h.length - 4], h[h.length - 3], h[h.length - 2], h[h.length - 1] }); 681 | 682 | // Consider Draft 75, and the Flash Security Policy 683 | // Request edge-case. 684 | } else if ((h.length >= 4 && h[h.length - 4] == DATA_CR && h[h.length - 3] == DATA_LF 685 | && h[h.length - 2] == DATA_CR && h[h.length - 1] == DATA_LF) 686 | && !(new String(this.remoteHandshake.array(), UTF8_CHARSET).contains("Sec")) 687 | || (h.length == 23 && h[h.length - 1] == 0)) { 688 | _readHandshake(null); 689 | } 690 | } 691 | 692 | private void _readHandshake(byte[] handShakeBody) throws IOException, NoSuchAlgorithmException { 693 | // byte[] handshakeBytes = this.remoteHandshake.array(); 694 | // String handshake = new String(handshakeBytes, UTF8_CHARSET); 695 | // TODO: Do some parsing of the returned handshake, and close connection 696 | // in received anything unexpected! 697 | 698 | this.handshakeComplete = true; 699 | boolean isConnectionReady = true; 700 | 701 | /* TODO: verify that this works. Was commented before due to null pointer exception */ 702 | if (this.draft == WebSocket.Draft.DRAFT76) { 703 | if (handShakeBody == null) { 704 | isConnectionReady = true; 705 | } 706 | byte[] challenge = new byte[] { (byte) (this.number1 >> 24), (byte) ((this.number1 << 8) >> 24), 707 | (byte) ((this.number1 << 16) >> 24), (byte) ((this.number1 << 24) >> 24), 708 | (byte) (this.number2 >> 24), (byte) ((this.number2 << 8) >> 24), 709 | (byte) ((this.number2 << 16) >> 24), (byte) ((this.number2 << 24) >> 24), this.key3[0], 710 | this.key3[1], this.key3[2], this.key3[3], this.key3[4], this.key3[5], this.key3[6], this.key3[7] }; 711 | MessageDigest md5 = MessageDigest.getInstance("MD5"); 712 | byte[] expected = md5.digest(challenge); 713 | for (int i = 0; i < handShakeBody.length; i++) { 714 | if (expected[i] != handShakeBody[i]) { 715 | isConnectionReady = true; 716 | } 717 | } 718 | } 719 | /* END */ 720 | 721 | if (isConnectionReady) { 722 | this.readyState = WEBSOCKET_STATE_OPEN; 723 | // fire onOpen method 724 | this.onOpen(); 725 | } else { 726 | close(); 727 | } 728 | } 729 | 730 | private String _randomKey() { 731 | Random r = new Random(); 732 | long maxNumber = 4294967295L; 733 | long spaces = r.nextInt(12) + 1; 734 | int max = new Long(maxNumber / spaces).intValue(); 735 | max = Math.abs(max); 736 | int number = r.nextInt(max) + 1; 737 | if (this.number1 == 0) { 738 | this.number1 = number; 739 | } else { 740 | this.number2 = number; 741 | } 742 | long product = number * spaces; 743 | String key = Long.toString(product); 744 | int numChars = r.nextInt(12); 745 | for (int i = 0; i < numChars; i++) { 746 | int position = r.nextInt(key.length()); 747 | position = Math.abs(position); 748 | char randChar = (char) (r.nextInt(95) + 33); 749 | // exclude numbers here 750 | if (randChar >= 48 && randChar <= 57) { 751 | randChar -= 15; 752 | } 753 | key = new StringBuilder(key).insert(position, randChar).toString(); 754 | } 755 | for (int i = 0; i < spaces; i++) { 756 | int n = key.length() - 1; 757 | int position; 758 | if(n == 0) { 759 | position = 1; 760 | } else { 761 | position = r.nextInt(n) + 1; 762 | } 763 | key = new StringBuilder(key).insert(position, "\u0020").toString(); 764 | } 765 | return key; 766 | } 767 | } 768 | -------------------------------------------------------------------------------- /src/com/strumsoft/websocket/phonegap/WebSocketFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2010 Animesh Kumar (https://github.com/anismiles) 3 | * Copyright (c) 2010 Strumsoft (https://strumsoft.com) 4 | * 5 | * Permission is hereby granted, free of charge, to any person 6 | * obtaining a copy of this software and associated documentation 7 | * files (the "Software"), to deal in the Software without 8 | * restriction, including without limitation the rights to use, 9 | * copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the 11 | * Software is furnished to do so, subject to the following 12 | * conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be 15 | * included in all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 18 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 19 | * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 20 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 21 | * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 22 | * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 23 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 24 | * OTHER DEALINGS IN THE SOFTWARE. 25 | * 26 | */ 27 | package com.strumsoft.websocket.phonegap; 28 | 29 | import java.net.URI; 30 | import java.util.Random; 31 | import java.util.Vector; 32 | 33 | import android.app.Activity; 34 | import android.os.Handler; 35 | import android.webkit.WebView; 36 | 37 | /** 38 | * The WebSocketFactory is like a helper class to instantiate new 39 | * WebSocket instaces especially from Javascript side. It expects a valid 40 | * "ws://" URI. 41 | * 42 | * @author Animesh Kumar 43 | */ 44 | public class WebSocketFactory { 45 | 46 | private Vector socketList = new Vector(); 47 | 48 | private Handler handler; 49 | 50 | /** The app view. */ 51 | WebView appView; 52 | 53 | /** 54 | * Instantiates a new web socket factory. 55 | * 56 | * @param appView 57 | * the app view 58 | */ 59 | public WebSocketFactory(Handler h, WebView appView) { 60 | this.appView = appView; 61 | this.handler = h; 62 | } 63 | 64 | public Vector getSocketList() { 65 | return socketList; 66 | } 67 | 68 | public WebSocket getInstance(String url) { 69 | // use Draft76 by default 70 | return getInstance(url, WebSocket.Draft.DRAFT76); 71 | } 72 | 73 | public WebSocket getInstance(String url, WebSocket.Draft draft) { 74 | WebSocket socket = null; 75 | Thread th = null; 76 | try { 77 | socket = new WebSocket(handler, appView, new URI(url), draft, getRandonUniqueId()); 78 | socketList.add(socket); 79 | th = socket.connect(); 80 | return socket; 81 | } catch (Exception e) { 82 | //Log.v("websocket", e.toString()); 83 | if(th != null) { 84 | th.interrupt(); 85 | } 86 | } 87 | return null; 88 | } 89 | 90 | /** 91 | * Generates random unique ids for WebSocket instances 92 | * 93 | * @return String 94 | */ 95 | private String getRandonUniqueId() { 96 | return "WEBSOCKET." + new Random().nextInt(100); 97 | } 98 | 99 | } 100 | --------------------------------------------------------------------------------