| OLD | NEW |
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 part of dart.io; | 5 part of dart.io; |
| 6 | 6 |
| 7 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; | 7 const String _webSocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; |
| 8 const String _clientNoContextTakeover = "client_no_context_takeover"; | 8 const String _clientNoContextTakeover = "client_no_context_takeover"; |
| 9 const String _serverNoContextTakeover = "server_no_context_takeover"; | 9 const String _serverNoContextTakeover = "server_no_context_takeover"; |
| 10 const String _clientMaxWindowBits = "client_max_window_bits"; | 10 const String _clientMaxWindowBits = "client_max_window_bits"; |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 51 /** | 51 /** |
| 52 * The web socket protocol transformer handles the protocol byte stream | 52 * The web socket protocol transformer handles the protocol byte stream |
| 53 * which is supplied through the [:handleData:]. As the protocol is processed, | 53 * which is supplied through the [:handleData:]. As the protocol is processed, |
| 54 * it'll output frame data as either a List<int> or String. | 54 * it'll output frame data as either a List<int> or String. |
| 55 * | 55 * |
| 56 * Important information about usage: Be sure you use cancelOnError, so the | 56 * Important information about usage: Be sure you use cancelOnError, so the |
| 57 * socket will be closed when the processor encounter an error. Not using it | 57 * socket will be closed when the processor encounter an error. Not using it |
| 58 * will lead to undefined behaviour. | 58 * will lead to undefined behaviour. |
| 59 */ | 59 */ |
| 60 // TODO(ajohnsen): make this transformer reusable? | 60 // TODO(ajohnsen): make this transformer reusable? |
| 61 class _WebSocketProtocolTransformer implements StreamTransformer, EventSink { | 61 class _WebSocketProtocolTransformer |
| 62 implements StreamTransformer, EventSink<Uint8List> { |
| 62 static const int START = 0; | 63 static const int START = 0; |
| 63 static const int LEN_FIRST = 1; | 64 static const int LEN_FIRST = 1; |
| 64 static const int LEN_REST = 2; | 65 static const int LEN_REST = 2; |
| 65 static const int MASK = 3; | 66 static const int MASK = 3; |
| 66 static const int PAYLOAD = 4; | 67 static const int PAYLOAD = 4; |
| 67 static const int CLOSED = 5; | 68 static const int CLOSED = 5; |
| 68 static const int FAILURE = 6; | 69 static const int FAILURE = 6; |
| 69 static const int FIN = 0x80; | 70 static const int FIN = 0x80; |
| 70 static const int RSV1 = 0x40; | 71 static const int RSV1 = 0x40; |
| 71 static const int RSV2 = 0x20; | 72 static const int RSV2 = 0x20; |
| (...skipping 917 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 989 request.headers.add("Sec-WebSocket-Protocol", protocols.toList()); | 990 request.headers.add("Sec-WebSocket-Protocol", protocols.toList()); |
| 990 } | 991 } |
| 991 | 992 |
| 992 if (compression.enabled) { | 993 if (compression.enabled) { |
| 993 request.headers | 994 request.headers |
| 994 .add("Sec-WebSocket-Extensions", compression._createHeader()); | 995 .add("Sec-WebSocket-Extensions", compression._createHeader()); |
| 995 } | 996 } |
| 996 | 997 |
| 997 return request.close(); | 998 return request.close(); |
| 998 }).then((response) { | 999 }).then((response) { |
| 1000 |
| 999 void error(String message) { | 1001 void error(String message) { |
| 1000 // Flush data. | 1002 // Flush data. |
| 1001 response.detachSocket().then((socket) { | 1003 response.detachSocket().then((socket) { |
| 1002 socket.destroy(); | 1004 socket.destroy(); |
| 1003 }); | 1005 }); |
| 1004 throw new WebSocketException(message); | 1006 throw new WebSocketException(message); |
| 1005 } | 1007 } |
| 1008 |
| 1006 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS || | 1009 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS || |
| 1007 response.headers[HttpHeaders.CONNECTION] == null || | 1010 response.headers[HttpHeaders.CONNECTION] == null || |
| 1008 !response.headers[HttpHeaders.CONNECTION] | 1011 !response.headers[HttpHeaders.CONNECTION] |
| 1009 .any((value) => value.toLowerCase() == "upgrade") || | 1012 .any((value) => value.toLowerCase() == "upgrade") || |
| 1010 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() != | 1013 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() != |
| 1011 "websocket") { | 1014 "websocket") { |
| 1012 error("Connection to '$uri' was not upgraded to websocket"); | 1015 error("Connection to '$uri' was not upgraded to websocket"); |
| 1013 } | 1016 } |
| 1014 String accept = response.headers.value("Sec-WebSocket-Accept"); | 1017 String accept = response.headers.value("Sec-WebSocket-Accept"); |
| 1015 if (accept == null) { | 1018 if (accept == null) { |
| 1016 error("Response did not contain a 'Sec-WebSocket-Accept' header"); | 1019 error("Response did not contain a 'Sec-WebSocket-Accept' header"); |
| 1017 } | 1020 } |
| 1018 _SHA1 sha1 = new _SHA1(); | 1021 _SHA1 sha1 = new _SHA1(); |
| 1019 sha1.add("$nonce$_webSocketGUID".codeUnits); | 1022 sha1.add("$nonce$_webSocketGUID".codeUnits); |
| 1020 List<int> expectedAccept = sha1.close(); | 1023 List<int> expectedAccept = sha1.close(); |
| 1021 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); | 1024 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); |
| 1022 if (expectedAccept.length != receivedAccept.length) { | 1025 if (expectedAccept.length != receivedAccept.length) { |
| 1023 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); | 1026 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); |
| 1024 } | 1027 } |
| 1025 for (int i = 0; i < expectedAccept.length; i++) { | 1028 for (int i = 0; i < expectedAccept.length; i++) { |
| 1026 if (expectedAccept[i] != receivedAccept[i]) { | 1029 if (expectedAccept[i] != receivedAccept[i]) { |
| 1027 error("Bad response 'Sec-WebSocket-Accept' header"); | 1030 error("Bad response 'Sec-WebSocket-Accept' header"); |
| 1028 } | 1031 } |
| 1029 } | 1032 } |
| 1030 var protocol = response.headers.value('Sec-WebSocket-Protocol'); | 1033 var protocol = response.headers.value('Sec-WebSocket-Protocol'); |
| 1031 | 1034 |
| 1032 _WebSocketPerMessageDeflate deflate = | 1035 _WebSocketPerMessageDeflate deflate = |
| 1033 negotiateClientCompression(response, compression); | 1036 negotiateClientCompression(response, compression); |
| 1034 | 1037 |
| 1035 return response.detachSocket().then((socket) => | 1038 return response.detachSocket().then/*<WebSocket>*/((socket) => |
| 1036 new _WebSocketImpl._fromSocket( | 1039 new _WebSocketImpl._fromSocket( |
| 1037 socket, protocol, compression, false, deflate)); | 1040 socket, protocol, compression, false, deflate)); |
| 1038 }); | 1041 }); |
| 1039 } | 1042 } |
| 1040 | 1043 |
| 1041 static _WebSocketPerMessageDeflate negotiateClientCompression( | 1044 static _WebSocketPerMessageDeflate negotiateClientCompression( |
| 1042 HttpClientResponse response, CompressionOptions compression) { | 1045 HttpClientResponse response, CompressionOptions compression) { |
| 1043 String extensionHeader = response.headers.value('Sec-WebSocket-Extensions'); | 1046 String extensionHeader = response.headers.value('Sec-WebSocket-Extensions'); |
| 1044 | 1047 |
| 1045 if (extensionHeader == null) { | 1048 if (extensionHeader == null) { |
| 1046 extensionHeader = ""; | 1049 extensionHeader = ""; |
| 1047 } | 1050 } |
| 1048 | 1051 |
| 1049 var hv = HeaderValue.parse(extensionHeader, valueSeparator: ','); | 1052 var hv = HeaderValue.parse(extensionHeader, valueSeparator: ','); |
| 1050 | 1053 |
| 1051 if (compression.enabled && hv.value == PER_MESSAGE_DEFLATE) { | 1054 if (compression.enabled && hv.value == PER_MESSAGE_DEFLATE) { |
| 1052 var serverNoContextTakeover = | 1055 var serverNoContextTakeover = |
| 1053 hv.parameters.containsKey(_serverNoContextTakeover); | 1056 hv.parameters.containsKey(_serverNoContextTakeover); |
| 1054 var clientNoContextTakeover = | 1057 var clientNoContextTakeover = |
| 1055 hv.parameters.containsKey(_clientNoContextTakeover); | 1058 hv.parameters.containsKey(_clientNoContextTakeover); |
| 1056 | 1059 |
| 1057 int getWindowBits(String type) { | 1060 int getWindowBits(String type) { |
| 1058 var o = hv.parameters[type]; | 1061 var o = hv.parameters[type]; |
| 1059 if (o == null) { | 1062 if (o == null) { |
| 1060 return DEFAULT_WINDOW_BITS; | 1063 return DEFAULT_WINDOW_BITS; |
| 1061 } | 1064 } |
| 1062 | 1065 |
| 1063 o = int.parse(o, onError: (s) => DEFAULT_WINDOW_BITS); | 1066 return int.parse(o, onError: (s) => DEFAULT_WINDOW_BITS); |
| 1064 return o; | |
| 1065 } | 1067 } |
| 1066 | 1068 |
| 1067 return new _WebSocketPerMessageDeflate( | 1069 return new _WebSocketPerMessageDeflate( |
| 1068 clientMaxWindowBits: getWindowBits(_clientMaxWindowBits), | 1070 clientMaxWindowBits: getWindowBits(_clientMaxWindowBits), |
| 1069 serverMaxWindowBits: getWindowBits(_serverMaxWindowBits), | 1071 serverMaxWindowBits: getWindowBits(_serverMaxWindowBits), |
| 1070 clientNoContextTakeover: clientNoContextTakeover, | 1072 clientNoContextTakeover: clientNoContextTakeover, |
| 1071 serverNoContextTakeover: serverNoContextTakeover); | 1073 serverNoContextTakeover: serverNoContextTakeover); |
| 1072 } | 1074 } |
| 1073 | 1075 |
| 1074 return null; | 1076 return null; |
| (...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1209 _outCloseReason = reason; | 1211 _outCloseReason = reason; |
| 1210 } | 1212 } |
| 1211 _writeClosed = true; | 1213 _writeClosed = true; |
| 1212 _consumer.closeSocket(); | 1214 _consumer.closeSocket(); |
| 1213 _webSockets.remove(_serviceId); | 1215 _webSockets.remove(_serviceId); |
| 1214 } | 1216 } |
| 1215 | 1217 |
| 1216 String get _serviceTypePath => 'io/websockets'; | 1218 String get _serviceTypePath => 'io/websockets'; |
| 1217 String get _serviceTypeName => 'WebSocket'; | 1219 String get _serviceTypeName => 'WebSocket'; |
| 1218 | 1220 |
| 1219 Map _toJSON(bool ref) { | 1221 Map<String, dynamic> _toJSON(bool ref) { |
| 1220 var name = '${_socket.address.host}:${_socket.port}'; | 1222 var name = '${_socket.address.host}:${_socket.port}'; |
| 1221 var r = { | 1223 var r = <String, dynamic>{ |
| 1222 'id': _servicePath, | 1224 'id': _servicePath, |
| 1223 'type': _serviceType(ref), | 1225 'type': _serviceType(ref), |
| 1224 'name': name, | 1226 'name': name, |
| 1225 'user_name': name, | 1227 'user_name': name, |
| 1226 }; | 1228 }; |
| 1227 if (ref) { | 1229 if (ref) { |
| 1228 return r; | 1230 return r; |
| 1229 } | 1231 } |
| 1230 try { | 1232 try { |
| 1231 r['socket'] = _socket._toJSON(true); | 1233 r['socket'] = _socket._toJSON(true); |
| (...skipping 12 matching lines...) Expand all Loading... |
| 1244 return code != null && | 1246 return code != null && |
| 1245 (code < WebSocketStatus.NORMAL_CLOSURE || | 1247 (code < WebSocketStatus.NORMAL_CLOSURE || |
| 1246 code == WebSocketStatus.RESERVED_1004 || | 1248 code == WebSocketStatus.RESERVED_1004 || |
| 1247 code == WebSocketStatus.NO_STATUS_RECEIVED || | 1249 code == WebSocketStatus.NO_STATUS_RECEIVED || |
| 1248 code == WebSocketStatus.ABNORMAL_CLOSURE || | 1250 code == WebSocketStatus.ABNORMAL_CLOSURE || |
| 1249 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && | 1251 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && |
| 1250 code < WebSocketStatus.RESERVED_1015) || | 1252 code < WebSocketStatus.RESERVED_1015) || |
| 1251 (code >= WebSocketStatus.RESERVED_1015 && code < 3000)); | 1253 (code >= WebSocketStatus.RESERVED_1015 && code < 3000)); |
| 1252 } | 1254 } |
| 1253 } | 1255 } |
| OLD | NEW |