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, |