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 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
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 | 61 class _WebSocketProtocolTransformer |
62 implements StreamTransformer<List<int>, dynamic>, EventSink<Uint8List> { | 62 implements StreamTransformer<List<int>, dynamic>, EventSink<List<int>> { |
63 static const int START = 0; | 63 static const int START = 0; |
64 static const int LEN_FIRST = 1; | 64 static const int LEN_FIRST = 1; |
65 static const int LEN_REST = 2; | 65 static const int LEN_REST = 2; |
66 static const int MASK = 3; | 66 static const int MASK = 3; |
67 static const int PAYLOAD = 4; | 67 static const int PAYLOAD = 4; |
68 static const int CLOSED = 5; | 68 static const int CLOSED = 5; |
69 static const int FAILURE = 6; | 69 static const int FAILURE = 6; |
70 static const int FIN = 0x80; | 70 static const int FIN = 0x80; |
71 static const int RSV1 = 0x40; | 71 static const int RSV1 = 0x40; |
72 static const int RSV2 = 0x20; | 72 static const int RSV2 = 0x20; |
(...skipping 920 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
993 request.headers.add("Sec-WebSocket-Protocol", protocols.toList()); | 993 request.headers.add("Sec-WebSocket-Protocol", protocols.toList()); |
994 } | 994 } |
995 | 995 |
996 if (compression.enabled) { | 996 if (compression.enabled) { |
997 request.headers | 997 request.headers |
998 .add("Sec-WebSocket-Extensions", compression._createHeader()); | 998 .add("Sec-WebSocket-Extensions", compression._createHeader()); |
999 } | 999 } |
1000 | 1000 |
1001 return request.close(); | 1001 return request.close(); |
1002 }).then((response) { | 1002 }).then((response) { |
1003 | |
1004 void error(String message) { | 1003 void error(String message) { |
1005 // Flush data. | 1004 // Flush data. |
1006 response.detachSocket().then((socket) { | 1005 response.detachSocket().then((socket) { |
1007 socket.destroy(); | 1006 socket.destroy(); |
1008 }); | 1007 }); |
1009 throw new WebSocketException(message); | 1008 throw new WebSocketException(message); |
1010 } | 1009 } |
1011 | |
1012 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS || | 1010 if (response.statusCode != HttpStatus.SWITCHING_PROTOCOLS || |
1013 response.headers[HttpHeaders.CONNECTION] == null || | 1011 response.headers[HttpHeaders.CONNECTION] == null || |
1014 !response.headers[HttpHeaders.CONNECTION] | 1012 !response.headers[HttpHeaders.CONNECTION] |
1015 .any((value) => value.toLowerCase() == "upgrade") || | 1013 .any((value) => value.toLowerCase() == "upgrade") || |
1016 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() != | 1014 response.headers.value(HttpHeaders.UPGRADE).toLowerCase() != |
1017 "websocket") { | 1015 "websocket") { |
1018 error("Connection to '$uri' was not upgraded to websocket"); | 1016 error("Connection to '$uri' was not upgraded to websocket"); |
1019 } | 1017 } |
1020 String accept = response.headers.value("Sec-WebSocket-Accept"); | 1018 String accept = response.headers.value("Sec-WebSocket-Accept"); |
1021 if (accept == null) { | 1019 if (accept == null) { |
1022 error("Response did not contain a 'Sec-WebSocket-Accept' header"); | 1020 error("Response did not contain a 'Sec-WebSocket-Accept' header"); |
1023 } | 1021 } |
1024 _SHA1 sha1 = new _SHA1(); | 1022 _SHA1 sha1 = new _SHA1(); |
1025 sha1.add("$nonce$_webSocketGUID".codeUnits); | 1023 sha1.add("$nonce$_webSocketGUID".codeUnits); |
1026 List<int> expectedAccept = sha1.close(); | 1024 List<int> expectedAccept = sha1.close(); |
1027 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); | 1025 List<int> receivedAccept = _CryptoUtils.base64StringToBytes(accept); |
1028 if (expectedAccept.length != receivedAccept.length) { | 1026 if (expectedAccept.length != receivedAccept.length) { |
1029 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); | 1027 error("Reasponse header 'Sec-WebSocket-Accept' is the wrong length"); |
1030 } | 1028 } |
1031 for (int i = 0; i < expectedAccept.length; i++) { | 1029 for (int i = 0; i < expectedAccept.length; i++) { |
1032 if (expectedAccept[i] != receivedAccept[i]) { | 1030 if (expectedAccept[i] != receivedAccept[i]) { |
1033 error("Bad response 'Sec-WebSocket-Accept' header"); | 1031 error("Bad response 'Sec-WebSocket-Accept' header"); |
1034 } | 1032 } |
1035 } | 1033 } |
1036 var protocol = response.headers.value('Sec-WebSocket-Protocol'); | 1034 var protocol = response.headers.value('Sec-WebSocket-Protocol'); |
1037 | 1035 |
1038 _WebSocketPerMessageDeflate deflate = | 1036 _WebSocketPerMessageDeflate deflate = |
1039 negotiateClientCompression(response, compression); | 1037 negotiateClientCompression(response, compression); |
1040 | 1038 |
1041 return response.detachSocket().then/*<WebSocket>*/((socket) => | 1039 return response.detachSocket().then((socket) => |
1042 new _WebSocketImpl._fromSocket( | 1040 new _WebSocketImpl._fromSocket( |
1043 socket, protocol, compression, false, deflate)); | 1041 socket, protocol, compression, false, deflate)); |
1044 }); | 1042 }); |
1045 } | 1043 } |
1046 | 1044 |
1047 static _WebSocketPerMessageDeflate negotiateClientCompression( | 1045 static _WebSocketPerMessageDeflate negotiateClientCompression( |
1048 HttpClientResponse response, CompressionOptions compression) { | 1046 HttpClientResponse response, CompressionOptions compression) { |
1049 String extensionHeader = response.headers.value('Sec-WebSocket-Extensions'); | 1047 String extensionHeader = response.headers.value('Sec-WebSocket-Extensions'); |
1050 | 1048 |
1051 if (extensionHeader == null) { | 1049 if (extensionHeader == null) { |
1052 extensionHeader = ""; | 1050 extensionHeader = ""; |
1053 } | 1051 } |
1054 | 1052 |
1055 var hv = HeaderValue.parse(extensionHeader, valueSeparator: ','); | 1053 var hv = HeaderValue.parse(extensionHeader, valueSeparator: ','); |
1056 | 1054 |
1057 if (compression.enabled && hv.value == PER_MESSAGE_DEFLATE) { | 1055 if (compression.enabled && hv.value == PER_MESSAGE_DEFLATE) { |
1058 var serverNoContextTakeover = | 1056 var serverNoContextTakeover = |
1059 hv.parameters.containsKey(_serverNoContextTakeover); | 1057 hv.parameters.containsKey(_serverNoContextTakeover); |
1060 var clientNoContextTakeover = | 1058 var clientNoContextTakeover = |
1061 hv.parameters.containsKey(_clientNoContextTakeover); | 1059 hv.parameters.containsKey(_clientNoContextTakeover); |
1062 | 1060 |
1063 int getWindowBits(String type) { | 1061 int getWindowBits(String type) { |
1064 var o = hv.parameters[type]; | 1062 var o = hv.parameters[type]; |
1065 if (o == null) { | 1063 if (o == null) { |
1066 return DEFAULT_WINDOW_BITS; | 1064 return DEFAULT_WINDOW_BITS; |
1067 } | 1065 } |
1068 | 1066 |
1069 return int.parse(o, onError: (s) => DEFAULT_WINDOW_BITS); | 1067 o = int.parse(o, onError: (s) => DEFAULT_WINDOW_BITS); |
| 1068 return o; |
1070 } | 1069 } |
1071 | 1070 |
1072 return new _WebSocketPerMessageDeflate( | 1071 return new _WebSocketPerMessageDeflate( |
1073 clientMaxWindowBits: getWindowBits(_clientMaxWindowBits), | 1072 clientMaxWindowBits: getWindowBits(_clientMaxWindowBits), |
1074 serverMaxWindowBits: getWindowBits(_serverMaxWindowBits), | 1073 serverMaxWindowBits: getWindowBits(_serverMaxWindowBits), |
1075 clientNoContextTakeover: clientNoContextTakeover, | 1074 clientNoContextTakeover: clientNoContextTakeover, |
1076 serverNoContextTakeover: serverNoContextTakeover); | 1075 serverNoContextTakeover: serverNoContextTakeover); |
1077 } | 1076 } |
1078 | 1077 |
1079 return null; | 1078 return null; |
(...skipping 134 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1214 _outCloseReason = reason; | 1213 _outCloseReason = reason; |
1215 } | 1214 } |
1216 _writeClosed = true; | 1215 _writeClosed = true; |
1217 _consumer.closeSocket(); | 1216 _consumer.closeSocket(); |
1218 _webSockets.remove(_serviceId); | 1217 _webSockets.remove(_serviceId); |
1219 } | 1218 } |
1220 | 1219 |
1221 String get _serviceTypePath => 'io/websockets'; | 1220 String get _serviceTypePath => 'io/websockets'; |
1222 String get _serviceTypeName => 'WebSocket'; | 1221 String get _serviceTypeName => 'WebSocket'; |
1223 | 1222 |
1224 Map<String, dynamic> _toJSON(bool ref) { | 1223 Map _toJSON(bool ref) { |
1225 var name = '${_socket.address.host}:${_socket.port}'; | 1224 var name = '${_socket.address.host}:${_socket.port}'; |
1226 var r = <String, dynamic>{ | 1225 var r = <String, dynamic>{ |
1227 'id': _servicePath, | 1226 'id': _servicePath, |
1228 'type': _serviceType(ref), | 1227 'type': _serviceType(ref), |
1229 'name': name, | 1228 'name': name, |
1230 'user_name': name, | 1229 'user_name': name, |
1231 }; | 1230 }; |
1232 if (ref) { | 1231 if (ref) { |
1233 return r; | 1232 return r; |
1234 } | 1233 } |
(...skipping 14 matching lines...) Expand all Loading... |
1249 return code != null && | 1248 return code != null && |
1250 (code < WebSocketStatus.NORMAL_CLOSURE || | 1249 (code < WebSocketStatus.NORMAL_CLOSURE || |
1251 code == WebSocketStatus.RESERVED_1004 || | 1250 code == WebSocketStatus.RESERVED_1004 || |
1252 code == WebSocketStatus.NO_STATUS_RECEIVED || | 1251 code == WebSocketStatus.NO_STATUS_RECEIVED || |
1253 code == WebSocketStatus.ABNORMAL_CLOSURE || | 1252 code == WebSocketStatus.ABNORMAL_CLOSURE || |
1254 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && | 1253 (code > WebSocketStatus.INTERNAL_SERVER_ERROR && |
1255 code < WebSocketStatus.RESERVED_1015) || | 1254 code < WebSocketStatus.RESERVED_1015) || |
1256 (code >= WebSocketStatus.RESERVED_1015 && code < 3000)); | 1255 (code >= WebSocketStatus.RESERVED_1015 && code < 3000)); |
1257 } | 1256 } |
1258 } | 1257 } |
OLD | NEW |