| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | |
| 6 | |
| 7 class _WebSocketMessageType { | |
| 8 static const int NONE = 0; | |
| 9 static const int BINARY = 1; | |
| 10 static const int TEXT = 2; | |
| 11 } | |
| 12 | |
| 13 | |
| 14 class _WebSocketOpcode { | |
| 15 static const int CONTINUATION = 0; | |
| 16 static const int TEXT = 1; | |
| 17 static const int BINARY = 2; | |
| 18 static const int RESERVED_3 = 3; | |
| 19 static const int RESERVED_4 = 4; | |
| 20 static const int RESERVED_5 = 5; | |
| 21 static const int RESERVED_6 = 6; | |
| 22 static const int RESERVED_7 = 7; | |
| 23 static const int CLOSE = 8; | |
| 24 static const int PING = 9; | |
| 25 static const int PONG = 10; | |
| 26 static const int RESERVED_B = 11; | |
| 27 static const int RESERVED_C = 12; | |
| 28 static const int RESERVED_D = 13; | |
| 29 static const int RESERVED_E = 14; | |
| 30 static const int RESERVED_F = 15; | |
| 31 } | |
| 32 | |
| 33 /** | |
| 34 * The web socket protocol processor handles the protocol byte stream | |
| 35 * which is supplied through the [:update:] and [:closed:] | |
| 36 * methods. As the protocol is processed the following callbacks are | |
| 37 * called: | |
| 38 * | |
| 39 * [:onMessageStart:] | |
| 40 * [:onMessageData:] | |
| 41 * [:onMessageEnd:] | |
| 42 * [:onClosed:] | |
| 43 * [:onError:] | |
| 44 * | |
| 45 */ | |
| 46 class _WebSocketProtocolProcessor { | |
| 47 static const int START = 0; | |
| 48 static const int LEN_FIRST = 1; | |
| 49 static const int LEN_REST = 2; | |
| 50 static const int MASK = 3; | |
| 51 static const int PAYLOAD = 4; | |
| 52 static const int CLOSED = 5; | |
| 53 static const int FAILURE = 6; | |
| 54 | |
| 55 _WebSocketProtocolProcessor() { | |
| 56 _prepareForNextFrame(); | |
| 57 _currentMessageType = _WebSocketMessageType.NONE; | |
| 58 } | |
| 59 | |
| 60 /** | |
| 61 * Process data received from the underlying communication channel. | |
| 62 */ | |
| 63 void update(List<int> buffer, int offset, int count) { | |
| 64 int index = offset; | |
| 65 int lastIndex = offset + count; | |
| 66 try { | |
| 67 if (_state == CLOSED) { | |
| 68 throw new WebSocketException("Data on closed connection"); | |
| 69 } | |
| 70 if (_state == FAILURE) { | |
| 71 throw new WebSocketException("Data on failed connection"); | |
| 72 } | |
| 73 while ((index < lastIndex) && _state != CLOSED && _state != FAILURE) { | |
| 74 int byte = buffer[index]; | |
| 75 switch (_state) { | |
| 76 case START: | |
| 77 _fin = (byte & 0x80) != 0; | |
| 78 _opcode = (byte & 0xF); | |
| 79 switch (_opcode) { | |
| 80 case _WebSocketOpcode.CONTINUATION: | |
| 81 if (_currentMessageType == _WebSocketMessageType.NONE) { | |
| 82 throw new WebSocketException("Protocol error"); | |
| 83 } | |
| 84 break; | |
| 85 | |
| 86 case _WebSocketOpcode.TEXT: | |
| 87 if (_currentMessageType != _WebSocketMessageType.NONE) { | |
| 88 throw new WebSocketException("Protocol error"); | |
| 89 } | |
| 90 _currentMessageType = _WebSocketMessageType.TEXT; | |
| 91 if (onMessageStart !== null) { | |
| 92 onMessageStart(_WebSocketMessageType.TEXT); | |
| 93 } | |
| 94 break; | |
| 95 | |
| 96 case _WebSocketOpcode.BINARY: | |
| 97 if (_currentMessageType != _WebSocketMessageType.NONE) { | |
| 98 throw new WebSocketException("Protocol error"); | |
| 99 } | |
| 100 _currentMessageType = _WebSocketMessageType.BINARY; | |
| 101 if (onMessageStart !== null) { | |
| 102 onMessageStart(_WebSocketMessageType.BINARY); | |
| 103 } | |
| 104 break; | |
| 105 | |
| 106 case _WebSocketOpcode.CLOSE: | |
| 107 case _WebSocketOpcode.PING: | |
| 108 case _WebSocketOpcode.PONG: | |
| 109 // Control frames cannot be fragmented. | |
| 110 if (!_fin) throw new WebSocketException("Protocol error"); | |
| 111 break; | |
| 112 | |
| 113 default: | |
| 114 throw new WebSocketException("Protocol error"); | |
| 115 } | |
| 116 _state = LEN_FIRST; | |
| 117 break; | |
| 118 | |
| 119 case LEN_FIRST: | |
| 120 _masked = (byte & 0x80) != 0; | |
| 121 _len = byte & 0x7F; | |
| 122 if (_isControlFrame() && _len > 126) { | |
| 123 throw new WebSocketException("Protocol error"); | |
| 124 } | |
| 125 if (_len < 126) { | |
| 126 _lengthDone(); | |
| 127 } else if (_len == 126) { | |
| 128 _len = 0; | |
| 129 _remainingLenBytes = 2; | |
| 130 _state = LEN_REST; | |
| 131 } else if (_len == 127) { | |
| 132 _len = 0; | |
| 133 _remainingLenBytes = 8; | |
| 134 _state = LEN_REST; | |
| 135 } | |
| 136 break; | |
| 137 | |
| 138 case LEN_REST: | |
| 139 _len = _len << 8 | byte; | |
| 140 _remainingLenBytes--; | |
| 141 if (_remainingLenBytes == 0) { | |
| 142 _lengthDone(); | |
| 143 } | |
| 144 break; | |
| 145 | |
| 146 case MASK: | |
| 147 _maskingKey = _maskingKey << 8 | byte; | |
| 148 _remainingMaskingKeyBytes--; | |
| 149 if (_remainingMaskingKeyBytes == 0) { | |
| 150 _maskDone(); | |
| 151 } | |
| 152 break; | |
| 153 | |
| 154 case PAYLOAD: | |
| 155 // The payload is not handled one byte at a time but in blocks. | |
| 156 int payload; | |
| 157 if (lastIndex - index <= _remainingPayloadBytes) { | |
| 158 payload = lastIndex - index; | |
| 159 } else { | |
| 160 payload = _remainingPayloadBytes; | |
| 161 } | |
| 162 _remainingPayloadBytes -= payload; | |
| 163 | |
| 164 // Unmask payload if masked. | |
| 165 if (_masked) { | |
| 166 for (int i = 0; i < payload; i++) { | |
| 167 int maskingByte = | |
| 168 ((_maskingKey >> ((3 - _unmaskingIndex) * 8)) & 0xFF); | |
| 169 buffer[index + i] = buffer[index + i] ^ maskingByte; | |
| 170 _unmaskingIndex = (_unmaskingIndex + 1) % 4; | |
| 171 } | |
| 172 } | |
| 173 | |
| 174 if (_isControlFrame()) { | |
| 175 if (payload > 0) { | |
| 176 // Allocate a buffer for collecting the control frame | |
| 177 // payload if any. | |
| 178 if (_controlPayload == null) { | |
| 179 _controlPayload = new List<int>(); | |
| 180 } | |
| 181 _controlPayload.addAll(buffer.getRange(index, payload)); | |
| 182 index += payload; | |
| 183 } | |
| 184 | |
| 185 if (_remainingPayloadBytes == 0) { | |
| 186 _controlFrameEnd(); | |
| 187 } | |
| 188 } else { | |
| 189 switch (_currentMessageType) { | |
| 190 case _WebSocketMessageType.NONE: | |
| 191 throw new WebSocketException("Protocol error"); | |
| 192 | |
| 193 case _WebSocketMessageType.TEXT: | |
| 194 case _WebSocketMessageType.BINARY: | |
| 195 if (onMessageData !== null) { | |
| 196 onMessageData(buffer, index, payload); | |
| 197 } | |
| 198 index += payload; | |
| 199 if (_remainingPayloadBytes == 0) { | |
| 200 _messageFrameEnd(); | |
| 201 } | |
| 202 break; | |
| 203 | |
| 204 default: | |
| 205 throw new WebSocketException("Protocol error"); | |
| 206 } | |
| 207 } | |
| 208 | |
| 209 // Hack - as we always do index++ below. | |
| 210 index--; | |
| 211 } | |
| 212 | |
| 213 // Move to the next byte. | |
| 214 index++; | |
| 215 } | |
| 216 } catch (e) { | |
| 217 if (onClosed !== null) onClosed(WebSocketStatus.PROTOCOL_ERROR, | |
| 218 "Protocol error"); | |
| 219 _state = FAILURE; | |
| 220 } | |
| 221 } | |
| 222 | |
| 223 /** | |
| 224 * Indicate that the underlying communication channel has been closed. | |
| 225 */ | |
| 226 void closed() { | |
| 227 if (_state == START || _state == CLOSED || _state == FAILURE) return; | |
| 228 if (onClosed !== null) onClosed(WebSocketStatus.ABNORMAL_CLOSURE, | |
| 229 "Connection closed unexpectedly"); | |
| 230 _state = CLOSED; | |
| 231 } | |
| 232 | |
| 233 void _lengthDone() { | |
| 234 if (_masked) { | |
| 235 _state = MASK; | |
| 236 _remainingMaskingKeyBytes = 4; | |
| 237 } else { | |
| 238 _remainingPayloadBytes = _len; | |
| 239 _startPayload(); | |
| 240 } | |
| 241 } | |
| 242 | |
| 243 void _maskDone() { | |
| 244 _remainingPayloadBytes = _len; | |
| 245 _startPayload(); | |
| 246 } | |
| 247 | |
| 248 void _startPayload() { | |
| 249 // If there is no actual payload perform perform callbacks without | |
| 250 // going through the PAYLOAD state. | |
| 251 if (_remainingPayloadBytes == 0) { | |
| 252 if (_isControlFrame()) { | |
| 253 switch (_opcode) { | |
| 254 case _WebSocketOpcode.CLOSE: | |
| 255 if (onClosed !== null) onClosed(1005, ""); | |
| 256 _state = CLOSED; | |
| 257 break; | |
| 258 case _WebSocketOpcode.PING: | |
| 259 if (onPing !== null) onPing(null); | |
| 260 break; | |
| 261 case _WebSocketOpcode.PONG: | |
| 262 if (onPong !== null) onPong(null); | |
| 263 break; | |
| 264 } | |
| 265 _prepareForNextFrame(); | |
| 266 } else { | |
| 267 _messageFrameEnd(); | |
| 268 } | |
| 269 } else { | |
| 270 _state = PAYLOAD; | |
| 271 } | |
| 272 } | |
| 273 | |
| 274 void _messageFrameEnd() { | |
| 275 if (_fin) { | |
| 276 if (onMessageEnd !== null) onMessageEnd(); | |
| 277 _currentMessageType = _WebSocketMessageType.NONE; | |
| 278 } | |
| 279 _prepareForNextFrame(); | |
| 280 } | |
| 281 | |
| 282 void _controlFrameEnd() { | |
| 283 switch (_opcode) { | |
| 284 case _WebSocketOpcode.CLOSE: | |
| 285 int status = WebSocketStatus.NO_STATUS_RECEIVED; | |
| 286 String reason = ""; | |
| 287 if (_controlPayload.length > 0) { | |
| 288 if (_controlPayload.length == 1) { | |
| 289 throw new WebSocketException("Protocol error"); | |
| 290 } | |
| 291 status = _controlPayload[0] << 8 | _controlPayload[1]; | |
| 292 if (status == WebSocketStatus.NO_STATUS_RECEIVED) { | |
| 293 throw new WebSocketException("Protocol error"); | |
| 294 } | |
| 295 if (_controlPayload.length > 2) { | |
| 296 var decoder = _StringDecoders.decoder(Encoding.UTF_8); | |
| 297 decoder.write( | |
| 298 _controlPayload.getRange(2, _controlPayload.length - 2)); | |
| 299 reason = decoder.decoded(); | |
| 300 } | |
| 301 } | |
| 302 if (onClosed !== null) onClosed(status, reason); | |
| 303 _state = CLOSED; | |
| 304 break; | |
| 305 | |
| 306 case _WebSocketOpcode.PING: | |
| 307 if (onPing !== null) onPing(_controlPayload); | |
| 308 break; | |
| 309 | |
| 310 case _WebSocketOpcode.PONG: | |
| 311 if (onPong !== null) onPong(_controlPayload); | |
| 312 break; | |
| 313 } | |
| 314 _prepareForNextFrame(); | |
| 315 } | |
| 316 | |
| 317 bool _isControlFrame() { | |
| 318 return _opcode == _WebSocketOpcode.CLOSE || | |
| 319 _opcode == _WebSocketOpcode.PING || | |
| 320 _opcode == _WebSocketOpcode.PONG; | |
| 321 } | |
| 322 | |
| 323 void _prepareForNextFrame() { | |
| 324 if (_state != CLOSED && _state != FAILURE) _state = START; | |
| 325 _fin = null; | |
| 326 _opcode = null; | |
| 327 _len = null; | |
| 328 _masked = null; | |
| 329 _maskingKey = 0; | |
| 330 _remainingLenBytes = null; | |
| 331 _remainingMaskingKeyBytes = null; | |
| 332 _remainingPayloadBytes = null; | |
| 333 _unmaskingIndex = 0; | |
| 334 _controlPayload = null; | |
| 335 } | |
| 336 | |
| 337 int _state; | |
| 338 bool _fin; | |
| 339 int _opcode; | |
| 340 int _len; | |
| 341 bool _masked; | |
| 342 int _maskingKey; | |
| 343 int _remainingLenBytes; | |
| 344 int _remainingMaskingKeyBytes; | |
| 345 int _remainingPayloadBytes; | |
| 346 int _unmaskingIndex; | |
| 347 | |
| 348 int _currentMessageType; | |
| 349 List<int> _controlPayload; | |
| 350 | |
| 351 Function onMessageStart; | |
| 352 Function onMessageData; | |
| 353 Function onMessageEnd; | |
| 354 Function onPing; | |
| 355 Function onPong; | |
| 356 Function onClosed; | |
| 357 } | |
| 358 | |
| 359 | |
| 360 class _WebSocketConnectionBase { | |
| 361 void _socketConnected(Socket socket) { | |
| 362 _socket = socket; | |
| 363 _socket.onError = (e) => _socket.close(); | |
| 364 } | |
| 365 | |
| 366 void _startProcessing(List<int> unparsedData) { | |
| 367 _WebSocketProtocolProcessor processor = new _WebSocketProtocolProcessor(); | |
| 368 processor.onMessageStart = _onWebSocketMessageStart; | |
| 369 processor.onMessageData = _onWebSocketMessageData; | |
| 370 processor.onMessageEnd = _onWebSocketMessageEnd; | |
| 371 processor.onPing = _onWebSocketPing; | |
| 372 processor.onPong = _onWebSocketPong; | |
| 373 processor.onClosed = _onWebSocketClosed; | |
| 374 if (unparsedData !== null) { | |
| 375 processor.update(unparsedData, 0, unparsedData.length); | |
| 376 } | |
| 377 _socket.onData = () { | |
| 378 int available = _socket.available(); | |
| 379 List<int> data = new List<int>(available); | |
| 380 int read = _socket.readList(data, 0, available); | |
| 381 processor.update(data, 0, read); | |
| 382 }; | |
| 383 _socket.onClosed = () { | |
| 384 processor.closed(); | |
| 385 if (_closeSent) { | |
| 386 // Got socket close in response to close frame. Don't treat | |
| 387 // that as an error. | |
| 388 if (_closeTimer !== null) _closeTimer.cancel(); | |
| 389 } else { | |
| 390 if (_onClosed !== null) _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, | |
| 391 "Unexpected close"); | |
| 392 } | |
| 393 _socket.close(); | |
| 394 }; | |
| 395 } | |
| 396 | |
| 397 void set onMessage(void callback(Object message)) { | |
| 398 _onMessage = callback; | |
| 399 } | |
| 400 | |
| 401 void set onClosed(void callback(int status, String reason)) { | |
| 402 _onClosed = callback; | |
| 403 } | |
| 404 | |
| 405 send(message) { | |
| 406 if (_closeSent) { | |
| 407 throw new WebSocketException("Connection closed"); | |
| 408 } | |
| 409 List<int> data; | |
| 410 int opcode; | |
| 411 if (message !== null) { | |
| 412 if (message is String) { | |
| 413 opcode = _WebSocketOpcode.TEXT; | |
| 414 data = _StringEncoders.encoder(Encoding.UTF_8).encodeString(message); | |
| 415 } else { | |
| 416 if (message is !List<int>) { | |
| 417 throw new ArgumentError(message); | |
| 418 } | |
| 419 opcode = _WebSocketOpcode.BINARY; | |
| 420 data = message; | |
| 421 } | |
| 422 } else { | |
| 423 opcode = _WebSocketOpcode.TEXT; | |
| 424 } | |
| 425 _sendFrame(opcode, data); | |
| 426 } | |
| 427 | |
| 428 close([int status, String reason]) { | |
| 429 if (status == WebSocketStatus.RESERVED_1004 || | |
| 430 status == WebSocketStatus.NO_STATUS_RECEIVED || | |
| 431 status == WebSocketStatus.RESERVED_1015) { | |
| 432 throw new WebSocketException("Reserved status code $status"); | |
| 433 } | |
| 434 | |
| 435 if (_closeSent) return; | |
| 436 List<int> data; | |
| 437 if (status !== null) { | |
| 438 data = new List<int>(); | |
| 439 data.add((status >> 8) & 0xFF); | |
| 440 data.add(status & 0xFF); | |
| 441 if (reason !== null) { | |
| 442 data.addAll( | |
| 443 _StringEncoders.encoder(Encoding.UTF_8).encodeString(reason)); | |
| 444 } | |
| 445 } | |
| 446 _sendFrame(_WebSocketOpcode.CLOSE, data); | |
| 447 | |
| 448 if (_closeReceived) { | |
| 449 // Close the socket when the close frame has been sent - if it | |
| 450 // does not take too long. | |
| 451 _socket.outputStream.close(); | |
| 452 _socket.outputStream.onClosed = () { | |
| 453 if (_closeTimer !== null) _closeTimer.cancel(); | |
| 454 _socket.close(); | |
| 455 }; | |
| 456 _closeTimer = new Timer(5000, (t) { | |
| 457 _socket.close(); | |
| 458 }); | |
| 459 } else { | |
| 460 // Half close the socket and expect a close frame in response | |
| 461 // before closing the socket. If a close frame does not arrive | |
| 462 // within a reasonable amount of time just close the socket. | |
| 463 _socket.outputStream.close(); | |
| 464 _closeTimer = new Timer(5000, (t) { | |
| 465 _socket.close(); | |
| 466 }); | |
| 467 } | |
| 468 _closeSent = true; | |
| 469 } | |
| 470 | |
| 471 int get hashCode => _hash; | |
| 472 | |
| 473 _onWebSocketMessageStart(int type) { | |
| 474 _currentMessageType = type; | |
| 475 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
| 476 _decoder = _StringDecoders.decoder(Encoding.UTF_8); | |
| 477 } else { | |
| 478 _outputStream = new ListOutputStream(); | |
| 479 } | |
| 480 } | |
| 481 | |
| 482 _onWebSocketMessageData(List<int> buffer, int offset, int count) { | |
| 483 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
| 484 _decoder.write(buffer.getRange(offset, count)); | |
| 485 } else { | |
| 486 _outputStream.write(buffer.getRange(offset, count)); | |
| 487 } | |
| 488 } | |
| 489 | |
| 490 _onWebSocketMessageEnd() { | |
| 491 if (_onMessage !== null) { | |
| 492 if (_currentMessageType == _WebSocketMessageType.TEXT) { | |
| 493 _onMessage(_decoder.decoded()); | |
| 494 } else { | |
| 495 _onMessage(_outputStream.read()); | |
| 496 } | |
| 497 } | |
| 498 _decoder = null; | |
| 499 _outputStream = null; | |
| 500 } | |
| 501 | |
| 502 _onWebSocketPing(List<int> payload) { | |
| 503 _sendFrame(_WebSocketOpcode.PONG, payload); | |
| 504 } | |
| 505 | |
| 506 _onWebSocketPong(List<int> payload) { | |
| 507 // Currently pong messages are ignored. | |
| 508 } | |
| 509 | |
| 510 _onWebSocketClosed(int status, String reason) { | |
| 511 _closeReceived = true; | |
| 512 if (_onClosed !== null) _onClosed(status, reason); | |
| 513 if (_closeSent) { | |
| 514 // Got close frame in response to close frame. Now close the socket. | |
| 515 if (_closeTimer !== null) _closeTimer.cancel(); | |
| 516 _socket.close(); | |
| 517 } else { | |
| 518 if (status != WebSocketStatus.NO_STATUS_RECEIVED) { | |
| 519 close(status); | |
| 520 } else { | |
| 521 close(); | |
| 522 } | |
| 523 } | |
| 524 } | |
| 525 | |
| 526 _sendFrame(int opcode, [List<int> data]) { | |
| 527 bool mask = false; // Masking not implemented for server. | |
| 528 int dataLength = data == null ? 0 : data.length; | |
| 529 // Determine the header size. | |
| 530 int headerSize = (mask) ? 6 : 2; | |
| 531 if (dataLength > 65535) { | |
| 532 headerSize += 8; | |
| 533 } else if (dataLength > 125) { | |
| 534 headerSize += 2; | |
| 535 } | |
| 536 List<int> header = new List<int>(headerSize); | |
| 537 int index = 0; | |
| 538 // Set FIN and opcode. | |
| 539 header[index++] = 0x80 | opcode; | |
| 540 // Determine size and position of length field. | |
| 541 int lengthBytes = 1; | |
| 542 int firstLengthByte = 1; | |
| 543 if (dataLength > 65535) { | |
| 544 header[index++] = 127; | |
| 545 lengthBytes = 8; | |
| 546 } else if (dataLength > 125) { | |
| 547 header[index++] = 126; | |
| 548 lengthBytes = 2; | |
| 549 } | |
| 550 // Write the length in network byte order into the header. | |
| 551 for (int i = 0; i < lengthBytes; i++) { | |
| 552 header[index++] = dataLength >> (((lengthBytes - 1) - i) * 8) & 0xFF; | |
| 553 } | |
| 554 assert(index == headerSize); | |
| 555 _socket.outputStream.write(header); | |
| 556 if (data !== null) { | |
| 557 _socket.outputStream.write(data); | |
| 558 } | |
| 559 } | |
| 560 | |
| 561 Socket _socket; | |
| 562 Timer _closeTimer; | |
| 563 int _hash; | |
| 564 | |
| 565 Function _onMessage; | |
| 566 Function _onClosed; | |
| 567 | |
| 568 int _currentMessageType = _WebSocketMessageType.NONE; | |
| 569 _StringDecoder _decoder; | |
| 570 ListOutputStream _outputStream; | |
| 571 bool _closeReceived = false; | |
| 572 bool _closeSent = false; | |
| 573 } | |
| 574 | |
| 575 | |
| 576 class _WebSocketConnection | |
| 577 extends _WebSocketConnectionBase implements WebSocketConnection { | |
| 578 _WebSocketConnection(DetachedSocket detached) { | |
| 579 _hash = detached.socket.hashCode; | |
| 580 _socketConnected(detached.socket); | |
| 581 _startProcessing(detached.unparsedData); | |
| 582 } | |
| 583 } | |
| 584 | |
| 585 | |
| 586 class _WebSocketHandler implements WebSocketHandler { | |
| 587 void onRequest(HttpRequest request, HttpResponse response) { | |
| 588 // Check that this is a web socket upgrade. | |
| 589 if (!_isWebSocketUpgrade(request)) { | |
| 590 response.statusCode = HttpStatus.BAD_REQUEST; | |
| 591 response.outputStream.close(); | |
| 592 return; | |
| 593 } | |
| 594 | |
| 595 // Send the upgrade response. | |
| 596 response.statusCode = HttpStatus.SWITCHING_PROTOCOLS; | |
| 597 response.headers.add(HttpHeaders.CONNECTION, "Upgrade"); | |
| 598 response.headers.add(HttpHeaders.UPGRADE, "websocket"); | |
| 599 String key = request.headers.value("Sec-WebSocket-Key"); | |
| 600 SHA1 sha1 = new SHA1(); | |
| 601 sha1.update("$key$_webSocketGUID".charCodes); | |
| 602 String accept = _Base64._encode(sha1.digest()); | |
| 603 response.headers.add("Sec-WebSocket-Accept", accept); | |
| 604 response.contentLength = 0; | |
| 605 | |
| 606 // Upgrade the connection and get the underlying socket. | |
| 607 WebSocketConnection conn = | |
| 608 new _WebSocketConnection(response.detachSocket()); | |
| 609 if (_onOpen !== null) _onOpen(conn); | |
| 610 } | |
| 611 | |
| 612 void set onOpen(callback(WebSocketConnection connection)) { | |
| 613 _onOpen = callback; | |
| 614 } | |
| 615 | |
| 616 bool _isWebSocketUpgrade(HttpRequest request) { | |
| 617 if (request.method != "GET") { | |
| 618 return false; | |
| 619 } | |
| 620 if (request.headers[HttpHeaders.CONNECTION] == null) { | |
| 621 return false; | |
| 622 } | |
| 623 bool isUpgrade = false; | |
| 624 request.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
| 625 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
| 626 }); | |
| 627 if (!isUpgrade) return false; | |
| 628 String upgrade = request.headers.value(HttpHeaders.UPGRADE); | |
| 629 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
| 630 return false; | |
| 631 } | |
| 632 String version = request.headers.value("Sec-WebSocket-Version"); | |
| 633 if (version == null || version != "13") { | |
| 634 return false; | |
| 635 } | |
| 636 String key = request.headers.value("Sec-WebSocket-Key"); | |
| 637 if (key == null) { | |
| 638 return false; | |
| 639 } | |
| 640 return true; | |
| 641 } | |
| 642 | |
| 643 Function _onOpen; | |
| 644 } | |
| 645 | |
| 646 | |
| 647 class _WebSocketClientConnection | |
| 648 extends _WebSocketConnectionBase implements WebSocketClientConnection { | |
| 649 _WebSocketClientConnection(HttpClientConnection this._conn, | |
| 650 [List<String> protocols]) { | |
| 651 _conn.onRequest = _onHttpClientRequest; | |
| 652 _conn.onResponse = _onHttpClientResponse; | |
| 653 _conn.onError = (e) { | |
| 654 if (_onClosed !== null) { | |
| 655 _onClosed(WebSocketStatus.ABNORMAL_CLOSURE, "$e"); | |
| 656 } | |
| 657 }; | |
| 658 | |
| 659 // Generate the nonce now as it is also used to set the hash code. | |
| 660 _generateNonceAndHash(); | |
| 661 } | |
| 662 | |
| 663 void set onRequest(void callback(HttpClientRequest request)) { | |
| 664 _onRequest = callback; | |
| 665 } | |
| 666 | |
| 667 void set onOpen(void callback()) { | |
| 668 _onOpen = callback; | |
| 669 } | |
| 670 | |
| 671 void set onNoUpgrade(void callback(HttpClientResponse request)) { | |
| 672 _onNoUpgrade = callback; | |
| 673 } | |
| 674 | |
| 675 void _onHttpClientRequest(HttpClientRequest request) { | |
| 676 if (_onRequest !== null) { | |
| 677 _onRequest(request); | |
| 678 } | |
| 679 // Setup the initial handshake. | |
| 680 request.headers.add(HttpHeaders.CONNECTION, "upgrade"); | |
| 681 request.headers.set(HttpHeaders.UPGRADE, "websocket"); | |
| 682 request.headers.set("Sec-WebSocket-Key", _nonce); | |
| 683 request.headers.set("Sec-WebSocket-Version", "13"); | |
| 684 request.contentLength = 0; | |
| 685 request.outputStream.close(); | |
| 686 } | |
| 687 | |
| 688 void _onHttpClientResponse(HttpClientResponse response) { | |
| 689 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS) { | |
| 690 if (_onNoUpgrade !== null) { | |
| 691 _onNoUpgrade(response); | |
| 692 } else { | |
| 693 _conn.detachSocket().socket.close(); | |
| 694 throw new WebSocketException("Protocol upgrade refused"); | |
| 695 } | |
| 696 return; | |
| 697 } | |
| 698 | |
| 699 if (!_isWebSocketUpgrade(response)) { | |
| 700 _conn.detachSocket().socket.close(); | |
| 701 throw new WebSocketException("Protocol upgrade failed"); | |
| 702 return; | |
| 703 } | |
| 704 | |
| 705 // Connection upgrade successful. | |
| 706 DetachedSocket detached = _conn.detachSocket(); | |
| 707 _socketConnected(detached.socket); | |
| 708 if (_onOpen !== null) _onOpen(); | |
| 709 _startProcessing(detached.unparsedData); | |
| 710 } | |
| 711 | |
| 712 void _generateNonceAndHash() { | |
| 713 Random random = new Random(); | |
| 714 assert(_nonce == null); | |
| 715 void intToBigEndianBytes(int value, List<int> bytes, int offset) { | |
| 716 bytes[offset] = (value >> 24) & 0xFF; | |
| 717 bytes[offset + 1] = (value >> 16) & 0xFF; | |
| 718 bytes[offset + 2] = (value >> 8) & 0xFF; | |
| 719 bytes[offset + 3] = value & 0xFF; | |
| 720 } | |
| 721 | |
| 722 // Generate 16 random bytes. Use the last four bytes for the hash code. | |
| 723 List<int> nonce = new List<int>(16); | |
| 724 for (int i = 0; i < 4; i++) { | |
| 725 int r = random.nextInt(0x100000000); | |
| 726 intToBigEndianBytes(r, nonce, i * 4); | |
| 727 } | |
| 728 _nonce = _Base64._encode(nonce); | |
| 729 _hash = random.nextInt(0x100000000); | |
| 730 } | |
| 731 | |
| 732 bool _isWebSocketUpgrade(HttpClientResponse response) { | |
| 733 if (response.headers[HttpHeaders.CONNECTION] == null) { | |
| 734 return false; | |
| 735 } | |
| 736 bool isUpgrade = false; | |
| 737 response.headers[HttpHeaders.CONNECTION].forEach((String value) { | |
| 738 if (value.toLowerCase() == "upgrade") isUpgrade = true; | |
| 739 }); | |
| 740 if (!isUpgrade) return false; | |
| 741 String upgrade = response.headers.value(HttpHeaders.UPGRADE); | |
| 742 if (upgrade == null || upgrade.toLowerCase() != "websocket") { | |
| 743 return false; | |
| 744 } | |
| 745 String accept = response.headers.value("Sec-WebSocket-Accept"); | |
| 746 if (accept == null) { | |
| 747 return false; | |
| 748 } | |
| 749 SHA1 sha1 = new SHA1(); | |
| 750 sha1.update("$_nonce$_webSocketGUID".charCodes); | |
| 751 List<int> expectedAccept = sha1.digest(); | |
| 752 List<int> receivedAccept = _Base64._decode(accept); | |
| 753 if (expectedAccept.length != receivedAccept.length) return false; | |
| 754 for (int i = 0; i < expectedAccept.length; i++) { | |
| 755 if (expectedAccept[i] != receivedAccept[i]) return false; | |
| 756 } | |
| 757 return true; | |
| 758 } | |
| 759 | |
| 760 Function _onRequest; | |
| 761 Function _onOpen; | |
| 762 Function _onNoUpgrade; | |
| 763 HttpClientConnection _conn; | |
| 764 String _nonce; | |
| 765 } | |
| 766 | |
| 767 | |
| 768 class _WebSocket implements WebSocket { | |
| 769 _WebSocket(String url, [protocols]) { | |
| 770 Uri uri = new Uri.fromString(url); | |
| 771 if (uri.scheme != "ws") { | |
| 772 throw new WebSocketException("Unsupported URL scheme ${uri.scheme}"); | |
| 773 } | |
| 774 if (uri.userInfo != "") { | |
| 775 throw new WebSocketException("Unsupported user info ${uri.userInfo}"); | |
| 776 } | |
| 777 int port = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; | |
| 778 String path = uri.path; | |
| 779 if (path.length == 0) path = "/"; | |
| 780 if (uri.query != "") { | |
| 781 if (uri.fragment != "") { | |
| 782 path = "${path}?${uri.query}#${uri.fragment}"; | |
| 783 } else { | |
| 784 path = "${path}?${uri.query}"; | |
| 785 } | |
| 786 } | |
| 787 | |
| 788 HttpClient client = new HttpClient(); | |
| 789 HttpClientConnection conn = client.open("GET", uri.domain, port, path); | |
| 790 if (protocols is String) protocols = [protocols]; | |
| 791 _wsconn = new WebSocketClientConnection(conn, protocols); | |
| 792 _wsconn.onOpen = () { | |
| 793 // HTTP client not needed after socket have been detached. | |
| 794 client.shutdown(); | |
| 795 client = null; | |
| 796 _readyState = WebSocket.OPEN; | |
| 797 if (_onopen !== null) _onopen(); | |
| 798 }; | |
| 799 _wsconn.onMessage = (message) { | |
| 800 if (_onmessage !== null) { | |
| 801 _onmessage(new _WebSocketMessageEvent(message)); | |
| 802 } | |
| 803 }; | |
| 804 _wsconn.onClosed = (status, reason) { | |
| 805 _readyState = WebSocket.CLOSED; | |
| 806 if (_onclose !== null) { | |
| 807 _onclose(new _WebSocketCloseEvent(true, status, reason)); | |
| 808 } | |
| 809 }; | |
| 810 _wsconn.onNoUpgrade = (response) { | |
| 811 if (_onclose !== null) { | |
| 812 _onclose( | |
| 813 new _WebSocketCloseEvent(true, | |
| 814 WebSocketStatus.ABNORMAL_CLOSURE, | |
| 815 "Connection not upgraded")); | |
| 816 } | |
| 817 }; | |
| 818 } | |
| 819 | |
| 820 int get readyState => _readyState; | |
| 821 int get bufferedAmount => 0; | |
| 822 | |
| 823 void set onopen(Function callback) { | |
| 824 _onopen = callback; | |
| 825 } | |
| 826 | |
| 827 void set onerror(Function callback) {} | |
| 828 | |
| 829 void set onclose(Function callback) { | |
| 830 _onclose = callback; | |
| 831 } | |
| 832 | |
| 833 String get extensions => null; | |
| 834 String get protocol => null; | |
| 835 | |
| 836 void close(int code, String reason) { | |
| 837 if (_readyState < WebSocket.CLOSING) _readyState = WebSocket.CLOSING; | |
| 838 _wsconn.close(code, reason); | |
| 839 } | |
| 840 | |
| 841 void set onmessage(Function callback) { | |
| 842 _onmessage = callback; | |
| 843 } | |
| 844 | |
| 845 void send(data) { | |
| 846 _wsconn.send(data); | |
| 847 } | |
| 848 | |
| 849 WebSocketClientConnection _wsconn; | |
| 850 int _readyState = WebSocket.CONNECTING; | |
| 851 Function _onopen; | |
| 852 Function _onclose; | |
| 853 Function _onmessage; | |
| 854 } | |
| 855 | |
| 856 | |
| 857 class _WebSocketMessageEvent implements MessageEvent { | |
| 858 _WebSocketMessageEvent(this._data); | |
| 859 get data => _data; | |
| 860 var _data; | |
| 861 } | |
| 862 | |
| 863 | |
| 864 class _WebSocketCloseEvent implements CloseEvent { | |
| 865 _WebSocketCloseEvent(this._wasClean, this._code, this._reason); | |
| 866 bool get wasClean => _wasClean; | |
| 867 int get code => _code; | |
| 868 String get reason => _reason; | |
| 869 bool _wasClean; | |
| 870 int _code; | |
| 871 String _reason; | |
| 872 } | |
| OLD | NEW |