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 class _HttpIncoming extends Stream<List<int>> { | 7 class _HttpIncoming extends Stream<List<int>> { |
8 final int _transferLength; | 8 final int _transferLength; |
9 final Completer _dataCompleter = new Completer(); | 9 final Completer _dataCompleter = new Completer(); |
10 Stream<List<int>> _stream; | 10 Stream<List<int>> _stream; |
(...skipping 255 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
266 | 266 |
267 bool get _shouldAuthenticate { | 267 bool get _shouldAuthenticate { |
268 // Only try to authenticate if there is a challenge in the response. | 268 // Only try to authenticate if there is a challenge in the response. |
269 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; | 269 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; |
270 return statusCode == HttpStatus.UNAUTHORIZED && | 270 return statusCode == HttpStatus.UNAUTHORIZED && |
271 challenge != null && challenge.length == 1; | 271 challenge != null && challenge.length == 1; |
272 } | 272 } |
273 | 273 |
274 Future<HttpClientResponse> _authenticate() { | 274 Future<HttpClientResponse> _authenticate() { |
275 Future<HttpClientResponse> retryWithCredentials(_Credentials cr) { | 275 Future<HttpClientResponse> retryWithCredentials(_Credentials cr) { |
276 if (cr != null) { | 276 if (cr != null && cr.scheme != _AuthenticationScheme.UNKNOWN) { |
277 // TODO(sgjesse): Support digest. | 277 // Drain body and retry. |
278 if (cr.scheme == _AuthenticationScheme.BASIC) { | 278 return fold(null, (x, y) {}).then((_) { |
279 // Drain body and retry. | 279 return _httpClient._openUrlFromRequest(_httpRequest.method, |
280 return fold(null, (x, y) {}).then((_) { | 280 _httpRequest.uri, |
281 return _httpClient._openUrlFromRequest(_httpRequest.method, | 281 _httpRequest) |
282 _httpRequest.uri, | 282 .then((request) => request.close()); |
283 _httpRequest) | 283 }); |
284 .then((request) => request.close()); | |
285 }); | |
286 } | |
287 } | 284 } |
288 | 285 |
289 // Fall through to here to perform normal response handling if | 286 // Fall through to here to perform normal response handling if |
290 // there is no sensible authorization handling. | 287 // there is no sensible authorization handling. |
291 return new Future.value(this); | 288 return new Future.value(this); |
292 } | 289 } |
293 | 290 |
294 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; | 291 List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; |
295 assert(challenge != null || challenge.length == 1); | 292 assert(challenge != null || challenge.length == 1); |
296 _HeaderValue header = | 293 _HeaderValue header = |
297 new _HeaderValue.fromString(challenge[0], parameterSeparator: ","); | 294 new _HeaderValue.fromString(challenge[0], parameterSeparator: ","); |
298 _AuthenticationScheme scheme = | 295 _AuthenticationScheme scheme = |
299 new _AuthenticationScheme.fromString(header.value); | 296 new _AuthenticationScheme.fromString(header.value); |
300 String realm = header.parameters["realm"]; | 297 String realm = header.parameters["realm"]; |
301 | 298 |
302 // See if any credentials are available. | 299 // See if any matching credentials are available. |
303 _Credentials cr = _httpClient._findCredentials(_httpRequest.uri, scheme); | 300 _Credentials cr = _httpClient._findCredentials(_httpRequest.uri, scheme); |
| 301 if (cr != null) { |
| 302 // For basic authentication don't retry already used credentials |
| 303 // as they must have already been added to the request causing |
| 304 // this authenticate response. |
| 305 if (cr.scheme == _AuthenticationScheme.BASIC && !cr.used) { |
| 306 // Credentials where found, prepare for retrying the request. |
| 307 return retryWithCredentials(cr); |
| 308 } |
304 | 309 |
305 if (cr != null && !cr.used) { | 310 // Digest authentication only supports the MD5 algorithm. |
306 // If credentials found prepare for retrying the request. | 311 if (cr.scheme == _AuthenticationScheme.DIGEST && |
307 return retryWithCredentials(cr); | 312 (header.parameters["algorithm"] == null || |
| 313 header.parameters["algorithm"].toLowerCase() == "md5")) { |
| 314 // If the nonce is not set then this is the first authenticate |
| 315 // response for these credentials. |
| 316 // TODO(sgjesse): Check for changed nonce. |
| 317 if (cr.nonce == null) { |
| 318 // Set up authentication state. |
| 319 cr.nonce = header.parameters["nonce"]; |
| 320 cr.algorithm = "MD5"; |
| 321 cr.qop = header.parameters["qop"]; |
| 322 cr.nonceCount = 0; |
| 323 } |
| 324 // Credentials where found, prepare for retrying the request. |
| 325 return retryWithCredentials(cr); |
| 326 } |
308 } | 327 } |
309 | 328 |
310 // Ask for more credentials if none found or the one found has | 329 // Ask for more credentials if none found or the one found has |
311 // already been used. If it has already been used it must now be | 330 // already been used. If it has already been used it must now be |
312 // invalid and is removed. | 331 // invalid and is removed. |
313 if (cr != null) { | 332 if (cr != null) { |
314 _httpClient._removeCredentials(cr); | 333 _httpClient._removeCredentials(cr); |
315 cr = null; | 334 cr = null; |
316 } | 335 } |
317 if (_httpClient._authenticate != null) { | 336 if (_httpClient._authenticate != null) { |
(...skipping 1623 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1941 if (this == BASIC) return "Basic"; | 1960 if (this == BASIC) return "Basic"; |
1942 if (this == DIGEST) return "Digest"; | 1961 if (this == DIGEST) return "Digest"; |
1943 return "Unknown"; | 1962 return "Unknown"; |
1944 } | 1963 } |
1945 | 1964 |
1946 final int _scheme; | 1965 final int _scheme; |
1947 } | 1966 } |
1948 | 1967 |
1949 | 1968 |
1950 class _Credentials { | 1969 class _Credentials { |
1951 _Credentials(this.uri, this.realm, this.credentials); | 1970 _Credentials(this.uri, this.realm, this.credentials) { |
| 1971 if (credentials.scheme == _AuthenticationScheme.DIGEST) { |
| 1972 // Calculate the H(A1) value once. There is no mentioning of |
| 1973 // username/password encoding in RFC 2617. However there is an |
| 1974 // open draft for adding an additional accept-charset parameter to |
| 1975 // the WWW-Authenticate and Proxy-Authenticate headers, see |
| 1976 // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For |
| 1977 // now always use UTF-8 encoding. |
| 1978 _HttpClientDigestCredentials creds = credentials; |
| 1979 var hasher = new MD5(); |
| 1980 hasher.add(_encodeString(creds.username)); |
| 1981 hasher.add([_CharCode.COLON]); |
| 1982 hasher.add(realm.codeUnits); |
| 1983 hasher.add([_CharCode.COLON]); |
| 1984 hasher.add(_encodeString(creds.password)); |
| 1985 ha1 = CryptoUtils.bytesToHex(hasher.close()); |
| 1986 } |
| 1987 } |
1952 | 1988 |
1953 _AuthenticationScheme get scheme => credentials.scheme; | 1989 _AuthenticationScheme get scheme => credentials.scheme; |
1954 | 1990 |
1955 bool applies(Uri uri, _AuthenticationScheme scheme) { | 1991 bool applies(Uri uri, _AuthenticationScheme scheme) { |
1956 if (scheme != null && credentials.scheme != scheme) return false; | 1992 if (scheme != null && credentials.scheme != scheme) return false; |
1957 if (uri.domain != this.uri.domain) return false; | 1993 if (uri.domain != this.uri.domain) return false; |
1958 int thisPort = | 1994 int thisPort = |
1959 this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port; | 1995 this.uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : this.uri.port; |
1960 int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; | 1996 int otherPort = uri.port == 0 ? HttpClient.DEFAULT_HTTP_PORT : uri.port; |
1961 if (otherPort != thisPort) return false; | 1997 if (otherPort != thisPort) return false; |
1962 return uri.path.startsWith(this.uri.path); | 1998 return uri.path.startsWith(this.uri.path); |
1963 } | 1999 } |
1964 | 2000 |
1965 void authorize(HttpClientRequest request) { | 2001 void authorize(HttpClientRequest request) { |
| 2002 // Digest credentials cannot be used without a nonce from the |
| 2003 // server. |
| 2004 if (credentials.scheme == _AuthenticationScheme.DIGEST && |
| 2005 nonce == null) { |
| 2006 return; |
| 2007 } |
1966 credentials.authorize(this, request); | 2008 credentials.authorize(this, request); |
1967 used = true; | 2009 used = true; |
1968 } | 2010 } |
1969 | 2011 |
1970 bool used = false; | 2012 bool used = false; |
1971 Uri uri; | 2013 Uri uri; |
1972 String realm; | 2014 String realm; |
1973 _HttpClientCredentials credentials; | 2015 _HttpClientCredentials credentials; |
1974 | 2016 |
1975 // Digest specific fields. | 2017 // Digest specific fields. |
| 2018 String ha1; |
1976 String nonce; | 2019 String nonce; |
1977 String algorithm; | 2020 String algorithm; |
1978 String qop; | 2021 String qop; |
| 2022 int nonceCount; |
1979 } | 2023 } |
1980 | 2024 |
1981 | 2025 |
1982 class _ProxyCredentials { | 2026 class _ProxyCredentials { |
1983 _ProxyCredentials(this.host, this.port, this.realm, this.credentials); | 2027 _ProxyCredentials(this.host, this.port, this.realm, this.credentials); |
1984 | 2028 |
1985 _AuthenticationScheme get scheme => credentials.scheme; | 2029 _AuthenticationScheme get scheme => credentials.scheme; |
1986 | 2030 |
1987 bool applies(_Proxy proxy, _AuthenticationScheme scheme) { | 2031 bool applies(_Proxy proxy, _AuthenticationScheme scheme) { |
1988 return proxy.host == host && proxy.port == port; | 2032 return proxy.host == host && proxy.port == port; |
(...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
2040 | 2084 |
2041 | 2085 |
2042 class _HttpClientDigestCredentials | 2086 class _HttpClientDigestCredentials |
2043 extends _HttpClientCredentials | 2087 extends _HttpClientCredentials |
2044 implements HttpClientDigestCredentials { | 2088 implements HttpClientDigestCredentials { |
2045 _HttpClientDigestCredentials(this.username, | 2089 _HttpClientDigestCredentials(this.username, |
2046 this.password); | 2090 this.password); |
2047 | 2091 |
2048 _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST; | 2092 _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST; |
2049 | 2093 |
| 2094 String authorization(_Credentials credentials, HttpClientRequest request) { |
| 2095 MD5 hasher = new MD5(); |
| 2096 hasher.add(request.method.codeUnits); |
| 2097 hasher.add([_CharCode.COLON]); |
| 2098 hasher.add(request.uri.path.codeUnits); |
| 2099 var ha2 = CryptoUtils.bytesToHex(hasher.close()); |
| 2100 |
| 2101 String qop; |
| 2102 String cnonce; |
| 2103 String nc; |
| 2104 var x; |
| 2105 hasher = new MD5(); |
| 2106 hasher.add(credentials.ha1.codeUnits); |
| 2107 hasher.add([_CharCode.COLON]); |
| 2108 if (credentials.qop == "auth") { |
| 2109 qop = credentials.qop; |
| 2110 cnonce = CryptoUtils.bytesToHex(_IOCrypto.getRandomBytes(4)); |
| 2111 ++credentials.nonceCount; |
| 2112 nc = credentials.nonceCount.toRadixString(16); |
| 2113 nc = "00000000".substring(0, 8 - nc.length + 1) + nc; |
| 2114 hasher.add(credentials.nonce.codeUnits); |
| 2115 hasher.add([_CharCode.COLON]); |
| 2116 hasher.add(nc.codeUnits); |
| 2117 hasher.add([_CharCode.COLON]); |
| 2118 hasher.add(cnonce.codeUnits); |
| 2119 hasher.add([_CharCode.COLON]); |
| 2120 hasher.add(credentials.qop.codeUnits); |
| 2121 hasher.add([_CharCode.COLON]); |
| 2122 hasher.add(ha2.codeUnits); |
| 2123 } else { |
| 2124 hasher.add(credentials.nonce.codeUnits); |
| 2125 hasher.add([_CharCode.COLON]); |
| 2126 hasher.add(ha2.codeUnits); |
| 2127 } |
| 2128 var response = CryptoUtils.bytesToHex(hasher.close()); |
| 2129 |
| 2130 StringBuffer buffer = new StringBuffer(); |
| 2131 buffer.write('Digest '); |
| 2132 buffer.write('username="$username"'); |
| 2133 buffer.write(', realm="${credentials.realm}"'); |
| 2134 buffer.write(', nonce="${credentials.nonce}"'); |
| 2135 buffer.write(', uri="${request.uri.path}"'); |
| 2136 buffer.write(', algorithm="${credentials.algorithm}"'); |
| 2137 if (qop == "auth") { |
| 2138 buffer.write(', qop="$qop"'); |
| 2139 buffer.write(', cnonce="$cnonce"'); |
| 2140 buffer.write(', nc="$nc"'); |
| 2141 } |
| 2142 buffer.write(', response="$response"'); |
| 2143 return buffer.toString(); |
| 2144 } |
| 2145 |
2050 void authorize(_Credentials credentials, HttpClientRequest request) { | 2146 void authorize(_Credentials credentials, HttpClientRequest request) { |
2051 // TODO(sgjesse): Implement!!! | 2147 request.headers.set(HttpHeaders.AUTHORIZATION, authorization(credentials, re
quest)); |
2052 throw new UnsupportedError("Digest authentication not yet supported"); | |
2053 } | 2148 } |
2054 | 2149 |
2055 void authorizeProxy(_ProxyCredentials credentials, | 2150 void authorizeProxy(_ProxyCredentials credentials, |
2056 HttpClientRequest request) { | 2151 HttpClientRequest request) { |
2057 // TODO(sgjesse): Implement!!! | 2152 // TODO(sgjesse): Implement!!! |
2058 throw new UnsupportedError("Digest authentication not yet supported"); | 2153 throw new UnsupportedError("Digest authentication not yet supported"); |
2059 } | 2154 } |
2060 | 2155 |
2061 String username; | 2156 String username; |
2062 String password; | 2157 String password; |
2063 } | 2158 } |
2064 | 2159 |
2065 | 2160 |
2066 class _RedirectInfo implements RedirectInfo { | 2161 class _RedirectInfo implements RedirectInfo { |
2067 const _RedirectInfo(int this.statusCode, | 2162 const _RedirectInfo(int this.statusCode, |
2068 String this.method, | 2163 String this.method, |
2069 Uri this.location); | 2164 Uri this.location); |
2070 final int statusCode; | 2165 final int statusCode; |
2071 final String method; | 2166 final String method; |
2072 final Uri location; | 2167 final Uri location; |
2073 } | 2168 } |
OLD | NEW |