| Index: sdk/lib/io/http_impl.dart
|
| diff --git a/sdk/lib/io/http_impl.dart b/sdk/lib/io/http_impl.dart
|
| index ee855ec8661e76acc5bd920272ab9938bce258f8..31f2a489eb8368291fe54e74e50cfb261bfeac33 100644
|
| --- a/sdk/lib/io/http_impl.dart
|
| +++ b/sdk/lib/io/http_impl.dart
|
| @@ -273,17 +273,14 @@ class _HttpClientResponse
|
|
|
| Future<HttpClientResponse> _authenticate() {
|
| Future<HttpClientResponse> retryWithCredentials(_Credentials cr) {
|
| - if (cr != null) {
|
| - // TODO(sgjesse): Support digest.
|
| - if (cr.scheme == _AuthenticationScheme.BASIC) {
|
| - // Drain body and retry.
|
| - return fold(null, (x, y) {}).then((_) {
|
| - return _httpClient._openUrlFromRequest(_httpRequest.method,
|
| - _httpRequest.uri,
|
| - _httpRequest)
|
| - .then((request) => request.close());
|
| - });
|
| - }
|
| + if (cr != null && cr.scheme != _AuthenticationScheme.UNKNOWN) {
|
| + // Drain body and retry.
|
| + return fold(null, (x, y) {}).then((_) {
|
| + return _httpClient._openUrlFromRequest(_httpRequest.method,
|
| + _httpRequest.uri,
|
| + _httpRequest)
|
| + .then((request) => request.close());
|
| + });
|
| }
|
|
|
| // Fall through to here to perform normal response handling if
|
| @@ -299,12 +296,34 @@ class _HttpClientResponse
|
| new _AuthenticationScheme.fromString(header.value);
|
| String realm = header.parameters["realm"];
|
|
|
| - // See if any credentials are available.
|
| + // See if any matching credentials are available.
|
| _Credentials cr = _httpClient._findCredentials(_httpRequest.uri, scheme);
|
| + if (cr != null) {
|
| + // For basic authentication don't retry already used credentials
|
| + // as they must have already been added to the request causing
|
| + // this authenticate response.
|
| + if (cr.scheme == _AuthenticationScheme.BASIC && !cr.used) {
|
| + // Credentials where found, prepare for retrying the request.
|
| + return retryWithCredentials(cr);
|
| + }
|
|
|
| - if (cr != null && !cr.used) {
|
| - // If credentials found prepare for retrying the request.
|
| - return retryWithCredentials(cr);
|
| + // Digest authentication only supports the MD5 algorithm.
|
| + if (cr.scheme == _AuthenticationScheme.DIGEST &&
|
| + (header.parameters["algorithm"] == null ||
|
| + header.parameters["algorithm"].toLowerCase() == "md5")) {
|
| + // If the nonce is not set then this is the first authenticate
|
| + // response for these credentials.
|
| + // TODO(sgjesse): Check for changed nonce.
|
| + if (cr.nonce == null) {
|
| + // Set up authentication state.
|
| + cr.nonce = header.parameters["nonce"];
|
| + cr.algorithm = "MD5";
|
| + cr.qop = header.parameters["qop"];
|
| + cr.nonceCount = 0;
|
| + }
|
| + // Credentials where found, prepare for retrying the request.
|
| + return retryWithCredentials(cr);
|
| + }
|
| }
|
|
|
| // Ask for more credentials if none found or the one found has
|
| @@ -1948,7 +1967,24 @@ class _AuthenticationScheme {
|
|
|
|
|
| class _Credentials {
|
| - _Credentials(this.uri, this.realm, this.credentials);
|
| + _Credentials(this.uri, this.realm, this.credentials) {
|
| + if (credentials.scheme == _AuthenticationScheme.DIGEST) {
|
| + // Calculate the H(A1) value once. There is no mentioning of
|
| + // username/password encoding in RFC 2617. However there is an
|
| + // open draft for adding an additional accept-charset parameter to
|
| + // the WWW-Authenticate and Proxy-Authenticate headers, see
|
| + // http://tools.ietf.org/html/draft-reschke-basicauth-enc-06. For
|
| + // now always use UTF-8 encoding.
|
| + _HttpClientDigestCredentials creds = credentials;
|
| + var hasher = new MD5();
|
| + hasher.add(_encodeString(creds.username));
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(realm.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(_encodeString(creds.password));
|
| + ha1 = CryptoUtils.bytesToHex(hasher.close());
|
| + }
|
| + }
|
|
|
| _AuthenticationScheme get scheme => credentials.scheme;
|
|
|
| @@ -1963,6 +1999,12 @@ class _Credentials {
|
| }
|
|
|
| void authorize(HttpClientRequest request) {
|
| + // Digest credentials cannot be used without a nonce from the
|
| + // server.
|
| + if (credentials.scheme == _AuthenticationScheme.DIGEST &&
|
| + nonce == null) {
|
| + return;
|
| + }
|
| credentials.authorize(this, request);
|
| used = true;
|
| }
|
| @@ -1973,9 +2015,11 @@ class _Credentials {
|
| _HttpClientCredentials credentials;
|
|
|
| // Digest specific fields.
|
| + String ha1;
|
| String nonce;
|
| String algorithm;
|
| String qop;
|
| + int nonceCount;
|
| }
|
|
|
|
|
| @@ -2047,9 +2091,60 @@ class _HttpClientDigestCredentials
|
|
|
| _AuthenticationScheme get scheme => _AuthenticationScheme.DIGEST;
|
|
|
| + String authorization(_Credentials credentials, HttpClientRequest request) {
|
| + MD5 hasher = new MD5();
|
| + hasher.add(request.method.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(request.uri.path.codeUnits);
|
| + var ha2 = CryptoUtils.bytesToHex(hasher.close());
|
| +
|
| + String qop;
|
| + String cnonce;
|
| + String nc;
|
| + var x;
|
| + hasher = new MD5();
|
| + hasher.add(credentials.ha1.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + if (credentials.qop == "auth") {
|
| + qop = credentials.qop;
|
| + cnonce = CryptoUtils.bytesToHex(_IOCrypto.getRandomBytes(4));
|
| + ++credentials.nonceCount;
|
| + nc = credentials.nonceCount.toRadixString(16);
|
| + nc = "00000000".substring(0, 8 - nc.length + 1) + nc;
|
| + hasher.add(credentials.nonce.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(nc.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(cnonce.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(credentials.qop.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(ha2.codeUnits);
|
| + } else {
|
| + hasher.add(credentials.nonce.codeUnits);
|
| + hasher.add([_CharCode.COLON]);
|
| + hasher.add(ha2.codeUnits);
|
| + }
|
| + var response = CryptoUtils.bytesToHex(hasher.close());
|
| +
|
| + StringBuffer buffer = new StringBuffer();
|
| + buffer.write('Digest ');
|
| + buffer.write('username="$username"');
|
| + buffer.write(', realm="${credentials.realm}"');
|
| + buffer.write(', nonce="${credentials.nonce}"');
|
| + buffer.write(', uri="${request.uri.path}"');
|
| + buffer.write(', algorithm="${credentials.algorithm}"');
|
| + if (qop == "auth") {
|
| + buffer.write(', qop="$qop"');
|
| + buffer.write(', cnonce="$cnonce"');
|
| + buffer.write(', nc="$nc"');
|
| + }
|
| + buffer.write(', response="$response"');
|
| + return buffer.toString();
|
| + }
|
| +
|
| void authorize(_Credentials credentials, HttpClientRequest request) {
|
| - // TODO(sgjesse): Implement!!!
|
| - throw new UnsupportedError("Digest authentication not yet supported");
|
| + request.headers.set(HttpHeaders.AUTHORIZATION, authorization(credentials, request));
|
| }
|
|
|
| void authorizeProxy(_ProxyCredentials credentials,
|
|
|