├── README.md ├── bin └── builder.js ├── examples ├── echo-client.js └── websocket_echo_server.rb ├── lib ├── events.js ├── sha1.js ├── utils.js └── websocket-client.js └── ti-websocket-client.js /README.md: -------------------------------------------------------------------------------- 1 | # WebSocket client for Titanium Mobile 2 | 3 | It's implementation of WebSocket client for Titanium Mobile. 4 | 5 | 6 | ## Supported protocols 7 | 8 | * [hybi-07](http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07) 9 | 10 | 11 | ## How to use it 12 | 13 | copy "ti-websocket-client.js" to your app Resources directory. 14 | 15 | ```javascript 16 | var WebSocket = require('ti-websocket-client').WebSocket; 17 | 18 | ws = new WebSocket("ws://localhost:3000/"); 19 | 20 | ws.onopen = function () { 21 | alert("Connected"); 22 | ws.send("Hello"); 23 | }; 24 | 25 | ws.onclose = function () { 26 | alert("Disconnected"); 27 | }; 28 | 29 | ws.onmessage = function (message) { 30 | alert("> "+message.data); 31 | }; 32 | 33 | ws.onerror = function (e) { 34 | alert('Error: ' + (e ? JSON.stringify(e) : 'A unknown error occurred')); 35 | }; 36 | ``` 37 | 38 | ## Progress 39 | 40 | * Working good with node and ruby's websocket servers 41 | * You can run on Android and iPhone. 42 | 43 | 44 | ## License 45 | 46 | MIT License. 47 | Copyright 2011 Yuichiro MASUI 48 | -------------------------------------------------------------------------------- /bin/builder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Titanium WebSocket Client 3 | * 4 | * http://github.com/masuidrive/ti-websocket-client 5 | * 6 | * Copyright 2011 Yuichiro MASUI 7 | * MIT License 8 | */ 9 | 10 | var LIB_DIR = __dirname + '/../lib/'; 11 | 12 | var fs = require('fs'); 13 | 14 | var extract_require = function(filename) { 15 | var content = fs.readFileSync(LIB_DIR + filename + '.js', 'utf8'); 16 | return content.replace(/require\s*\(\s*['"]{1}(.*?)['"]{1}\s*\)/g, function(full, fname) { 17 | var exports = extract_require(fname); 18 | return "(function(){var exports={};" + exports + "return exports;}())" 19 | }); 20 | }; 21 | 22 | fs.write( 23 | fs.openSync(__dirname + '/../ti-websocket-client.js', 'w') 24 | , extract_require('websocket-client') 25 | , 0 26 | , 'utf8' 27 | ); 28 | 29 | console.log('Successfully generated the build: ti-websocket-client.js'); 30 | -------------------------------------------------------------------------------- /examples/echo-client.js: -------------------------------------------------------------------------------- 1 | // 2 | // WebSocket client sample app for Titanium Mobile 3 | // 4 | // Yuichiro MASUI 5 | // MIT License 6 | var WebSocket = require('ti-websocket-client').WebSocket; 7 | 8 | var win = Titanium.UI.createWindow({ 9 | title:'WebSocket', 10 | backgroundColor:'#fff' 11 | }); 12 | 13 | var textarea = Titanium.UI.createTextArea({ 14 | backgroundColor: "#eee", 15 | value: '', 16 | editable: false, 17 | top: 70, 18 | left: 0, 19 | right: 0, 20 | bottom: 0 21 | }); 22 | win.add(textarea); 23 | 24 | var connectBtn = Titanium.UI.createButton({ 25 | title:'Connect', 26 | font:{fontSize:16,fontFamily:'Helvetica Neue'}, 27 | textAlign:'center', 28 | width: 100, 29 | height: 20, 30 | top: 5, 31 | left: 5 32 | }); 33 | win.add(connectBtn); 34 | 35 | var ws; 36 | connectBtn.addEventListener('click', function() { 37 | ws = new WebSocket("ws://localhost:3000/"); 38 | 39 | ws.onopen = function () { 40 | Ti.API.info("message Connected"); 41 | log("Connected"); 42 | }; 43 | 44 | ws.onclose = function () { 45 | log("Disconnected"); 46 | }; 47 | 48 | ws.onmessage = function (message) { 49 | log("> "+message.data); 50 | }; 51 | 52 | ws.onerror = function (e) { 53 | log('Error: ' + (e ? JSON.stringify(e) : 'A unknown error occurred')); 54 | }; 55 | 56 | log("Connecting..."); 57 | }); 58 | 59 | var closeBtn = Titanium.UI.createButton({ 60 | title:'Close', 61 | font:{fontSize:16,fontFamily:'Helvetica Neue'}, 62 | textAlign:'center', 63 | width:100, 64 | height: 20, 65 | top: 5, 66 | right: 5 67 | }); 68 | 69 | win.add(closeBtn); 70 | closeBtn.addEventListener('click', function() { 71 | ws.close(); 72 | }); 73 | 74 | var log = function(str) { 75 | textarea.value += str + "\n"; 76 | }; 77 | 78 | var messageField = Ti.UI.createTextField({ 79 | borderStyle:Titanium.UI.INPUT_BORDERSTYLE_ROUNDED, 80 | width:230, 81 | height: 30, 82 | top: 35, 83 | left: 5 84 | }); 85 | win.add(messageField); 86 | 87 | var sendBtn = Titanium.UI.createButton({ 88 | title:'Send', 89 | font:{fontSize:16,fontFamily:'Helvetica Neue'}, 90 | textAlign:'center', 91 | width:70, 92 | height: 30, 93 | top: 35, 94 | right: 5 95 | }); 96 | win.add(sendBtn); 97 | sendBtn.addEventListener('click', function() { 98 | var v = messageField.value; 99 | log('< ' + v); 100 | ws.send(v); 101 | messageField.value = ""; 102 | messageField.blur(); 103 | }); 104 | 105 | 106 | 107 | win.open(); 108 | 109 | -------------------------------------------------------------------------------- /examples/websocket_echo_server.rb: -------------------------------------------------------------------------------- 1 | # Copyright: Hiroshi Ichikawa 2 | # Lincense: New BSD Lincense 3 | 4 | # Copyright: Hiroshi Ichikawa 5 | # Lincense: New BSD Lincense 6 | # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-75 7 | # Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76 8 | # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-07 9 | # Reference: http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10 10 | 11 | require "base64" 12 | require "socket" 13 | require "uri" 14 | require "digest/md5" 15 | require "digest/sha1" 16 | require "openssl" 17 | require "stringio" 18 | 19 | 20 | class WebSocket 21 | 22 | class << self 23 | 24 | attr_accessor(:debug) 25 | 26 | end 27 | 28 | class Error < RuntimeError 29 | 30 | end 31 | 32 | WEB_SOCKET_GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 33 | OPCODE_CONTINUATION = 0x00 34 | OPCODE_TEXT = 0x01 35 | OPCODE_BINARY = 0x02 36 | OPCODE_CLOSE = 0x08 37 | OPCODE_PING = 0x09 38 | OPCODE_PONG = 0x0a 39 | 40 | def initialize(arg, params = {}) 41 | if params[:server] # server 42 | 43 | @server = params[:server] 44 | @socket = arg 45 | line = gets().chomp() 46 | if !(line =~ /\AGET (\S+) HTTP\/1.1\z/n) 47 | raise(WebSocket::Error, "invalid request: #{line}") 48 | end 49 | @path = $1 50 | read_header() 51 | if @header["sec-websocket-version"] 52 | @web_socket_version = @header["sec-websocket-version"] 53 | @key3 = nil 54 | elsif @header["sec-websocket-key1"] && @header["sec-websocket-key2"] 55 | @web_socket_version = "hixie-76" 56 | @key3 = read(8) 57 | else 58 | @web_socket_version = "hixie-75" 59 | @key3 = nil 60 | end 61 | if !@server.accepted_origin?(self.origin) 62 | raise(WebSocket::Error, 63 | ("Unaccepted origin: %s (server.accepted_domains = %p)\n\n" + 64 | "To accept this origin, write e.g. \n" + 65 | " WebSocketServer.new(..., :accepted_domains => [%p]), or\n" + 66 | " WebSocketServer.new(..., :accepted_domains => [\"*\"])\n") % 67 | [self.origin, @server.accepted_domains, @server.origin_to_domain(self.origin)]) 68 | end 69 | @handshaked = false 70 | 71 | else # client 72 | 73 | @web_socket_version = "hixie-76" 74 | uri = arg.is_a?(String) ? URI.parse(arg) : arg 75 | 76 | if uri.scheme == "ws" 77 | default_port = 80 78 | elsif uri.scheme = "wss" 79 | default_port = 443 80 | else 81 | raise(WebSocket::Error, "unsupported scheme: #{uri.scheme}") 82 | end 83 | 84 | @path = (uri.path.empty? ? "/" : uri.path) + (uri.query ? "?" + uri.query : "") 85 | host = uri.host + ((!uri.port || uri.port == default_port) ? "" : ":#{uri.port}") 86 | origin = params[:origin] || "http://#{uri.host}" 87 | key1 = generate_key() 88 | key2 = generate_key() 89 | key3 = generate_key3() 90 | 91 | socket = TCPSocket.new(uri.host, uri.port || default_port) 92 | 93 | if uri.scheme == "ws" 94 | @socket = socket 95 | else 96 | @socket = ssl_handshake(socket) 97 | end 98 | 99 | write( 100 | "GET #{@path} HTTP/1.1\r\n" + 101 | "Upgrade: WebSocket\r\n" + 102 | "Connection: Upgrade\r\n" + 103 | "Host: #{host}\r\n" + 104 | "Origin: #{origin}\r\n" + 105 | "Sec-WebSocket-Key1: #{key1}\r\n" + 106 | "Sec-WebSocket-Key2: #{key2}\r\n" + 107 | "\r\n" + 108 | "#{key3}") 109 | flush() 110 | 111 | line = gets().chomp() 112 | raise(WebSocket::Error, "bad response: #{line}") if !(line =~ /\AHTTP\/1.1 101 /n) 113 | read_header() 114 | if (@header["sec-websocket-origin"] || "").downcase() != origin.downcase() 115 | raise(WebSocket::Error, 116 | "origin doesn't match: '#{@header["sec-websocket-origin"]}' != '#{origin}'") 117 | end 118 | reply_digest = read(16) 119 | expected_digest = hixie_76_security_digest(key1, key2, key3) 120 | if reply_digest != expected_digest 121 | raise(WebSocket::Error, 122 | "security digest doesn't match: %p != %p" % [reply_digest, expected_digest]) 123 | end 124 | @handshaked = true 125 | 126 | end 127 | @received = [] 128 | @buffer = "" 129 | @closing_started = false 130 | end 131 | 132 | attr_reader(:server, :header, :path) 133 | 134 | def handshake(status = nil, header = {}) 135 | if @handshaked 136 | raise(WebSocket::Error, "handshake has already been done") 137 | end 138 | status ||= "101 Switching Protocols" 139 | def_header = {} 140 | case @web_socket_version 141 | when "hixie-75" 142 | def_header["WebSocket-Origin"] = self.origin 143 | def_header["WebSocket-Location"] = self.location 144 | extra_bytes = "" 145 | when "hixie-76" 146 | def_header["Sec-WebSocket-Origin"] = self.origin 147 | def_header["Sec-WebSocket-Location"] = self.location 148 | extra_bytes = hixie_76_security_digest( 149 | @header["Sec-WebSocket-Key1"], @header["Sec-WebSocket-Key2"], @key3) 150 | else 151 | def_header["Sec-WebSocket-Accept"] = security_digest(@header["sec-websocket-key"]) 152 | extra_bytes = "" 153 | end 154 | header = def_header.merge(header) 155 | header_str = header.map(){ |k, v| "#{k}: #{v}\r\n" }.join("") 156 | # Note that Upgrade and Connection must appear in this order. 157 | write( 158 | "HTTP/1.1 #{status}\r\n" + 159 | "Upgrade: websocket\r\n" + 160 | "Connection: Upgrade\r\n" + 161 | "#{header_str}\r\n#{extra_bytes}") 162 | flush() 163 | @handshaked = true 164 | end 165 | 166 | def send(data) 167 | if !@handshaked 168 | raise(WebSocket::Error, "call WebSocket\#handshake first") 169 | end 170 | case @web_socket_version 171 | when "hixie-75", "hixie-76" 172 | data = force_encoding(data.dup(), "ASCII-8BIT") 173 | write("\x00#{data}\xff") 174 | flush() 175 | else 176 | send_frame(OPCODE_TEXT, data, !@server) 177 | end 178 | end 179 | 180 | def receive() 181 | if !@handshaked 182 | raise(WebSocket::Error, "call WebSocket\#handshake first") 183 | end 184 | case @web_socket_version 185 | 186 | when "hixie-75", "hixie-76" 187 | packet = gets("\xff") 188 | return nil if !packet 189 | if packet =~ /\A\x00(.*)\xff\z/nm 190 | return force_encoding($1, "UTF-8") 191 | elsif packet == "\xff" && read(1) == "\x00" # closing 192 | close(1005, "", :peer) 193 | return nil 194 | else 195 | raise(WebSocket::Error, "input must be either '\\x00...\\xff' or '\\xff\\x00'") 196 | end 197 | 198 | else 199 | begin 200 | bytes = read(2).unpack("C*") 201 | fin = (bytes[0] & 0x80) != 0 202 | opcode = bytes[0] & 0x0f 203 | mask = (bytes[1] & 0x80) != 0 204 | plength = bytes[1] & 0x7f 205 | if plength == 126 206 | bytes = read(2) 207 | plength = bytes.unpack("n")[0] 208 | elsif plength == 127 209 | bytes = read(8) 210 | (high, low) = bytes.unpack("NN") 211 | plength = high * (2 ** 32) + low 212 | end 213 | if @server && !mask 214 | # Masking is required. 215 | @socket.close() 216 | raise(WebSocket::Error, "received unmasked data") 217 | end 218 | mask_key = mask ? read(4).unpack("C*") : nil 219 | payload = read(plength) 220 | payload = apply_mask(payload, mask_key) if mask 221 | case opcode 222 | when OPCODE_TEXT 223 | return force_encoding(payload, "UTF-8") 224 | when OPCODE_BINARY 225 | raise(WebSocket::Error, "received binary data, which is not supported") 226 | when OPCODE_CLOSE 227 | close(1005, "", :peer) 228 | return nil 229 | when OPCODE_PING 230 | raise(WebSocket::Error, "received ping, which is not supported") 231 | when OPCODE_PONG 232 | else 233 | raise(WebSocket::Error, "received unknown opcode: %d" % opcode) 234 | end 235 | rescue EOFError 236 | return nil 237 | end 238 | 239 | end 240 | end 241 | 242 | def tcp_socket 243 | return @socket 244 | end 245 | 246 | def host 247 | return @header["host"] 248 | end 249 | 250 | def origin 251 | case @web_socket_version 252 | when "7", "8" 253 | name = "sec-websocket-origin" 254 | else 255 | name = "origin" 256 | end 257 | if @header[name] 258 | return @header[name] 259 | else 260 | raise(WebSocket::Error, "%s header is missing" % name) 261 | end 262 | end 263 | 264 | def location 265 | return "ws://#{self.host}#{@path}" 266 | end 267 | 268 | # Does closing handshake. 269 | def close(code = 1005, reason = "", origin = :self) 270 | if !@closing_started 271 | case @web_socket_version 272 | when "hixie-75", "hixie-76" 273 | write("\xff\x00") 274 | else 275 | if code == 1005 276 | payload = "" 277 | else 278 | payload = [code].pack("n") + force_encoding(reason.dup(), "ASCII-8BIT") 279 | end 280 | send_frame(OPCODE_CLOSE, payload, false) 281 | end 282 | end 283 | @socket.close() if origin == :peer 284 | @closing_started = true 285 | end 286 | 287 | def close_socket() 288 | @socket.close() 289 | end 290 | 291 | private 292 | 293 | NOISE_CHARS = ("\x21".."\x2f").to_a() + ("\x3a".."\x7e").to_a() 294 | 295 | def read_header() 296 | @header = {} 297 | while line = gets() 298 | line = line.chomp() 299 | break if line.empty? 300 | if !(line =~ /\A(\S+): (.*)\z/n) 301 | raise(WebSocket::Error, "invalid request: #{line}") 302 | end 303 | @header[$1] = $2 304 | @header[$1.downcase()] = $2 305 | end 306 | if !(@header["upgrade"] =~ /\AWebSocket\z/i) 307 | raise(WebSocket::Error, "invalid Upgrade: " + @header["upgrade"]) 308 | end 309 | if !(@header["connection"] =~ /\AUpgrade\z/i) 310 | raise(WebSocket::Error, "invalid Connection: " + @header["connection"]) 311 | end 312 | end 313 | 314 | def send_frame(opcode, payload, mask) 315 | payload = force_encoding(payload.dup(), "ASCII-8BIT") 316 | # Setting StringIO's encoding to ASCII-8BIT. 317 | buffer = StringIO.new(force_encoding("", "ASCII-8BIT")) 318 | write_byte(buffer, 0x80 | opcode) 319 | masked_byte = mask ? 0x80 : 0x00 320 | if payload.bytesize <= 125 321 | write_byte(buffer, masked_byte | payload.bytesize) 322 | elsif payload.bytesize < 2 ** 16 323 | write_byte(buffer, masked_byte | 126) 324 | buffer.write([payload.bytesize].pack("n")) 325 | else 326 | write_byte(buffer, masked_byte | 127) 327 | buffer.write([payload.bytesize / (2 ** 32), payload.bytesize % (2 ** 32)].pack("NN")) 328 | end 329 | if mask 330 | mask_key = Array.new(4){ rand(256) } 331 | buffer.write(mask_key.pack("C*")) 332 | payload = apply_mask(payload, mask_key) 333 | end 334 | buffer.write(payload) 335 | write(buffer.string) 336 | end 337 | 338 | def gets(rs = $/) 339 | line = @socket.gets(rs) 340 | $stderr.printf("recv> %p\n", line) if WebSocket.debug 341 | return line 342 | end 343 | 344 | def read(num_bytes) 345 | str = @socket.read(num_bytes) 346 | $stderr.printf("recv> %p\n", str) if WebSocket.debug 347 | if str && str.bytesize == num_bytes 348 | return str 349 | else 350 | raise(EOFError) 351 | end 352 | end 353 | 354 | def write(data) 355 | if WebSocket.debug 356 | data.scan(/\G(.*?(\n|\z))/n) do 357 | $stderr.printf("send> %p\n", $&) if !$&.empty? 358 | end 359 | end 360 | @socket.write(data) 361 | end 362 | 363 | def flush() 364 | @socket.flush() 365 | end 366 | 367 | def write_byte(buffer, byte) 368 | buffer.write([byte].pack("C")) 369 | end 370 | 371 | def security_digest(key) 372 | return Base64.encode64(Digest::SHA1.digest(key + WEB_SOCKET_GUID)).gsub(/\n/, "") 373 | end 374 | 375 | def hixie_76_security_digest(key1, key2, key3) 376 | bytes1 = websocket_key_to_bytes(key1) 377 | bytes2 = websocket_key_to_bytes(key2) 378 | return Digest::MD5.digest(bytes1 + bytes2 + key3) 379 | end 380 | 381 | def apply_mask(payload, mask_key) 382 | orig_bytes = payload.unpack("C*") 383 | new_bytes = [] 384 | orig_bytes.each_with_index() do |b, i| 385 | new_bytes.push(b ^ mask_key[i % 4]) 386 | end 387 | return new_bytes.pack("C*") 388 | end 389 | 390 | def generate_key() 391 | spaces = 1 + rand(12) 392 | max = 0xffffffff / spaces 393 | number = rand(max + 1) 394 | key = (number * spaces).to_s() 395 | (1 + rand(12)).times() do 396 | char = NOISE_CHARS[rand(NOISE_CHARS.size)] 397 | pos = rand(key.size + 1) 398 | key[pos...pos] = char 399 | end 400 | spaces.times() do 401 | pos = 1 + rand(key.size - 1) 402 | key[pos...pos] = " " 403 | end 404 | return key 405 | end 406 | 407 | def generate_key3() 408 | return [rand(0x100000000)].pack("N") + [rand(0x100000000)].pack("N") 409 | end 410 | 411 | def websocket_key_to_bytes(key) 412 | num = key.gsub(/[^\d]/n, "").to_i() / key.scan(/ /).size 413 | return [num].pack("N") 414 | end 415 | 416 | def force_encoding(str, encoding) 417 | if str.respond_to?(:force_encoding) 418 | return str.force_encoding(encoding) 419 | else 420 | return str 421 | end 422 | end 423 | 424 | def ssl_handshake(socket) 425 | ssl_context = OpenSSL::SSL::SSLContext.new() 426 | ssl_socket = OpenSSL::SSL::SSLSocket.new(socket, ssl_context) 427 | ssl_socket.sync_close = true 428 | ssl_socket.connect() 429 | return ssl_socket 430 | end 431 | 432 | end 433 | 434 | 435 | class WebSocketServer 436 | 437 | def initialize(params_or_uri, params = nil) 438 | if params 439 | uri = params_or_uri.is_a?(String) ? URI.parse(params_or_uri) : params_or_uri 440 | params[:port] ||= uri.port 441 | params[:accepted_domains] ||= [uri.host] 442 | else 443 | params = params_or_uri 444 | end 445 | @port = params[:port] || 80 446 | @accepted_domains = params[:accepted_domains] 447 | if !@accepted_domains 448 | raise(ArgumentError, "params[:accepted_domains] is required") 449 | end 450 | if params[:host] 451 | @tcp_server = TCPServer.open(params[:host], @port) 452 | else 453 | @tcp_server = TCPServer.open(@port) 454 | end 455 | end 456 | 457 | attr_reader(:tcp_server, :port, :accepted_domains) 458 | 459 | def run(&block) 460 | while true 461 | Thread.start(accept()) do |s| 462 | begin 463 | ws = create_web_socket(s) 464 | yield(ws) if ws 465 | rescue => ex 466 | print_backtrace(ex) 467 | ensure 468 | begin 469 | ws.close_socket() if ws 470 | rescue 471 | end 472 | end 473 | end 474 | end 475 | end 476 | 477 | def accept() 478 | return @tcp_server.accept() 479 | end 480 | 481 | def accepted_origin?(origin) 482 | domain = origin_to_domain(origin) 483 | return @accepted_domains.any?(){ |d| File.fnmatch(d, domain) } 484 | end 485 | 486 | def origin_to_domain(origin) 487 | if origin == "null" || origin == "file://" # local file 488 | return "null" 489 | else 490 | return URI.parse(origin).host 491 | end 492 | end 493 | 494 | def create_web_socket(socket) 495 | ch = socket.getc() 496 | if ch == ?< 497 | # This is Flash socket policy file request, not an actual Web Socket connection. 498 | send_flash_socket_policy_file(socket) 499 | return nil 500 | else 501 | socket.ungetc(ch) 502 | return WebSocket.new(socket, :server => self) 503 | end 504 | end 505 | 506 | private 507 | 508 | def print_backtrace(ex) 509 | $stderr.printf("%s: %s (%p)\n", ex.backtrace[0], ex.message, ex.class) 510 | for s in ex.backtrace[1..-1] 511 | $stderr.printf(" %s\n", s) 512 | end 513 | end 514 | 515 | # Handles Flash socket policy file request sent when web-socket-js is used: 516 | # http://github.com/gimite/web-socket-js/tree/master 517 | def send_flash_socket_policy_file(socket) 518 | socket.puts('') 519 | socket.puts('') 521 | socket.puts('') 522 | for domain in @accepted_domains 523 | next if domain == "file://" 524 | socket.puts("") 525 | end 526 | socket.puts('') 527 | socket.close() 528 | end 529 | 530 | end 531 | 532 | Thread.abort_on_exception = true 533 | # WebSocket.debug = true 534 | 535 | if ARGV.size != 2 536 | $stderr.puts("Usage: ruby sample/echo_server.rb ACCEPTED_DOMAIN PORT") 537 | end 538 | 539 | server = WebSocketServer.new( 540 | :accepted_domains => [ARGV[0] || "localhost"], 541 | :port => (ARGV[1] || "3000").to_i()) 542 | puts("Server is running at port %d" % server.port) 543 | server.run() do |ws| 544 | puts("Connection accepted") 545 | puts("Path: #{ws.path}, Origin: #{ws.origin}") 546 | if ws.path == "/" 547 | ws.handshake() 548 | while data = ws.receive() 549 | printf("Received: %p\n", data) 550 | ws.send(data) 551 | printf("Sent: %p\n", data) 552 | end 553 | else 554 | ws.handshake("404 Not Found") 555 | end 556 | puts("Connection closed") 557 | end 558 | -------------------------------------------------------------------------------- /lib/events.js: -------------------------------------------------------------------------------- 1 | // Copyright Joyent, Inc. and other Node contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a 4 | // copy of this software and associated documentation files (the 5 | // "Software"), to deal in the Software without restriction, including 6 | // without limitation the rights to use, copy, modify, merge, publish, 7 | // distribute, sublicense, and/or sell copies of the Software, and to permit 8 | // persons to whom the Software is furnished to do so, subject to the 9 | // following conditions: 10 | // 11 | // The above copyright notice and this permission notice shall be included 12 | // in all copies or substantial portions of the Software. 13 | // 14 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 15 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 17 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 18 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 19 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 20 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | 22 | var isArray = Array.isArray; 23 | 24 | function EventEmitter() { } 25 | exports.EventEmitter = EventEmitter; 26 | 27 | // By default EventEmitters will print a warning if more than 28 | // 10 listeners are added to it. This is a useful default which 29 | // helps finding memory leaks. 30 | // 31 | // Obviously not all Emitters should be limited to 10. This function allows 32 | // that to be increased. Set to zero for unlimited. 33 | var defaultMaxListeners = 10; 34 | EventEmitter.prototype.setMaxListeners = function(n) { 35 | if (!this._events) { this._events = {}; } 36 | this._maxListeners = n; 37 | }; 38 | 39 | 40 | EventEmitter.prototype.emit = function() { 41 | var type = arguments[0]; 42 | 43 | if (!this._events) { return false; } 44 | var handler = this._events[type]; 45 | if (!handler) { return false; } 46 | 47 | var args, l, i; 48 | if (typeof handler === 'function') { 49 | switch (arguments.length) { 50 | // fast cases 51 | case 1: 52 | handler.call(this); 53 | break; 54 | case 2: 55 | handler.call(this, arguments[1]); 56 | break; 57 | case 3: 58 | handler.call(this, arguments[1], arguments[2]); 59 | break; 60 | // slower 61 | default: 62 | l = arguments.length; 63 | args = new Array(l - 1); 64 | for (i = 1; i < l; i++) { args[i - 1] = arguments[i]; } 65 | handler.apply(this, args); 66 | } 67 | return true; 68 | 69 | } else if (isArray(handler)) { 70 | l = arguments.length; 71 | args = new Array(l - 1); 72 | for (i = 1; i < l; i++) { args[i - 1] = arguments[i]; } 73 | 74 | var listeners = handler.slice(); 75 | for (i = 0, l = listeners.length; i < l; i++) { 76 | listeners[i].apply(this, args); 77 | } 78 | return true; 79 | 80 | } else { 81 | return false; 82 | } 83 | }; 84 | 85 | // EventEmitter is defined in src/node_events.cc 86 | // EventEmitter.prototype.emit() is also defined there. 87 | EventEmitter.prototype.addListener = function(type, listener) { 88 | if ('function' !== typeof listener) { 89 | throw new Error('addListener only takes instances of Function'); 90 | } 91 | 92 | if (!this._events) { this._events = {}; } 93 | 94 | // To avoid recursion in the case that type === "newListeners"! Before 95 | // adding it to the listeners, first emit "newListeners". 96 | this.emit('newListener', type, listener); 97 | 98 | if (!this._events[type]) { 99 | // Optimize the case of one listener. Don't need the extra array object. 100 | this._events[type] = listener; 101 | } else if (isArray(this._events[type])) { 102 | 103 | // If we've already got an array, just append. 104 | this._events[type].push(listener); 105 | 106 | // Check for listener leak 107 | if (!this._events[type].warned) { 108 | var m; 109 | if (this._maxListeners !== undefined) { 110 | m = this._maxListeners; 111 | } else { 112 | m = defaultMaxListeners; 113 | } 114 | 115 | if (m && m > 0 && this._events[type].length > m) { 116 | this._events[type].warned = true; 117 | console.error('(node) warning: possible EventEmitter memory ' + 118 | 'leak detected. %d listeners added. ' + 119 | 'Use emitter.setMaxListeners() to increase limit.', 120 | this._events[type].length); 121 | console.trace(); 122 | } 123 | } 124 | } else { 125 | // Adding the second element, need to change to array. 126 | this._events[type] = [this._events[type], listener]; 127 | } 128 | 129 | return this; 130 | }; 131 | 132 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 133 | 134 | EventEmitter.prototype.once = function(type, listener) { 135 | if ('function' !== typeof listener) { 136 | throw new Error('.once only takes instances of Function'); 137 | } 138 | 139 | var self = this; 140 | function g() { 141 | self.removeListener(type, g); 142 | listener.apply(this, arguments); 143 | } 144 | 145 | g.listener = listener; 146 | self.on(type, g); 147 | 148 | return this; 149 | }; 150 | 151 | EventEmitter.prototype.removeListener = function(type, listener) { 152 | if ('function' !== typeof listener) { 153 | throw new Error('removeListener only takes instances of Function'); 154 | } 155 | 156 | // does not use listeners(), so no side effect of creating _events[type] 157 | if (!this._events || !this._events[type]) { return this; } 158 | 159 | var list = this._events[type]; 160 | 161 | if (isArray(list)) { 162 | var i, position = -1; 163 | for (i = 0, length = list.length; i < length; i++) { 164 | if (list[i] === listener || 165 | (list[i].listener && list[i].listener === listener)) 166 | { 167 | position = i; 168 | break; 169 | } 170 | } 171 | 172 | if (position < 0) { return this; } 173 | list.splice(position, 1); 174 | if (list.length === 0) { 175 | delete this._events[type]; 176 | } 177 | } else if (list === listener || 178 | (list.listener && list.listener === listener)) 179 | { 180 | delete this._events[type]; 181 | } 182 | 183 | return this; 184 | }; 185 | 186 | EventEmitter.prototype.removeAllListeners = function(type) { 187 | if (arguments.length === 0) { 188 | this._events = {}; 189 | return this; 190 | } 191 | 192 | // does not use listeners(), so no side effect of creating _events[type] 193 | if (type && this._events && this._events[type]) { this._events[type] = null; } 194 | return this; 195 | }; 196 | 197 | EventEmitter.prototype.listeners = function(type) { 198 | if (!this._events) { this._events = {}; } 199 | if (!this._events[type]) { this._events[type] = []; } 200 | if (!isArray(this._events[type])) { 201 | this._events[type] = [this._events[type]]; 202 | } 203 | return this._events[type]; 204 | }; 205 | -------------------------------------------------------------------------------- /lib/sha1.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Modified by Yuichiro MASUI 3 | * Tested on nodejs and Titanium Mobile 4 | * 5 | * The JavaScript implementation of the Secure Hash Algorithm 1 6 | * 7 | * Copyright (c) 2008 Takanori Ishikawa 8 | * All rights reserved. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 14 | * 1. Redistributions of source code must retain the above copyright 15 | * notice, this list of conditions and the following disclaimer. 16 | * 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * 3. Neither the name of the authors nor the names of its contributors 22 | * may be used to endorse or promote products derived from this 23 | * software without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 31 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 32 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | /** 38 | * This is the javascript file for code which implements 39 | * the Secure Hash Algorithm 1 as defined in FIPS 180-1 published April 17, 1995. 40 | * 41 | * Author: Takanori Ishikawa 42 | * Copyright: Takanori Ishikawa 2008 43 | * License: BSD License (see above) 44 | * 45 | * NOTE: 46 | * Only 8-bit string is supported, please use encodeURIComponent() function 47 | * if you want to hash multibyte string. 48 | * 49 | * Supported Browsers: 50 | * [Win] IE 6, Firefox 2 51 | * [Mac] Safari 3, Firefox 2 52 | * 53 | * Usage: 54 | * var hexdigest = new SHA1("Hello.").hexdigest(); // "9b56d519ccd9e1e5b2a725e186184cdc68de0731" 55 | * 56 | * See Also: 57 | * FIPS 180-1 - Secure Hash Standard 58 | * http://www.itl.nist.gov/fipspubs/fip180-1.htm 59 | * 60 | */ 61 | 62 | var SHA1 = (function(){ 63 | 64 | /** 65 | * Spec is the BDD style test utilities. 66 | */ 67 | var Spec; 68 | Spec = { 69 | /** Replace the Spec.describe function with empty function if false. */ 70 | enabled: true, 71 | 72 | /** Indicates whether object 'a' is "equal to" 'b'. */ 73 | equals: function(a, b) { 74 | var i; 75 | if (a instanceof Array && b instanceof Array) { 76 | if (a.length !== b.length) { return false; } 77 | for (i = 0; i < a.length; i++) { if (!Spec.equals(a[i], b[i])) { return false; } } 78 | return true; 79 | } 80 | if ((a !== null && b !== null) && (typeof a === "object" && typeof b === "object")) { 81 | for (i in a) { if(a.hasOwnProperty(i)) { if (!Spec.equals(a[i], b[i])) { return false; } } } 82 | return true; 83 | } 84 | return (a === b); 85 | }, 86 | 87 | /** equivalent to xUint's assert */ 88 | should: function(expection, message) { 89 | Spec.currentIndicator++; 90 | if (!expection) { 91 | var warning = [ 92 | "[Spec failed", 93 | Spec.currentTitle ? " (" + Spec.currentTitle + ")] " : "] ", 94 | (message || (Spec.currentMessage + " " + Spec.currentIndicator) || "") 95 | ].join(""); 96 | 97 | alert(warning); 98 | throw warning; 99 | } 100 | return !!expection; 101 | }, 102 | 103 | /** Write your specification by using describe method. */ 104 | describe: function(title, spec) { 105 | Spec.currentTitle = title; 106 | var name; 107 | for (name in spec) { 108 | if (spec.hasOwnProperty(name)) { 109 | Spec.currentMessage = name; 110 | Spec.currentIndicator = 0; 111 | spec[name](); 112 | Spec.currentIndicator = null; 113 | } 114 | } 115 | Spec.currentMessage = Spec.currentTitle = null; 116 | }, 117 | Version: "0.1" 118 | }; 119 | 120 | // Other BDD style stuffs. 121 | Spec.should.equal = function(a, b, message) { return Spec.should(Spec.equals(a, b), message); }; 122 | Spec.should.not = function(a, message) { return Spec.should(!a, message); }; 123 | Spec.should.not.equal = function(a, b, message) { return Spec.should(!Spec.equals(a, b), message); }; 124 | if (!Spec.enabled) { Spec.describe = function(){}; } 125 | 126 | 127 | // self test 128 | Spec.describe("Spec object", { 129 | "should": function() { 130 | Spec.should(true); 131 | Spec.should(1); 132 | }, 133 | "should.not": function() { 134 | Spec.should.not(false); 135 | Spec.should.not(0); 136 | }, 137 | "should.equal": function() { 138 | Spec.should.equal(null, null); 139 | Spec.should.equal("", ""); 140 | Spec.should.equal(12345, 12345); 141 | Spec.should.equal([0,1,2], [0,1,2]); 142 | Spec.should.equal([0,1,[0,1,2]], [0,1,[0,1,2]]); 143 | Spec.should.equal({}, {}); 144 | Spec.should.equal({x:1}, {x:1}); 145 | Spec.should.equal({x:[1]}, {x:[1]}); 146 | }, 147 | "should.not.equal": function() { 148 | Spec.should.not.equal([1,2,3], [1,2,3,4]); 149 | Spec.should.not.equal({x:1}, [1,2,3,4]); 150 | } 151 | }); 152 | 153 | 154 | // ----------------------------------------------------------- 155 | // Utilities 156 | // ----------------------------------------------------------- 157 | // int32 -> hexdigits string (e.g. 0x123 -> '00000123') 158 | function strfhex32(i32) { 159 | i32 &= 0xffffffff; 160 | if (i32 < 0) { i32 += 0x100000000; } 161 | var hex = Number(i32).toString(16); 162 | if (hex.length < 8) { hex = "00000000".substr(0, 8 - hex.length) + hex; } 163 | return hex; 164 | } 165 | Spec.describe("sha1", { 166 | "strfhex32": function() { 167 | Spec.should.equal(strfhex32(0x0), "00000000"); 168 | Spec.should.equal(strfhex32(0x123), "00000123"); 169 | Spec.should.equal(strfhex32(0xffffffff), "ffffffff"); 170 | } 171 | }); 172 | /* 173 | // int32 -> string (e.g. 123 -> '00000000 00000000 00000000 01111011') 174 | function strfbits(i32) { 175 | if (typeof arguments.callee.ZERO32 === 'undefined') { 176 | arguments.callee.ZERO32 = new Array(33).join("0"); 177 | } 178 | 179 | var bits = Number(i32).toString(2); 180 | // '0' padding 181 | if (bits.length < 32) bits = arguments.callee.ZERO32.substr(0, 32 - bits.length) + bits; 182 | // split by 8 bits 183 | return bits.replace(/(¥d{8})/g, '$1 ') 184 | .replace(/^¥s*(.*?)¥s*$/, '$1'); 185 | } 186 | Spec.describe("sha1", { 187 | "strfbits": function() { 188 | Ti.API.info(strfbits(0)); 189 | Ti.API.info(strfbits(1)); 190 | Ti.API.info(strfbits(123)); 191 | Spec.should.equal(strfbits(0), "00000000 00000000 00000000 00000000"); 192 | Spec.should.equal(strfbits(1), "00000000 00000000 00000000 00000001"); 193 | Spec.should.equal(strfbits(123), "00000000 00000000 00000000 01111011"); 194 | } 195 | }); 196 | */ 197 | 198 | // ----------------------------------------------------------- 199 | // SHA-1 200 | // ----------------------------------------------------------- 201 | // Returns Number(32bit unsigned integer) array size to fit for blocks (512-bit strings) 202 | function padding_size(nbits) { 203 | var n = nbits + 1 + 64; 204 | return 512 * Math.ceil(n / 512) / 32; 205 | } 206 | Spec.describe("sha1", { 207 | "padding_size": function() { 208 | Spec.should.equal(padding_size(0), 16); 209 | Spec.should.equal(padding_size(1), 16); 210 | Spec.should.equal(padding_size(512 - 64 - 1), 16); 211 | Spec.should.equal(padding_size(512 - 64), 32); 212 | } 213 | }); 214 | 215 | // 8bit string -> uint32[] 216 | function word_array(m) { 217 | var nchar = m.length; 218 | var size = padding_size(nchar * 8); 219 | var words = new Array(size); 220 | var i; 221 | for (i = 0, j = 0; i < nchar; ) { 222 | words[j++] = ((m.charCodeAt(i++) & 0xff) << 24) | 223 | ((m.charCodeAt(i++) & 0xff) << 16) | 224 | ((m.charCodeAt(i++) & 0xff) << 8) | 225 | ((m.charCodeAt(i++) & 0xff)); 226 | } 227 | while (j < size) { words[j++] = 0; } 228 | return words; 229 | } 230 | Spec.describe("sha1", { 231 | "word_array": function() { 232 | Spec.should.equal(word_array(""), [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); 233 | Spec.should.equal(word_array("1234")[0], 0x31323334); 234 | } 235 | }); 236 | 237 | function write_nbits(words, length, nbits) { 238 | if (nbits > 0xffffffff) { 239 | var lo = nbits & 0xffffffff; 240 | if (lo < 0) { lo += 0x100000000; } 241 | words[length - 1] = lo; 242 | words[length - 2] = (nbits - lo) / 0x100000000; 243 | } else { 244 | words[length - 1] = nbits; 245 | words[length - 2] = 0x0; 246 | } 247 | return words; 248 | } 249 | Spec.describe("sha1", { 250 | "write_nbits": function() { 251 | Spec.should.equal(write_nbits([0, 0], 2, 1), [0, 1]); 252 | Spec.should.equal(write_nbits([0, 0], 2, 0xffffffff), [0, 0xffffffff]); 253 | Spec.should.equal(write_nbits([0, 0], 2, 0x100000000), [1, 0]); 254 | Spec.should.equal(write_nbits([0, 0], 2, 0x1ffffffff), [1, 0xffffffff]); 255 | Spec.should.equal(write_nbits([0, 0], 2, 0x12300000000), [0x123, 0]); 256 | Spec.should.equal(write_nbits([0, 0], 2, 0x123abcdef12), [0x123, 0xabcdef12]); 257 | } 258 | }); 259 | 260 | function padding(words, nbits) { 261 | var i = Math.floor(nbits / 32); 262 | 263 | words[i] |= (1 << (((i + 1) * 32) - nbits - 1)); 264 | write_nbits(words, padding_size(nbits), nbits); 265 | return words; 266 | } 267 | 268 | function digest(words) { 269 | var i = 0, t = 0; 270 | var H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]; 271 | 272 | while (i < words.length) { 273 | var W = new Array(80); 274 | 275 | // (a) 276 | for (t = 0; t < 16; t++) { W[t] = words[i++]; } 277 | 278 | // (b) 279 | for (t = 16; t < 80; t++) { 280 | var w = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]; 281 | W[t] = (w << 1) | (w >>> 31); 282 | } 283 | 284 | // (c) 285 | var A = H[0], B = H[1], C = H[2], D = H[3], E = H[4]; 286 | 287 | // (d) TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; 288 | // E = D; D = C; C = S30(B); B = A; A = TEMP; 289 | for (t = 0; t < 80; t++) { 290 | var tmp = ((A << 5) | (A >>> 27)) + E + W[t]; 291 | 292 | if (t >= 0 && t <= 19) { tmp += ((B & C) | ((~B) & D)) + 0x5a827999; } 293 | else if (t >= 20 && t <= 39) { tmp += (B ^ C ^ D) + 0x6ed9eba1; } 294 | else if (t >= 40 && t <= 59) { tmp += ((B & C) | (B & D) | (C & D)) + 0x8f1bbcdc; } 295 | else if (t >= 60 && t <= 79) { tmp += (B ^ C ^ D) + 0xca62c1d6; } 296 | 297 | E = D; D = C; C = ((B << 30) | (B >>> 2)); B = A; A = tmp; 298 | } 299 | 300 | // (e) H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. 301 | H[0] = (H[0] + A) & 0xffffffff; 302 | H[1] = (H[1] + B) & 0xffffffff; 303 | H[2] = (H[2] + C) & 0xffffffff; 304 | H[3] = (H[3] + D) & 0xffffffff; 305 | H[4] = (H[4] + E) & 0xffffffff; 306 | if (H[0] < 0) { H[0] += 0x100000000; } 307 | if (H[1] < 0) { H[1] += 0x100000000; } 308 | if (H[2] < 0) { H[2] += 0x100000000; } 309 | if (H[3] < 0) { H[3] += 0x100000000; } 310 | if (H[4] < 0) { H[4] += 0x100000000; } 311 | } 312 | 313 | return H; 314 | } 315 | 316 | // message: 8bit string 317 | var SHA1 = function(message) { 318 | this.message = message; 319 | }; 320 | 321 | function strfhex8(i8) { 322 | i8 &= 0xff; 323 | if (i8 < 0) { i8 += 0x100; } 324 | var hex = Number(i8).toString(16); 325 | if (hex.length < 2) { hex = "00".substr(0, 2 - hex.length) + hex; } 326 | return hex; 327 | } 328 | 329 | 330 | _base64_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 331 | SHA1.prototype = { 332 | digest: function() { 333 | var nbits = this.message.length * 8; 334 | var words = padding(word_array(this.message), nbits); 335 | return digest(words); 336 | }, 337 | 338 | base64digest: function() { 339 | var hex = this.hexdigest(); 340 | var output = ""; 341 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 342 | var i = 0; 343 | while (i < hex.length) { 344 | chr1 = parseInt(hex.substring(i, i+2), 16); 345 | chr2 = parseInt(hex.substring(i+2, i+4), 16); 346 | chr3 = parseInt(hex.substring(i+4, i+6), 16); 347 | 348 | enc1 = chr1 >> 2; 349 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 350 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 351 | enc4 = chr3 & 63; 352 | 353 | if (isNaN(chr2)) { 354 | enc3 = enc4 = 64; 355 | } else if (isNaN(chr3)) { 356 | enc4 = 64; 357 | } 358 | 359 | output = output + 360 | _base64_keyStr.charAt(enc1) + _base64_keyStr.charAt(enc2) + 361 | _base64_keyStr.charAt(enc3) + _base64_keyStr.charAt(enc4); 362 | i += 6; 363 | } 364 | 365 | return output; 366 | }, 367 | 368 | hexdigest: function() { 369 | var digest = this.digest(); 370 | var i; 371 | for (i = 0; i < digest.length; i++) { digest[i] = strfhex32(digest[i]); } 372 | return digest.join(""); 373 | } 374 | }; 375 | 376 | Spec.describe("sha1", { 377 | "SHA1#hexdigest": function() { 378 | Spec.should.equal(new SHA1("").hexdigest(), "da39a3ee5e6b4b0d3255bfef95601890afd80709"); 379 | Spec.should.equal(new SHA1("1").hexdigest(), "356a192b7913b04c54574d18c28d46e6395428ab"); 380 | Spec.should.equal(new SHA1("Hello.").hexdigest(), "9b56d519ccd9e1e5b2a725e186184cdc68de0731"); 381 | Spec.should.equal(new SHA1("9b56d519ccd9e1e5b2a725e186184cdc68de0731").hexdigest(), "f042dc98a62cbad68dbe21f11bbc1e9d416d2bf6"); 382 | Spec.should.equal(new SHA1("MD5abZRVSXZVRcasdfasdddddddddddddddds+BNRJFSLKJFN+SEONBBJFJXLKCJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wuraddddddasdfasdfd").hexdigest(), "662dbf4ebc9cdb4224766e87634e5ba9e6de672b"); 383 | } 384 | }); 385 | 386 | return SHA1; 387 | }()); 388 | 389 | exports.SHA1 = SHA1; // add for node.js 390 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | 2 | exports.read_byte = function(buffer, position) { 3 | var data = Ti.Codec.decodeNumber({ 4 | source: buffer, 5 | position: position || 0, 6 | type: Ti.Codec.TYPE_BYTE, 7 | byteOrder: Ti.Codec.BIG_ENDIAN 8 | }); 9 | if(data < 0) { data += 256; } //2**8; 10 | return data; 11 | }; 12 | 13 | exports.read_2byte = function(buffer, position) { 14 | var data = Ti.Codec.decodeNumber({ 15 | source: buffer, 16 | position: position || 0, 17 | type: Ti.Codec.TYPE_SHORT, 18 | byteOrder: Ti.Codec.BIG_ENDIAN 19 | }); 20 | if(data < 0) { data += 65536; } // 2**16 21 | return data; 22 | }; 23 | 24 | exports.read_8byte = function(buffer, position) { 25 | var data = Ti.Codec.decodeNumber({ 26 | source: buffer, 27 | position: position || 0, 28 | type: Ti.Codec.TYPE_LONG, 29 | byteOrder: Ti.Codec.BIG_ENDIAN 30 | 31 | }); 32 | if(data < 0) { data += 18446744073709551616; } // 2**64 33 | return data; 34 | }; 35 | 36 | exports.byte_length = function(str) { 37 | var buffer = Ti.createBuffer({length: 65536}); 38 | var length = Ti.Codec.encodeString({ 39 | source: str, 40 | dest: buffer 41 | }); 42 | return length; 43 | }; 44 | 45 | exports.trim = function(str) { 46 | return String(str).replace(/^\s+|\s+$/g, ""); 47 | }; 48 | -------------------------------------------------------------------------------- /lib/websocket-client.js: -------------------------------------------------------------------------------- 1 | var SHA1 = require('sha1').SHA1; 2 | var Utils = require('utils'); 3 | var events = require('events'); 4 | 5 | var debug = function(str) { 6 | Ti.API.debug(str); 7 | }; 8 | 9 | var CONNECTING = 0; 10 | var OPEN = 1; 11 | var CLOSING = 2; 12 | var CLOSED = 3; 13 | 14 | var BUFFER_SIZE = 65536; 15 | var CLOSING_TIMEOUT = 1000; 16 | 17 | var WebSocket = function(url, protocols, origin, extensions) { 18 | this.url = url; 19 | if(!this._parse_url()) { 20 | throw "Wrong url scheme for WebSocket: " + this.url; 21 | } 22 | 23 | this.origin = origin || String.format("http://%s:%s/", this._host, this._port); 24 | this.protocols = protocols; 25 | this.extensions = extensions; 26 | 27 | this.readyState = CONNECTING; 28 | 29 | this._masking_disabled = false; 30 | this._headers = []; 31 | this._pong_received = false; 32 | this._readBuffer = ''; 33 | this._socketReadBuffer = undefined; 34 | this._closingTimer = undefined; 35 | this._handshake = undefined; 36 | 37 | this._socket = undefined; 38 | 39 | this._connect(); 40 | }; 41 | exports.WebSocket = WebSocket; 42 | WebSocket.prototype = new events.EventEmitter(); 43 | 44 | WebSocket.prototype.onopen = function() { 45 | // NO OP 46 | }; 47 | 48 | WebSocket.prototype.onmessage = function() { 49 | // NO OP 50 | }; 51 | 52 | WebSocket.prototype.onerror = function() { 53 | // NO OP 54 | }; 55 | 56 | WebSocket.prototype.onclose = function() { 57 | // NO OP 58 | }; 59 | 60 | WebSocket.prototype._parse_url = function() { 61 | var parsed = this.url.match(/^([a-z]+):\/\/([\w.]+)(:(\d+)|)(.*)/i); 62 | if(!parsed || parsed[1] !== 'ws') { 63 | return false; 64 | } 65 | this._host = parsed[2]; 66 | this._port = parsed[4] || 80; 67 | this._path = parsed[5]; 68 | 69 | return true; 70 | }; 71 | 72 | var make_handshake_key = function() { 73 | var i, key = ""; 74 | for(i=0; i<16; ++i) { 75 | key += String.fromCharCode(Math.random()*255+1); 76 | } 77 | return Utils.trim(Ti.Utils.base64encode(key)); 78 | }; 79 | 80 | var make_handshake = function(host, path, origin, protocols, extensions, handshake) { 81 | str = "GET " + path + " HTTP/1.1\r\n"; 82 | str += "Host: " + host + "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"; 83 | str += "Sec-WebSocket-Key: " + handshake + "\r\n"; 84 | str += "Origin: " + origin + "\r\n"; 85 | str += "Sec-WebSocket-Origin: " + origin + "\r\n"; 86 | str += "Sec-WebSocket-Version: 7\r\n"; 87 | 88 | if(protocols && protocols.length > 0) { 89 | str += "Sec-WebSocket-Protocol: " + protocols.join(',') + "\r\n"; 90 | } 91 | 92 | if(extensions && extensions.length > 0) { 93 | str += "Sec-WebSocket-Extensions: " + extensions.join(',') + "\r\n"; 94 | } 95 | 96 | // TODO: compression 97 | //if @compression 98 | // extensions << "deflate-application-data" 99 | //end 100 | 101 | return str + "\r\n"; 102 | }; 103 | 104 | WebSocket.prototype._send_handshake = function() { 105 | this._handshake = make_handshake_key(); 106 | var handshake = make_handshake(this._host, this._path, this.origin, this.protocols, this.extensions, this._handshake); 107 | return this._socket.write(Ti.createBuffer({ value: handshake })) > 0; 108 | }; 109 | 110 | WebSocket.prototype._read_http_headers = function() { 111 | var string = ""; 112 | var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 113 | var counter = 10; 114 | while(true) { 115 | var bytesRead = this._socket.read(buffer); 116 | if(bytesRead > 0) { 117 | var lastStringLen = string.length; 118 | string += Ti.Codec.decodeString({ 119 | source: buffer, 120 | charset: Ti.Codec.CHARSET_ASCII 121 | }); 122 | var eoh = string.match(/\r\n\r\n/); 123 | if(eoh) { 124 | var offset = (eoh.index + 4) - lastStringLen; 125 | string = string.substring(0, offset-2); 126 | 127 | this.buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 128 | this.bufferSize = bytesRead - offset; 129 | this.buffer.copy(buffer, 0, offset, this.bufferSize); 130 | break; 131 | } 132 | } 133 | else { 134 | debug("read_http_headers: timeout"); 135 | --counter; 136 | if(counter < 0) { 137 | return false; // Timeout 138 | } 139 | } 140 | buffer.clear(); // clear the buffer before the next read 141 | } 142 | buffer.clear(); 143 | this.headers = string.split("\r\n"); 144 | 145 | return true; 146 | }; 147 | 148 | var extract_headers = function(headers) { 149 | var result = {}; 150 | headers.forEach(function(line) { 151 | var index = line.indexOf(":"); 152 | if(index > 0) { 153 | var key = Utils.trim(line.slice(0, index)); 154 | var value = Utils.trim(line.slice(index + 1)); 155 | result[key] = value; 156 | } 157 | }); 158 | return result; 159 | }; 160 | 161 | var handshake_reponse = function(handshake) { 162 | return (new SHA1(handshake + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")).base64digest(); 163 | }; 164 | 165 | WebSocket.prototype._check_handshake_response = function() { 166 | var version = this.headers.shift(); 167 | if(version !== "HTTP/1.1 101 Switching Protocols") { 168 | // Mismatch protocol version 169 | debug("mismatch protocol version"); 170 | return false; 171 | } 172 | var h = extract_headers(this.headers); 173 | if(!h.Upgrade || !h.Connection || !h['Sec-WebSocket-Accept']) { 174 | return false; 175 | } 176 | if(h.Upgrade.toLowerCase() !== 'websocket' || h.Connection.toLowerCase() !== 'upgrade' || h['Sec-WebSocket-Accept'] !== handshake_reponse(this._handshake)) { 177 | return false; 178 | } 179 | 180 | // TODO: compression 181 | // if h.has_key?('Sec-WebSocket-Extensions') and h['Sec-WebSocket-Extensions'] === 'deflate-application-data' 182 | // if @compression 183 | // @zout = Zlib::Deflate.new(Zlib::BEST_SPEED, Zlib::MAX_WBITS, 8, 1) 184 | // @zin = Zlib::Inflate.new 185 | // end 186 | // else 187 | // @compression = false 188 | // end 189 | 190 | this.readyState = OPEN; 191 | return true; 192 | }; 193 | 194 | WebSocket.prototype._create_frame = function(opcode, d, last_frame) { 195 | if(typeof last_frame === 'undefined') { 196 | last_frame = true; 197 | } 198 | 199 | if(last_frame === false && opcode >= 0x8 && opcode <= 0xf) { 200 | return false; 201 | } 202 | 203 | // apply per frame compression 204 | var out = Ti.createBuffer({ length: BUFFER_SIZE }); 205 | var outIndex = 0; 206 | 207 | var data = d || ''; //compress(d) // TODO 208 | 209 | var byte1 = opcode; 210 | if(last_frame) { 211 | byte1 = byte1 | 0x80; 212 | } 213 | 214 | Ti.Codec.encodeNumber({ 215 | source: byte1, 216 | dest: out, 217 | position: outIndex++, 218 | type: Ti.Codec.TYPE_BYTE, 219 | }); 220 | 221 | var length = Utils.byte_length(data); 222 | 223 | if(length <= 125) { 224 | var byte2 = length; 225 | if(!this._masking_disabled) { 226 | byte2 = (byte2 | 0x80); // # set masking bit 227 | } 228 | Ti.Codec.encodeNumber({ 229 | source: byte2, 230 | dest: out, 231 | position: outIndex++, 232 | type: Ti.Codec.TYPE_BYTE 233 | }); 234 | } 235 | else if(length < BUFFER_SIZE) { // # write 2 byte length 236 | Ti.Codec.encodeNumber({ 237 | source: (126 | 0x80), 238 | dest: out, 239 | position: outIndex++, 240 | type: Ti.Codec.TYPE_BYTE 241 | }); 242 | Ti.Codec.encodeNumber({ 243 | source: length, 244 | dest: out, 245 | position: outIndex++, 246 | type: Ti.Codec.TYPE_SHORT, 247 | byteOrder: Ti.Codec.BIG_ENDIAN 248 | }); 249 | outIndex += 2; 250 | } 251 | else { // # write 8 byte length 252 | Ti.Codec.encodeNumber({ 253 | source: (127 | 0x80), 254 | dest: out, 255 | position: outIndex++, 256 | type: Ti.Codec.TYPE_BYTE 257 | }); 258 | Ti.Codec.encodeNumber({ 259 | source: length, 260 | dest: out, 261 | position: outIndex, 262 | type: Ti.Codec.TYPE_LONG, 263 | byteOrder: Ti.Codec.BIG_ENDIAN 264 | }); 265 | outIndex += 8; 266 | } 267 | 268 | //# mask data 269 | outIndex = this._mask_payload(out, outIndex, data); 270 | out.length = outIndex; 271 | 272 | return out; 273 | }; 274 | 275 | WebSocket.prototype._mask_payload = function(out, outIndex, payload) { 276 | if(!this._masking_disabled) { 277 | var i, masking_key = []; 278 | for(i = 0; i < 4; ++i) { 279 | var key = Math.floor(Math.random()*255) & 0xff; 280 | masking_key.push(key); 281 | Ti.Codec.encodeNumber({ 282 | source: key, 283 | dest: out, 284 | position: outIndex++, 285 | type: Ti.Codec.TYPE_BYTE 286 | }); 287 | } 288 | 289 | var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 290 | var length = Ti.Codec.encodeString({ 291 | source: payload, 292 | dest: buffer 293 | }); 294 | buffer.length = length; 295 | 296 | var string = Ti.Codec.decodeString({ 297 | source: buffer, 298 | charset: Ti.Codec.CHARSET_ASCII 299 | }); 300 | 301 | var masked_string = ""; 302 | for(i = 0; i < string.length; ++i) { 303 | Ti.Codec.encodeNumber({ 304 | source: string.charCodeAt(i) ^ masking_key[i % 4], 305 | dest: out, 306 | position: outIndex++, 307 | type: Ti.Codec.TYPE_BYTE, 308 | }); 309 | } 310 | return outIndex; 311 | } 312 | else { 313 | var len = Ti.Codec.encodeString({ 314 | source: payload, 315 | dest: out, 316 | destPosition: outIndex 317 | }); 318 | return len + outIndex; 319 | } 320 | }; 321 | 322 | var parse_frame = function(buffer, size) { 323 | if(size < 3) { 324 | return undefined; 325 | } 326 | 327 | var byte1 = Utils.read_byte(buffer, 0); 328 | var fin = !!(byte1 & 0x80); 329 | var opcode = byte1 & 0x0f; 330 | 331 | var byte2 = Utils.read_byte(buffer, 1); 332 | var mask = !!(byte2 & 0x80); 333 | var len = byte2 & 0x7f; 334 | 335 | var offset = 2; 336 | switch(len) { 337 | case 126: 338 | len = Utils.read_2byte(buffer, offset); 339 | offset += 2; 340 | break; 341 | 342 | case 127: 343 | // too large I felt 344 | len = Utils.read_8byte(buffer, offset); 345 | offset += 8; 346 | break; 347 | } 348 | 349 | if(len + offset > size) { 350 | return undefined; 351 | } 352 | 353 | var string = Ti.Codec.decodeString({ 354 | source: buffer, 355 | position: offset, 356 | length: len, 357 | charset: Ti.Codec.CHARSET_UTF8 358 | }); 359 | 360 | return({fin: fin, opcode: opcode, payload: string, size: len + offset}); 361 | }; 362 | 363 | WebSocket.prototype.send = function(data) { 364 | if(data && this.readyState === OPEN) { 365 | var frame = this._create_frame(0x01, data); 366 | var bytesWritten = this._socket.write(frame); 367 | return bytesWritten > 0; 368 | } 369 | else { 370 | return false; 371 | } 372 | }; 373 | 374 | WebSocket.prototype._socket_close = function() { 375 | if(this._closingTimer) { 376 | clearTimeout(this._closingTimer); 377 | } 378 | this._closingTimer = undefined; 379 | 380 | this._readBuffer = ''; 381 | this._socketReadBuffer = undefined; 382 | 383 | var ev; 384 | if(this.readyState === CLOSING) { 385 | this.readyState = CLOSED; 386 | this._socket.close(); 387 | ev = { 388 | code: 1000, 389 | wasClean: true, 390 | reason: "" 391 | }; 392 | this.emit("close", ev); 393 | this.onclose(ev); 394 | } 395 | else if(this.readyState !== CLOSED) { 396 | this._socket.close(); 397 | this.readyState = CLOSED; 398 | ev = { 399 | advice: "reconnect" 400 | }; 401 | this.emit("error", ev); 402 | this.onerror(ev); 403 | } 404 | this._socket = undefined; 405 | }; 406 | 407 | 408 | WebSocket.prototype._read_callback = function(e) { 409 | var self = this; 410 | 411 | var nextTick = function() { 412 | self._socketReadBuffer.clear(); 413 | Ti.Stream.read(self._socket, self._socketReadBuffer, function(e) { self._read_callback(e); }); 414 | }; 415 | 416 | if('undefined' !== typeof e) { 417 | if (0 === e.bytesProcessed) { 418 | return nextTick(); 419 | } 420 | 421 | if(-1 === e.bytesProcessed) { // EOF 422 | this._socket_close(); 423 | return undefined; 424 | } 425 | 426 | if('undefined' === typeof this.buffer) { 427 | this.buffer = this._socketReadBuffer.clone(); 428 | this.bufferSize = e.bytesProcessed; 429 | } 430 | else { 431 | this.buffer.copy(this._socketReadBuffer, this.bufferSize, 0, e.bytesProcessed); 432 | this.bufferSize += e.bytesProcessed; 433 | this._socketReadBuffer.clear(); 434 | } 435 | } 436 | 437 | var frame = parse_frame(this.buffer, this.bufferSize); 438 | if('undefined' === typeof frame) { 439 | return nextTick(); 440 | } 441 | else { 442 | if(frame.size < this.bufferSize) { 443 | var nextBuffer = Ti.createBuffer({ length: BUFFER_SIZE }); 444 | if(this.bufferSize - frame.size > 0) { 445 | nextBuffer.copy(this.buffer, 0, frame.size, this.bufferSize - frame.size); 446 | } 447 | this.buffer.clear(); 448 | this.buffer = nextBuffer; 449 | this.bufferSize -= frame.size; 450 | } 451 | else { 452 | this.buffer.clear(); 453 | this.bufferSize = 0; 454 | } 455 | 456 | switch(frame.opcode) { 457 | case 0x00: // continuation frame 458 | case 0x01: // text frame 459 | case 0x02: // binary frame 460 | if(frame.fin) { 461 | this.emit("message", {data: this._readBuffer + frame.payload}); 462 | this.onmessage({data: this._readBuffer + frame.payload}); 463 | this._readBuffer = ''; 464 | } 465 | else { 466 | this._readBuffer += frame.payload; 467 | } 468 | break; 469 | 470 | case 0x08: // connection close 471 | if(this.readyState === CLOSING) { 472 | this._socket_close(); 473 | } 474 | else { 475 | this.readyState = CLOSING; 476 | this._socket.write(this._create_frame(0x08)); 477 | this._closingTimer = setTimeout(function() { 478 | self._socket_close(); 479 | }, CLOSING_TIMEOUT); 480 | } 481 | break; 482 | 483 | case 0x09: // ping 484 | this._socket.write(this._create_frame(0x0a, frame.payload)); 485 | break; 486 | 487 | case 0x0a: // pong 488 | this._pong_received = true; 489 | break; 490 | } 491 | 492 | this._read_callback(); 493 | } 494 | }; 495 | WebSocket.prototype._error = function(code, reason) { 496 | if(this.buffer) { 497 | this.buffer.clear(); 498 | } 499 | this.buffer = undefined; 500 | this.bufferSize = 0; 501 | 502 | this.readyState = CLOSED; 503 | if(this._socket) { 504 | try { 505 | this._socket.close(); 506 | } 507 | catch(e) { } 508 | this._socket = undefined; 509 | } 510 | var ev = { 511 | wasClean: true, 512 | code: ('undefined' === typeof code) ? 1000 : code, 513 | advice: "reconnect", 514 | reason: reason 515 | }; 516 | this.emit("error", ev); 517 | this.onerror(ev); 518 | }; 519 | 520 | WebSocket.prototype._raise_protocol_error = function(reason) { 521 | this._error(1002, reason); 522 | }; 523 | 524 | WebSocket.prototype.close = function(code, message) { 525 | if(this.readyState === OPEN) { 526 | this.readyState = CLOSING; 527 | 528 | var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 529 | 530 | Ti.Codec.encodeNumber({ 531 | source: code || 1000, 532 | dest: buffer, 533 | position: 0, 534 | type: Ti.Codec.TYPE_SHORT, 535 | byteOrder: Ti.Codec.BIG_ENDIAN 536 | }); 537 | 538 | if(message) { 539 | var length = Ti.Codec.encodeString({ 540 | source: message, 541 | dest: buffer, 542 | destPosition: 2 543 | }); 544 | buffer.length = 2 + length; 545 | } 546 | else { 547 | buffer.length = 2; 548 | } 549 | 550 | var payload = Ti.Codec.decodeString({ 551 | source: buffer, 552 | charset: Ti.Codec.CHARSET_ASCII 553 | }); 554 | this._socket.write(this._create_frame(0x08, payload)); 555 | 556 | var self = this; 557 | this._closingTimer = setTimeout(function() { 558 | self._socket_close(); 559 | }, CLOSING_TIMEOUT); 560 | } 561 | }; 562 | 563 | WebSocket.prototype._connect = function() { 564 | if(this.readyState === OPEN || this.readyState === CLOSING) { 565 | return false; 566 | } 567 | 568 | var self = this; 569 | this._socket = Ti.Network.Socket.createTCP({ 570 | host: this._host, 571 | port: this._port, 572 | mode: Ti.Network.READ_WRITE_MODE, 573 | connected: function(e) { 574 | var result; 575 | result = self._send_handshake(); 576 | if(!result) { 577 | return self._raise_protocol_error("send handshake"); 578 | } 579 | 580 | result = self._read_http_headers(); 581 | if(!result) { 582 | return self._raise_protocol_error("parse http header"); 583 | } 584 | 585 | result = self._check_handshake_response(); 586 | if(!result) { 587 | return self._raise_protocol_error("wrong handshake"); 588 | } 589 | 590 | self._readBuffer = ''; 591 | self._socketReadBuffer = Ti.createBuffer({ length: BUFFER_SIZE }); 592 | 593 | self.readyState = OPEN; 594 | self.emit("open"); 595 | self.onopen(); 596 | 597 | self._read_callback(); 598 | }, 599 | closed: function() { 600 | self._socket_close(); 601 | if(self.buffer) { 602 | self.buffer.clear(); 603 | } 604 | self.buffer = undefined; 605 | self.bufferSize = 0; 606 | }, 607 | error: function(e) { 608 | var reason; 609 | if('undefined' !== typeof e) { 610 | reason = e.error; 611 | } 612 | self._error(1000, reason); 613 | } 614 | }); 615 | this._socket.connect(); 616 | }; 617 | 618 | -------------------------------------------------------------------------------- /ti-websocket-client.js: -------------------------------------------------------------------------------- 1 | var SHA1 = (function(){var exports={};/* 2 | * Modified by Yuichiro MASUI 3 | * Tested on nodejs and Titanium Mobile 4 | * 5 | * The JavaScript implementation of the Secure Hash Algorithm 1 6 | * 7 | * Copyright (c) 2008 Takanori Ishikawa 8 | * All rights reserved. 9 | * 10 | * Redistribution and use in source and binary forms, with or without 11 | * modification, are permitted provided that the following conditions 12 | * are met: 13 | * 14 | * 1. Redistributions of source code must retain the above copyright 15 | * notice, this list of conditions and the following disclaimer. 16 | * 17 | * 2. Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * 21 | * 3. Neither the name of the authors nor the names of its contributors 22 | * may be used to endorse or promote products derived from this 23 | * software without specific prior written permission. 24 | * 25 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 26 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 27 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 28 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 29 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 30 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 31 | * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 32 | * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 33 | * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 34 | * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 35 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 36 | */ 37 | /** 38 | * This is the javascript file for code which implements 39 | * the Secure Hash Algorithm 1 as defined in FIPS 180-1 published April 17, 1995. 40 | * 41 | * Author: Takanori Ishikawa 42 | * Copyright: Takanori Ishikawa 2008 43 | * License: BSD License (see above) 44 | * 45 | * NOTE: 46 | * Only 8-bit string is supported, please use encodeURIComponent() function 47 | * if you want to hash multibyte string. 48 | * 49 | * Supported Browsers: 50 | * [Win] IE 6, Firefox 2 51 | * [Mac] Safari 3, Firefox 2 52 | * 53 | * Usage: 54 | * var hexdigest = new SHA1("Hello.").hexdigest(); // "9b56d519ccd9e1e5b2a725e186184cdc68de0731" 55 | * 56 | * See Also: 57 | * FIPS 180-1 - Secure Hash Standard 58 | * http://www.itl.nist.gov/fipspubs/fip180-1.htm 59 | * 60 | */ 61 | 62 | var SHA1 = (function(){ 63 | 64 | /** 65 | * Spec is the BDD style test utilities. 66 | */ 67 | var Spec; 68 | Spec = { 69 | /** Replace the Spec.describe function with empty function if false. */ 70 | enabled: true, 71 | 72 | /** Indicates whether object 'a' is "equal to" 'b'. */ 73 | equals: function(a, b) { 74 | var i; 75 | if (a instanceof Array && b instanceof Array) { 76 | if (a.length !== b.length) { return false; } 77 | for (i = 0; i < a.length; i++) { if (!Spec.equals(a[i], b[i])) { return false; } } 78 | return true; 79 | } 80 | if ((a !== null && b !== null) && (typeof a === "object" && typeof b === "object")) { 81 | for (i in a) { if(a.hasOwnProperty(i)) { if (!Spec.equals(a[i], b[i])) { return false; } } } 82 | return true; 83 | } 84 | return (a === b); 85 | }, 86 | 87 | /** equivalent to xUint's assert */ 88 | should: function(expection, message) { 89 | Spec.currentIndicator++; 90 | if (!expection) { 91 | var warning = [ 92 | "[Spec failed", 93 | Spec.currentTitle ? " (" + Spec.currentTitle + ")] " : "] ", 94 | (message || (Spec.currentMessage + " " + Spec.currentIndicator) || "") 95 | ].join(""); 96 | 97 | alert(warning); 98 | throw warning; 99 | } 100 | return !!expection; 101 | }, 102 | 103 | /** Write your specification by using describe method. */ 104 | describe: function(title, spec) { 105 | Spec.currentTitle = title; 106 | var name; 107 | for (name in spec) { 108 | if (spec.hasOwnProperty(name)) { 109 | Spec.currentMessage = name; 110 | Spec.currentIndicator = 0; 111 | spec[name](); 112 | Spec.currentIndicator = null; 113 | } 114 | } 115 | Spec.currentMessage = Spec.currentTitle = null; 116 | }, 117 | Version: "0.1" 118 | }; 119 | 120 | // Other BDD style stuffs. 121 | Spec.should.equal = function(a, b, message) { return Spec.should(Spec.equals(a, b), message); }; 122 | Spec.should.not = function(a, message) { return Spec.should(!a, message); }; 123 | Spec.should.not.equal = function(a, b, message) { return Spec.should(!Spec.equals(a, b), message); }; 124 | if (!Spec.enabled) { Spec.describe = function(){}; } 125 | 126 | 127 | // self test 128 | Spec.describe("Spec object", { 129 | "should": function() { 130 | Spec.should(true); 131 | Spec.should(1); 132 | }, 133 | "should.not": function() { 134 | Spec.should.not(false); 135 | Spec.should.not(0); 136 | }, 137 | "should.equal": function() { 138 | Spec.should.equal(null, null); 139 | Spec.should.equal("", ""); 140 | Spec.should.equal(12345, 12345); 141 | Spec.should.equal([0,1,2], [0,1,2]); 142 | Spec.should.equal([0,1,[0,1,2]], [0,1,[0,1,2]]); 143 | Spec.should.equal({}, {}); 144 | Spec.should.equal({x:1}, {x:1}); 145 | Spec.should.equal({x:[1]}, {x:[1]}); 146 | }, 147 | "should.not.equal": function() { 148 | Spec.should.not.equal([1,2,3], [1,2,3,4]); 149 | Spec.should.not.equal({x:1}, [1,2,3,4]); 150 | } 151 | }); 152 | 153 | 154 | // ----------------------------------------------------------- 155 | // Utilities 156 | // ----------------------------------------------------------- 157 | // int32 -> hexdigits string (e.g. 0x123 -> '00000123') 158 | function strfhex32(i32) { 159 | i32 &= 0xffffffff; 160 | if (i32 < 0) { i32 += 0x100000000; } 161 | var hex = Number(i32).toString(16); 162 | if (hex.length < 8) { hex = "00000000".substr(0, 8 - hex.length) + hex; } 163 | return hex; 164 | } 165 | Spec.describe("sha1", { 166 | "strfhex32": function() { 167 | Spec.should.equal(strfhex32(0x0), "00000000"); 168 | Spec.should.equal(strfhex32(0x123), "00000123"); 169 | Spec.should.equal(strfhex32(0xffffffff), "ffffffff"); 170 | } 171 | }); 172 | /* 173 | // int32 -> string (e.g. 123 -> '00000000 00000000 00000000 01111011') 174 | function strfbits(i32) { 175 | if (typeof arguments.callee.ZERO32 === 'undefined') { 176 | arguments.callee.ZERO32 = new Array(33).join("0"); 177 | } 178 | 179 | var bits = Number(i32).toString(2); 180 | // '0' padding 181 | if (bits.length < 32) bits = arguments.callee.ZERO32.substr(0, 32 - bits.length) + bits; 182 | // split by 8 bits 183 | return bits.replace(/(¥d{8})/g, '$1 ') 184 | .replace(/^¥s*(.*?)¥s*$/, '$1'); 185 | } 186 | Spec.describe("sha1", { 187 | "strfbits": function() { 188 | Ti.API.info(strfbits(0)); 189 | Ti.API.info(strfbits(1)); 190 | Ti.API.info(strfbits(123)); 191 | Spec.should.equal(strfbits(0), "00000000 00000000 00000000 00000000"); 192 | Spec.should.equal(strfbits(1), "00000000 00000000 00000000 00000001"); 193 | Spec.should.equal(strfbits(123), "00000000 00000000 00000000 01111011"); 194 | } 195 | }); 196 | */ 197 | 198 | // ----------------------------------------------------------- 199 | // SHA-1 200 | // ----------------------------------------------------------- 201 | // Returns Number(32bit unsigned integer) array size to fit for blocks (512-bit strings) 202 | function padding_size(nbits) { 203 | var n = nbits + 1 + 64; 204 | return 512 * Math.ceil(n / 512) / 32; 205 | } 206 | Spec.describe("sha1", { 207 | "padding_size": function() { 208 | Spec.should.equal(padding_size(0), 16); 209 | Spec.should.equal(padding_size(1), 16); 210 | Spec.should.equal(padding_size(512 - 64 - 1), 16); 211 | Spec.should.equal(padding_size(512 - 64), 32); 212 | } 213 | }); 214 | 215 | // 8bit string -> uint32[] 216 | function word_array(m) { 217 | var nchar = m.length; 218 | var size = padding_size(nchar * 8); 219 | var words = new Array(size); 220 | var i; 221 | for (i = 0, j = 0; i < nchar; ) { 222 | words[j++] = ((m.charCodeAt(i++) & 0xff) << 24) | 223 | ((m.charCodeAt(i++) & 0xff) << 16) | 224 | ((m.charCodeAt(i++) & 0xff) << 8) | 225 | ((m.charCodeAt(i++) & 0xff)); 226 | } 227 | while (j < size) { words[j++] = 0; } 228 | return words; 229 | } 230 | Spec.describe("sha1", { 231 | "word_array": function() { 232 | Spec.should.equal(word_array(""), [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]); 233 | Spec.should.equal(word_array("1234")[0], 0x31323334); 234 | } 235 | }); 236 | 237 | function write_nbits(words, length, nbits) { 238 | if (nbits > 0xffffffff) { 239 | var lo = nbits & 0xffffffff; 240 | if (lo < 0) { lo += 0x100000000; } 241 | words[length - 1] = lo; 242 | words[length - 2] = (nbits - lo) / 0x100000000; 243 | } else { 244 | words[length - 1] = nbits; 245 | words[length - 2] = 0x0; 246 | } 247 | return words; 248 | } 249 | Spec.describe("sha1", { 250 | "write_nbits": function() { 251 | Spec.should.equal(write_nbits([0, 0], 2, 1), [0, 1]); 252 | Spec.should.equal(write_nbits([0, 0], 2, 0xffffffff), [0, 0xffffffff]); 253 | Spec.should.equal(write_nbits([0, 0], 2, 0x100000000), [1, 0]); 254 | Spec.should.equal(write_nbits([0, 0], 2, 0x1ffffffff), [1, 0xffffffff]); 255 | Spec.should.equal(write_nbits([0, 0], 2, 0x12300000000), [0x123, 0]); 256 | Spec.should.equal(write_nbits([0, 0], 2, 0x123abcdef12), [0x123, 0xabcdef12]); 257 | } 258 | }); 259 | 260 | function padding(words, nbits) { 261 | var i = Math.floor(nbits / 32); 262 | 263 | words[i] |= (1 << (((i + 1) * 32) - nbits - 1)); 264 | write_nbits(words, padding_size(nbits), nbits); 265 | return words; 266 | } 267 | 268 | function digest(words) { 269 | var i = 0, t = 0; 270 | var H = [0x67452301, 0xEFCDAB89, 0x98BADCFE, 0x10325476, 0xC3D2E1F0]; 271 | 272 | while (i < words.length) { 273 | var W = new Array(80); 274 | 275 | // (a) 276 | for (t = 0; t < 16; t++) { W[t] = words[i++]; } 277 | 278 | // (b) 279 | for (t = 16; t < 80; t++) { 280 | var w = W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16]; 281 | W[t] = (w << 1) | (w >>> 31); 282 | } 283 | 284 | // (c) 285 | var A = H[0], B = H[1], C = H[2], D = H[3], E = H[4]; 286 | 287 | // (d) TEMP = S5(A) + ft(B,C,D) + E + Wt + Kt; 288 | // E = D; D = C; C = S30(B); B = A; A = TEMP; 289 | for (t = 0; t < 80; t++) { 290 | var tmp = ((A << 5) | (A >>> 27)) + E + W[t]; 291 | 292 | if (t >= 0 && t <= 19) { tmp += ((B & C) | ((~B) & D)) + 0x5a827999; } 293 | else if (t >= 20 && t <= 39) { tmp += (B ^ C ^ D) + 0x6ed9eba1; } 294 | else if (t >= 40 && t <= 59) { tmp += ((B & C) | (B & D) | (C & D)) + 0x8f1bbcdc; } 295 | else if (t >= 60 && t <= 79) { tmp += (B ^ C ^ D) + 0xca62c1d6; } 296 | 297 | E = D; D = C; C = ((B << 30) | (B >>> 2)); B = A; A = tmp; 298 | } 299 | 300 | // (e) H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E. 301 | H[0] = (H[0] + A) & 0xffffffff; 302 | H[1] = (H[1] + B) & 0xffffffff; 303 | H[2] = (H[2] + C) & 0xffffffff; 304 | H[3] = (H[3] + D) & 0xffffffff; 305 | H[4] = (H[4] + E) & 0xffffffff; 306 | if (H[0] < 0) { H[0] += 0x100000000; } 307 | if (H[1] < 0) { H[1] += 0x100000000; } 308 | if (H[2] < 0) { H[2] += 0x100000000; } 309 | if (H[3] < 0) { H[3] += 0x100000000; } 310 | if (H[4] < 0) { H[4] += 0x100000000; } 311 | } 312 | 313 | return H; 314 | } 315 | 316 | // message: 8bit string 317 | var SHA1 = function(message) { 318 | this.message = message; 319 | }; 320 | 321 | function strfhex8(i8) { 322 | i8 &= 0xff; 323 | if (i8 < 0) { i8 += 0x100; } 324 | var hex = Number(i8).toString(16); 325 | if (hex.length < 2) { hex = "00".substr(0, 2 - hex.length) + hex; } 326 | return hex; 327 | } 328 | 329 | 330 | _base64_keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="; 331 | SHA1.prototype = { 332 | digest: function() { 333 | var nbits = this.message.length * 8; 334 | var words = padding(word_array(this.message), nbits); 335 | return digest(words); 336 | }, 337 | 338 | base64digest: function() { 339 | var hex = this.hexdigest(); 340 | var output = ""; 341 | var chr1, chr2, chr3, enc1, enc2, enc3, enc4; 342 | var i = 0; 343 | while (i < hex.length) { 344 | chr1 = parseInt(hex.substring(i, i+2), 16); 345 | chr2 = parseInt(hex.substring(i+2, i+4), 16); 346 | chr3 = parseInt(hex.substring(i+4, i+6), 16); 347 | 348 | enc1 = chr1 >> 2; 349 | enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); 350 | enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); 351 | enc4 = chr3 & 63; 352 | 353 | if (isNaN(chr2)) { 354 | enc3 = enc4 = 64; 355 | } else if (isNaN(chr3)) { 356 | enc4 = 64; 357 | } 358 | 359 | output = output + 360 | _base64_keyStr.charAt(enc1) + _base64_keyStr.charAt(enc2) + 361 | _base64_keyStr.charAt(enc3) + _base64_keyStr.charAt(enc4); 362 | i += 6; 363 | } 364 | 365 | return output; 366 | }, 367 | 368 | hexdigest: function() { 369 | var digest = this.digest(); 370 | var i; 371 | for (i = 0; i < digest.length; i++) { digest[i] = strfhex32(digest[i]); } 372 | return digest.join(""); 373 | } 374 | }; 375 | 376 | Spec.describe("sha1", { 377 | "SHA1#hexdigest": function() { 378 | Spec.should.equal(new SHA1("").hexdigest(), "da39a3ee5e6b4b0d3255bfef95601890afd80709"); 379 | Spec.should.equal(new SHA1("1").hexdigest(), "356a192b7913b04c54574d18c28d46e6395428ab"); 380 | Spec.should.equal(new SHA1("Hello.").hexdigest(), "9b56d519ccd9e1e5b2a725e186184cdc68de0731"); 381 | Spec.should.equal(new SHA1("9b56d519ccd9e1e5b2a725e186184cdc68de0731").hexdigest(), "f042dc98a62cbad68dbe21f11bbc1e9d416d2bf6"); 382 | Spec.should.equal(new SHA1("MD5abZRVSXZVRcasdfasdddddddddddddddds+BNRJFSLKJFN+SEONBBJFJXLKCJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wurJFSE)RUNVXDLILKVJRN)#NVFJ)WVFWRW#)NVS$Q=$dddddddddddddWV;no9wuraddddddasdfasdfd").hexdigest(), "662dbf4ebc9cdb4224766e87634e5ba9e6de672b"); 383 | } 384 | }); 385 | 386 | return SHA1; 387 | }()); 388 | 389 | exports.SHA1 = SHA1; // add for node.js 390 | return exports;}()).SHA1; 391 | var Utils = (function(){var exports={}; 392 | exports.read_byte = function(buffer, position) { 393 | var data = Ti.Codec.decodeNumber({ 394 | source: buffer, 395 | position: position || 0, 396 | type: Ti.Codec.TYPE_BYTE, 397 | byteOrder: Ti.Codec.BIG_ENDIAN 398 | }); 399 | if(data < 0) { data += 256; } //2**8; 400 | return data; 401 | }; 402 | 403 | exports.read_2byte = function(buffer, position) { 404 | var data = Ti.Codec.decodeNumber({ 405 | source: buffer, 406 | position: position || 0, 407 | type: Ti.Codec.TYPE_SHORT, 408 | byteOrder: Ti.Codec.BIG_ENDIAN 409 | }); 410 | if(data < 0) { data += 65536; } // 2**16 411 | return data; 412 | }; 413 | 414 | exports.read_8byte = function(buffer, position) { 415 | var data = Ti.Codec.decodeNumber({ 416 | source: buffer, 417 | position: position || 0, 418 | type: Ti.Codec.TYPE_LONG, 419 | byteOrder: Ti.Codec.BIG_ENDIAN 420 | 421 | }); 422 | if(data < 0) { data += 18446744073709551616; } // 2**64 423 | return data; 424 | }; 425 | 426 | exports.byte_length = function(str) { 427 | var buffer = Ti.createBuffer({length: 65536}); 428 | var length = Ti.Codec.encodeString({ 429 | source: str, 430 | dest: buffer 431 | }); 432 | return length; 433 | }; 434 | 435 | exports.trim = function(str) { 436 | return String(str).replace(/^\s+|\s+$/g, ""); 437 | }; 438 | return exports;}()); 439 | var events = (function(){var exports={};// Copyright Joyent, Inc. and other Node contributors. 440 | // 441 | // Permission is hereby granted, free of charge, to any person obtaining a 442 | // copy of this software and associated documentation files (the 443 | // "Software"), to deal in the Software without restriction, including 444 | // without limitation the rights to use, copy, modify, merge, publish, 445 | // distribute, sublicense, and/or sell copies of the Software, and to permit 446 | // persons to whom the Software is furnished to do so, subject to the 447 | // following conditions: 448 | // 449 | // The above copyright notice and this permission notice shall be included 450 | // in all copies or substantial portions of the Software. 451 | // 452 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 453 | // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 454 | // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 455 | // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 456 | // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 457 | // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 458 | // USE OR OTHER DEALINGS IN THE SOFTWARE. 459 | 460 | var isArray = Array.isArray; 461 | 462 | function EventEmitter() { } 463 | exports.EventEmitter = EventEmitter; 464 | 465 | // By default EventEmitters will print a warning if more than 466 | // 10 listeners are added to it. This is a useful default which 467 | // helps finding memory leaks. 468 | // 469 | // Obviously not all Emitters should be limited to 10. This function allows 470 | // that to be increased. Set to zero for unlimited. 471 | var defaultMaxListeners = 10; 472 | EventEmitter.prototype.setMaxListeners = function(n) { 473 | if (!this._events) { this._events = {}; } 474 | this._maxListeners = n; 475 | }; 476 | 477 | 478 | EventEmitter.prototype.emit = function() { 479 | var type = arguments[0]; 480 | 481 | if (!this._events) { return false; } 482 | var handler = this._events[type]; 483 | if (!handler) { return false; } 484 | 485 | var args, l, i; 486 | if (typeof handler === 'function') { 487 | switch (arguments.length) { 488 | // fast cases 489 | case 1: 490 | handler.call(this); 491 | break; 492 | case 2: 493 | handler.call(this, arguments[1]); 494 | break; 495 | case 3: 496 | handler.call(this, arguments[1], arguments[2]); 497 | break; 498 | // slower 499 | default: 500 | l = arguments.length; 501 | args = new Array(l - 1); 502 | for (i = 1; i < l; i++) { args[i - 1] = arguments[i]; } 503 | handler.apply(this, args); 504 | } 505 | return true; 506 | 507 | } else if (isArray(handler)) { 508 | l = arguments.length; 509 | args = new Array(l - 1); 510 | for (i = 1; i < l; i++) { args[i - 1] = arguments[i]; } 511 | 512 | var listeners = handler.slice(); 513 | for (i = 0, l = listeners.length; i < l; i++) { 514 | listeners[i].apply(this, args); 515 | } 516 | return true; 517 | 518 | } else { 519 | return false; 520 | } 521 | }; 522 | 523 | // EventEmitter is defined in src/node_events.cc 524 | // EventEmitter.prototype.emit() is also defined there. 525 | EventEmitter.prototype.addListener = function(type, listener) { 526 | if ('function' !== typeof listener) { 527 | throw new Error('addListener only takes instances of Function'); 528 | } 529 | 530 | if (!this._events) { this._events = {}; } 531 | 532 | // To avoid recursion in the case that type === "newListeners"! Before 533 | // adding it to the listeners, first emit "newListeners". 534 | this.emit('newListener', type, listener); 535 | 536 | if (!this._events[type]) { 537 | // Optimize the case of one listener. Don't need the extra array object. 538 | this._events[type] = listener; 539 | } else if (isArray(this._events[type])) { 540 | 541 | // If we've already got an array, just append. 542 | this._events[type].push(listener); 543 | 544 | // Check for listener leak 545 | if (!this._events[type].warned) { 546 | var m; 547 | if (this._maxListeners !== undefined) { 548 | m = this._maxListeners; 549 | } else { 550 | m = defaultMaxListeners; 551 | } 552 | 553 | if (m && m > 0 && this._events[type].length > m) { 554 | this._events[type].warned = true; 555 | console.error('(node) warning: possible EventEmitter memory ' + 556 | 'leak detected. %d listeners added. ' + 557 | 'Use emitter.setMaxListeners() to increase limit.', 558 | this._events[type].length); 559 | console.trace(); 560 | } 561 | } 562 | } else { 563 | // Adding the second element, need to change to array. 564 | this._events[type] = [this._events[type], listener]; 565 | } 566 | 567 | return this; 568 | }; 569 | 570 | EventEmitter.prototype.on = EventEmitter.prototype.addListener; 571 | 572 | EventEmitter.prototype.once = function(type, listener) { 573 | if ('function' !== typeof listener) { 574 | throw new Error('.once only takes instances of Function'); 575 | } 576 | 577 | var self = this; 578 | function g() { 579 | self.removeListener(type, g); 580 | listener.apply(this, arguments); 581 | } 582 | 583 | g.listener = listener; 584 | self.on(type, g); 585 | 586 | return this; 587 | }; 588 | 589 | EventEmitter.prototype.removeListener = function(type, listener) { 590 | if ('function' !== typeof listener) { 591 | throw new Error('removeListener only takes instances of Function'); 592 | } 593 | 594 | // does not use listeners(), so no side effect of creating _events[type] 595 | if (!this._events || !this._events[type]) { return this; } 596 | 597 | var list = this._events[type]; 598 | 599 | if (isArray(list)) { 600 | var i, position = -1; 601 | for (i = 0, length = list.length; i < length; i++) { 602 | if (list[i] === listener || 603 | (list[i].listener && list[i].listener === listener)) 604 | { 605 | position = i; 606 | break; 607 | } 608 | } 609 | 610 | if (position < 0) { return this; } 611 | list.splice(position, 1); 612 | if (list.length === 0) { 613 | delete this._events[type]; 614 | } 615 | } else if (list === listener || 616 | (list.listener && list.listener === listener)) 617 | { 618 | delete this._events[type]; 619 | } 620 | 621 | return this; 622 | }; 623 | 624 | EventEmitter.prototype.removeAllListeners = function(type) { 625 | if (arguments.length === 0) { 626 | this._events = {}; 627 | return this; 628 | } 629 | 630 | // does not use listeners(), so no side effect of creating _events[type] 631 | if (type && this._events && this._events[type]) { this._events[type] = null; } 632 | return this; 633 | }; 634 | 635 | EventEmitter.prototype.listeners = function(type) { 636 | if (!this._events) { this._events = {}; } 637 | if (!this._events[type]) { this._events[type] = []; } 638 | if (!isArray(this._events[type])) { 639 | this._events[type] = [this._events[type]]; 640 | } 641 | return this._events[type]; 642 | }; 643 | return exports;}()); 644 | 645 | var debug = function(str) { 646 | Ti.API.debug(str); 647 | }; 648 | 649 | var CONNECTING = 0; 650 | var OPEN = 1; 651 | var CLOSING = 2; 652 | var CLOSED = 3; 653 | 654 | var BUFFER_SIZE = 65536; 655 | var CLOSING_TIMEOUT = 1000; 656 | 657 | var WebSocket = function(url, protocols, origin, extensions) { 658 | this.url = url; 659 | if(!this._parse_url()) { 660 | throw "Wrong url scheme for WebSocket: " + this.url; 661 | } 662 | 663 | this.origin = origin || String.format("http://%s:%s/", this._host, this._port); 664 | this.protocols = protocols; 665 | this.extensions = extensions; 666 | 667 | this.readyState = CONNECTING; 668 | 669 | this._masking_disabled = false; 670 | this._headers = []; 671 | this._pong_received = false; 672 | this._readBuffer = ''; 673 | this._socketReadBuffer = undefined; 674 | this._closingTimer = undefined; 675 | this._handshake = undefined; 676 | 677 | this._socket = undefined; 678 | 679 | this._connect(); 680 | }; 681 | exports.WebSocket = WebSocket; 682 | WebSocket.prototype = new events.EventEmitter(); 683 | 684 | WebSocket.prototype.onopen = function() { 685 | // NO OP 686 | }; 687 | 688 | WebSocket.prototype.onmessage = function() { 689 | // NO OP 690 | }; 691 | 692 | WebSocket.prototype.onerror = function() { 693 | // NO OP 694 | }; 695 | 696 | WebSocket.prototype.onclose = function() { 697 | // NO OP 698 | }; 699 | 700 | WebSocket.prototype._parse_url = function() { 701 | var parsed = this.url.match(/^([a-z]+):\/\/([\w.]+)(:(\d+)|)(.*)/i); 702 | if(!parsed || parsed[1] !== 'ws') { 703 | return false; 704 | } 705 | this._host = parsed[2]; 706 | this._port = parsed[4] || 80; 707 | this._path = parsed[5]; 708 | 709 | return true; 710 | }; 711 | 712 | var make_handshake_key = function() { 713 | var i, key = ""; 714 | for(i=0; i<16; ++i) { 715 | key += String.fromCharCode(Math.random()*255+1); 716 | } 717 | return Utils.trim(Ti.Utils.base64encode(key)); 718 | }; 719 | 720 | var make_handshake = function(host, path, origin, protocols, extensions, handshake) { 721 | str = "GET " + path + " HTTP/1.1\r\n"; 722 | str += "Host: " + host + "\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n"; 723 | str += "Sec-WebSocket-Key: " + handshake + "\r\n"; 724 | str += "Origin: " + origin + "\r\n"; 725 | str += "Sec-WebSocket-Origin: " + origin + "\r\n"; 726 | str += "Sec-WebSocket-Version: 7\r\n"; 727 | 728 | if(protocols && protocols.length > 0) { 729 | str += "Sec-WebSocket-Protocol: " + protocols.join(',') + "\r\n"; 730 | } 731 | 732 | if(extensions && extensions.length > 0) { 733 | str += "Sec-WebSocket-Extensions: " + extensions.join(',') + "\r\n"; 734 | } 735 | 736 | // TODO: compression 737 | //if @compression 738 | // extensions << "deflate-application-data" 739 | //end 740 | 741 | return str + "\r\n"; 742 | }; 743 | 744 | WebSocket.prototype._send_handshake = function() { 745 | this._handshake = make_handshake_key(); 746 | var handshake = make_handshake(this._host, this._path, this.origin, this.protocols, this.extensions, this._handshake); 747 | return this._socket.write(Ti.createBuffer({ value: handshake })) > 0; 748 | }; 749 | 750 | WebSocket.prototype._read_http_headers = function() { 751 | var string = ""; 752 | var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 753 | var counter = 10; 754 | while(true) { 755 | var bytesRead = this._socket.read(buffer); 756 | if(bytesRead > 0) { 757 | var lastStringLen = string.length; 758 | string += Ti.Codec.decodeString({ 759 | source: buffer, 760 | charset: Ti.Codec.CHARSET_ASCII 761 | }); 762 | var eoh = string.match(/\r\n\r\n/); 763 | if(eoh) { 764 | var offset = (eoh.index + 4) - lastStringLen; 765 | string = string.substring(0, offset-2); 766 | 767 | this.buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 768 | this.bufferSize = bytesRead - offset; 769 | this.buffer.copy(buffer, 0, offset, this.bufferSize); 770 | break; 771 | } 772 | } 773 | else { 774 | debug("read_http_headers: timeout"); 775 | --counter; 776 | if(counter < 0) { 777 | return false; // Timeout 778 | } 779 | } 780 | buffer.clear(); // clear the buffer before the next read 781 | } 782 | buffer.clear(); 783 | this.headers = string.split("\r\n"); 784 | 785 | return true; 786 | }; 787 | 788 | var extract_headers = function(headers) { 789 | var result = {}; 790 | headers.forEach(function(line) { 791 | var index = line.indexOf(":"); 792 | if(index > 0) { 793 | var key = Utils.trim(line.slice(0, index)); 794 | var value = Utils.trim(line.slice(index + 1)); 795 | result[key] = value; 796 | } 797 | }); 798 | return result; 799 | }; 800 | 801 | var handshake_reponse = function(handshake) { 802 | return (new SHA1(handshake + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")).base64digest(); 803 | }; 804 | 805 | WebSocket.prototype._check_handshake_response = function() { 806 | var version = this.headers.shift(); 807 | if(version !== "HTTP/1.1 101 Switching Protocols") { 808 | // Mismatch protocol version 809 | debug("mismatch protocol version"); 810 | return false; 811 | } 812 | var h = extract_headers(this.headers); 813 | if(!h.Upgrade || !h.Connection || !h['Sec-WebSocket-Accept']) { 814 | return false; 815 | } 816 | if(h.Upgrade.toLowerCase() !== 'websocket' || h.Connection.toLowerCase() !== 'upgrade' || h['Sec-WebSocket-Accept'] !== handshake_reponse(this._handshake)) { 817 | return false; 818 | } 819 | 820 | // TODO: compression 821 | // if h.has_key?('Sec-WebSocket-Extensions') and h['Sec-WebSocket-Extensions'] === 'deflate-application-data' 822 | // if @compression 823 | // @zout = Zlib::Deflate.new(Zlib::BEST_SPEED, Zlib::MAX_WBITS, 8, 1) 824 | // @zin = Zlib::Inflate.new 825 | // end 826 | // else 827 | // @compression = false 828 | // end 829 | 830 | this.readyState = OPEN; 831 | return true; 832 | }; 833 | 834 | WebSocket.prototype._create_frame = function(opcode, d, last_frame) { 835 | if(typeof last_frame === 'undefined') { 836 | last_frame = true; 837 | } 838 | 839 | if(last_frame === false && opcode >= 0x8 && opcode <= 0xf) { 840 | return false; 841 | } 842 | 843 | // apply per frame compression 844 | var out = Ti.createBuffer({ length: BUFFER_SIZE }); 845 | var outIndex = 0; 846 | 847 | var data = d || ''; //compress(d) // TODO 848 | 849 | var byte1 = opcode; 850 | if(last_frame) { 851 | byte1 = byte1 | 0x80; 852 | } 853 | 854 | Ti.Codec.encodeNumber({ 855 | source: byte1, 856 | dest: out, 857 | position: outIndex++, 858 | type: Ti.Codec.TYPE_BYTE, 859 | }); 860 | 861 | var length = Utils.byte_length(data); 862 | 863 | if(length <= 125) { 864 | var byte2 = length; 865 | if(!this._masking_disabled) { 866 | byte2 = (byte2 | 0x80); // # set masking bit 867 | } 868 | Ti.Codec.encodeNumber({ 869 | source: byte2, 870 | dest: out, 871 | position: outIndex++, 872 | type: Ti.Codec.TYPE_BYTE 873 | }); 874 | } 875 | else if(length < BUFFER_SIZE) { // # write 2 byte length 876 | Ti.Codec.encodeNumber({ 877 | source: (126 | 0x80), 878 | dest: out, 879 | position: outIndex++, 880 | type: Ti.Codec.TYPE_BYTE 881 | }); 882 | Ti.Codec.encodeNumber({ 883 | source: length, 884 | dest: out, 885 | position: outIndex++, 886 | type: Ti.Codec.TYPE_SHORT, 887 | byteOrder: Ti.Codec.BIG_ENDIAN 888 | }); 889 | outIndex += 2; 890 | } 891 | else { // # write 8 byte length 892 | Ti.Codec.encodeNumber({ 893 | source: (127 | 0x80), 894 | dest: out, 895 | position: outIndex++, 896 | type: Ti.Codec.TYPE_BYTE 897 | }); 898 | Ti.Codec.encodeNumber({ 899 | source: length, 900 | dest: out, 901 | position: outIndex, 902 | type: Ti.Codec.TYPE_LONG, 903 | byteOrder: Ti.Codec.BIG_ENDIAN 904 | }); 905 | outIndex += 8; 906 | } 907 | 908 | //# mask data 909 | outIndex = this._mask_payload(out, outIndex, data); 910 | out.length = outIndex; 911 | 912 | return out; 913 | }; 914 | 915 | WebSocket.prototype._mask_payload = function(out, outIndex, payload) { 916 | if(!this._masking_disabled) { 917 | var i, masking_key = []; 918 | for(i = 0; i < 4; ++i) { 919 | var key = Math.floor(Math.random()*255) & 0xff; 920 | masking_key.push(key); 921 | Ti.Codec.encodeNumber({ 922 | source: key, 923 | dest: out, 924 | position: outIndex++, 925 | type: Ti.Codec.TYPE_BYTE 926 | }); 927 | } 928 | 929 | var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 930 | var length = Ti.Codec.encodeString({ 931 | source: payload, 932 | dest: buffer 933 | }); 934 | buffer.length = length; 935 | 936 | var string = Ti.Codec.decodeString({ 937 | source: buffer, 938 | charset: Ti.Codec.CHARSET_ASCII 939 | }); 940 | 941 | var masked_string = ""; 942 | for(i = 0; i < string.length; ++i) { 943 | Ti.Codec.encodeNumber({ 944 | source: string.charCodeAt(i) ^ masking_key[i % 4], 945 | dest: out, 946 | position: outIndex++, 947 | type: Ti.Codec.TYPE_BYTE, 948 | }); 949 | } 950 | return outIndex; 951 | } 952 | else { 953 | var len = Ti.Codec.encodeString({ 954 | source: payload, 955 | dest: out, 956 | destPosition: outIndex 957 | }); 958 | return len + outIndex; 959 | } 960 | }; 961 | 962 | var parse_frame = function(buffer, size) { 963 | if(size < 3) { 964 | return undefined; 965 | } 966 | 967 | var byte1 = Utils.read_byte(buffer, 0); 968 | var fin = !!(byte1 & 0x80); 969 | var opcode = byte1 & 0x0f; 970 | 971 | var byte2 = Utils.read_byte(buffer, 1); 972 | var mask = !!(byte2 & 0x80); 973 | var len = byte2 & 0x7f; 974 | 975 | var offset = 2; 976 | switch(len) { 977 | case 126: 978 | len = Utils.read_2byte(buffer, offset); 979 | offset += 2; 980 | break; 981 | 982 | case 127: 983 | // too large I felt 984 | len = Utils.read_8byte(buffer, offset); 985 | offset += 8; 986 | break; 987 | } 988 | 989 | if(len + offset > size) { 990 | return undefined; 991 | } 992 | 993 | var string = Ti.Codec.decodeString({ 994 | source: buffer, 995 | position: offset, 996 | length: len, 997 | charset: Ti.Codec.CHARSET_UTF8 998 | }); 999 | 1000 | return({fin: fin, opcode: opcode, payload: string, size: len + offset}); 1001 | }; 1002 | 1003 | WebSocket.prototype.send = function(data) { 1004 | if(data && this.readyState === OPEN) { 1005 | var frame = this._create_frame(0x01, data); 1006 | var bytesWritten = this._socket.write(frame); 1007 | return bytesWritten > 0; 1008 | } 1009 | else { 1010 | return false; 1011 | } 1012 | }; 1013 | 1014 | WebSocket.prototype._socket_close = function() { 1015 | if(this._closingTimer) { 1016 | clearTimeout(this._closingTimer); 1017 | } 1018 | this._closingTimer = undefined; 1019 | 1020 | this._readBuffer = ''; 1021 | this._socketReadBuffer = undefined; 1022 | 1023 | var ev; 1024 | if(this.readyState === CLOSING) { 1025 | this.readyState = CLOSED; 1026 | this._socket.close(); 1027 | ev = { 1028 | code: 1000, 1029 | wasClean: true, 1030 | reason: "" 1031 | }; 1032 | this.emit("close", ev); 1033 | this.onclose(ev); 1034 | } 1035 | else if(this.readyState !== CLOSED) { 1036 | this._socket.close(); 1037 | this.readyState = CLOSED; 1038 | ev = { 1039 | advice: "reconnect" 1040 | }; 1041 | this.emit("error", ev); 1042 | this.onerror(ev); 1043 | } 1044 | this._socket = undefined; 1045 | }; 1046 | 1047 | 1048 | WebSocket.prototype._read_callback = function(e) { 1049 | var self = this; 1050 | 1051 | var nextTick = function() { 1052 | self._socketReadBuffer.clear(); 1053 | Ti.Stream.read(self._socket, self._socketReadBuffer, function(e) { self._read_callback(e); }); 1054 | }; 1055 | 1056 | if('undefined' !== typeof e) { 1057 | if (0 === e.bytesProcessed) { 1058 | return nextTick(); 1059 | } 1060 | 1061 | if(-1 === e.bytesProcessed) { // EOF 1062 | this._socket_close(); 1063 | return undefined; 1064 | } 1065 | 1066 | if('undefined' === typeof this.buffer) { 1067 | this.buffer = this._socketReadBuffer.clone(); 1068 | this.bufferSize = e.bytesProcessed; 1069 | } 1070 | else { 1071 | this.buffer.copy(this._socketReadBuffer, this.bufferSize, 0, e.bytesProcessed); 1072 | this.bufferSize += e.bytesProcessed; 1073 | this._socketReadBuffer.clear(); 1074 | } 1075 | } 1076 | 1077 | var frame = parse_frame(this.buffer, this.bufferSize); 1078 | if('undefined' === typeof frame) { 1079 | return nextTick(); 1080 | } 1081 | else { 1082 | if(frame.size < this.bufferSize) { 1083 | var nextBuffer = Ti.createBuffer({ length: BUFFER_SIZE }); 1084 | if(this.bufferSize - frame.size > 0) { 1085 | nextBuffer.copy(this.buffer, 0, frame.size, this.bufferSize - frame.size); 1086 | } 1087 | this.buffer.clear(); 1088 | this.buffer = nextBuffer; 1089 | this.bufferSize -= frame.size; 1090 | } 1091 | else { 1092 | this.buffer.clear(); 1093 | this.bufferSize = 0; 1094 | } 1095 | 1096 | switch(frame.opcode) { 1097 | case 0x00: // continuation frame 1098 | case 0x01: // text frame 1099 | case 0x02: // binary frame 1100 | if(frame.fin) { 1101 | this.emit("message", {data: this._readBuffer + frame.payload}); 1102 | this.onmessage({data: this._readBuffer + frame.payload}); 1103 | this._readBuffer = ''; 1104 | } 1105 | else { 1106 | this._readBuffer += frame.payload; 1107 | } 1108 | break; 1109 | 1110 | case 0x08: // connection close 1111 | if(this.readyState === CLOSING) { 1112 | this._socket_close(); 1113 | } 1114 | else { 1115 | this.readyState = CLOSING; 1116 | this._socket.write(this._create_frame(0x08)); 1117 | this._closingTimer = setTimeout(function() { 1118 | self._socket_close(); 1119 | }, CLOSING_TIMEOUT); 1120 | } 1121 | break; 1122 | 1123 | case 0x09: // ping 1124 | this._socket.write(this._create_frame(0x0a, frame.payload)); 1125 | break; 1126 | 1127 | case 0x0a: // pong 1128 | this._pong_received = true; 1129 | break; 1130 | } 1131 | 1132 | this._read_callback(); 1133 | } 1134 | }; 1135 | WebSocket.prototype._error = function(code, reason) { 1136 | if(this.buffer) { 1137 | this.buffer.clear(); 1138 | } 1139 | this.buffer = undefined; 1140 | this.bufferSize = 0; 1141 | 1142 | this.readyState = CLOSED; 1143 | if(this._socket) { 1144 | try { 1145 | this._socket.close(); 1146 | } 1147 | catch(e) { } 1148 | this._socket = undefined; 1149 | } 1150 | var ev = { 1151 | wasClean: true, 1152 | code: ('undefined' === typeof code) ? 1000 : code, 1153 | advice: "reconnect", 1154 | reason: reason 1155 | }; 1156 | this.emit("error", ev); 1157 | this.onerror(ev); 1158 | }; 1159 | 1160 | WebSocket.prototype._raise_protocol_error = function(reason) { 1161 | this._error(1002, reason); 1162 | }; 1163 | 1164 | WebSocket.prototype.close = function(code, message) { 1165 | if(this.readyState === OPEN) { 1166 | this.readyState = CLOSING; 1167 | 1168 | var buffer = Ti.createBuffer({ length: BUFFER_SIZE }); 1169 | 1170 | Ti.Codec.encodeNumber({ 1171 | source: code || 1000, 1172 | dest: buffer, 1173 | position: 0, 1174 | type: Ti.Codec.TYPE_SHORT, 1175 | byteOrder: Ti.Codec.BIG_ENDIAN 1176 | }); 1177 | 1178 | if(message) { 1179 | var length = Ti.Codec.encodeString({ 1180 | source: message, 1181 | dest: buffer, 1182 | destPosition: 2 1183 | }); 1184 | buffer.length = 2 + length; 1185 | } 1186 | else { 1187 | buffer.length = 2; 1188 | } 1189 | 1190 | var payload = Ti.Codec.decodeString({ 1191 | source: buffer, 1192 | charset: Ti.Codec.CHARSET_ASCII 1193 | }); 1194 | this._socket.write(this._create_frame(0x08, payload)); 1195 | 1196 | var self = this; 1197 | this._closingTimer = setTimeout(function() { 1198 | self._socket_close(); 1199 | }, CLOSING_TIMEOUT); 1200 | } 1201 | }; 1202 | 1203 | WebSocket.prototype._connect = function() { 1204 | if(this.readyState === OPEN || this.readyState === CLOSING) { 1205 | return false; 1206 | } 1207 | 1208 | var self = this; 1209 | this._socket = Ti.Network.Socket.createTCP({ 1210 | host: this._host, 1211 | port: this._port, 1212 | mode: Ti.Network.READ_WRITE_MODE, 1213 | connected: function(e) { 1214 | var result; 1215 | result = self._send_handshake(); 1216 | if(!result) { 1217 | return self._raise_protocol_error("send handshake"); 1218 | } 1219 | 1220 | result = self._read_http_headers(); 1221 | if(!result) { 1222 | return self._raise_protocol_error("parse http header"); 1223 | } 1224 | 1225 | result = self._check_handshake_response(); 1226 | if(!result) { 1227 | return self._raise_protocol_error("wrong handshake"); 1228 | } 1229 | 1230 | self._readBuffer = ''; 1231 | self._socketReadBuffer = Ti.createBuffer({ length: BUFFER_SIZE }); 1232 | 1233 | self.readyState = OPEN; 1234 | self.emit("open"); 1235 | self.onopen(); 1236 | 1237 | self._read_callback(); 1238 | }, 1239 | closed: function() { 1240 | self._socket_close(); 1241 | if(self.buffer) { 1242 | self.buffer.clear(); 1243 | } 1244 | self.buffer = undefined; 1245 | self.bufferSize = 0; 1246 | }, 1247 | error: function(e) { 1248 | var reason; 1249 | if('undefined' !== typeof e) { 1250 | reason = e.error; 1251 | } 1252 | self._error(1000, reason); 1253 | } 1254 | }); 1255 | this._socket.connect(); 1256 | }; 1257 | 1258 | --------------------------------------------------------------------------------