Index: sdk/lib/io/http_impl.dart |
diff --git a/sdk/lib/io/http_impl.dart b/sdk/lib/io/http_impl.dart |
index 952b2703a82a7952e6e0c4681851253336938353..965a6fd009d9e6a7ddccce10e9c95b18655e8857 100644 |
--- a/sdk/lib/io/http_impl.dart |
+++ b/sdk/lib/io/http_impl.dart |
@@ -251,6 +251,13 @@ class _HttpClientResponse |
HttpConnectionInfo get connectionInfo => _httpRequest.connectionInfo; |
+ bool get _shouldAuthenticateProxy { |
+ // Only try to authenticate if there is a challenge in the response. |
+ List<String> challenge = headers[HttpHeaders.PROXY_AUTHENTICATE]; |
Anders Johnsen
2013/04/15 07:07:19
Use headers.value? It should do the length == 1 ch
Søren Gjesse
2013/04/15 07:42:00
Value throws, and I don't want that here.
|
+ return statusCode == HttpStatus.PROXY_AUTHENTICATION_REQUIRED && |
+ challenge != null && challenge.length == 1; |
+ } |
+ |
bool get _shouldAuthenticate { |
// Only try to authenticate if there is a challenge in the response. |
List<String> challenge = headers[HttpHeaders.WWW_AUTHENTICATE]; |
@@ -315,6 +322,56 @@ class _HttpClientResponse |
} |
}); |
} |
+ |
+ // No credentials were found and the callback was not set. |
+ return new Future.immediate(this); |
+ } |
+ |
+ Future<HttpClientResponse> _authenticateProxy() { |
+ Future<HttpClientResponse> retryWithProxyCredentials(_ProxyCredentials cr) { |
+ _httpRequest.headers._mutable = true; |
Anders Johnsen
2013/04/15 07:07:19
Are you sure about this? Don't you want to modify
Søren Gjesse
2013/04/15 07:42:00
Not needed - removed.
|
+ cr.authorize(_httpRequest); |
Anders Johnsen
2013/04/15 07:07:19
This should be done automatically in _HttpClientRe
Søren Gjesse
2013/04/15 07:42:00
Good point - removed.
|
+ return fold(null, (x, y) {}).then((_) { |
+ return _httpClient._openUrlFromRequest(_httpRequest.method, |
+ _httpRequest.uri, |
+ _httpRequest) |
+ .then((request) => request.close()); |
+ }); |
+ } |
+ |
+ List<String> challenge = headers[HttpHeaders.PROXY_AUTHENTICATE]; |
+ assert(challenge != null || challenge.length == 1); |
+ _HeaderValue header = |
+ new _HeaderValue.fromString(challenge[0], parameterSeparator: ","); |
+ _AuthenticationScheme scheme = |
+ new _AuthenticationScheme.fromString(header.value); |
+ String realm = header.parameters["realm"]; |
+ |
+ // See if any credentials are available. |
+ var proxy = _httpRequest._proxy; |
+ |
+ var cr = _httpClient._findProxyCredentials(proxy); |
+ if (cr != null) { |
+ return retryWithProxyCredentials(cr); |
+ } |
+ |
+ // Ask for more credentials if none found. |
+ if (_httpClient._authenticateProxy != null) { |
+ Future authComplete = _httpClient._authenticateProxy(proxy.host, |
+ proxy.port, |
+ "basic", |
+ realm); |
+ return authComplete.then((credsAvailable) { |
+ if (credsAvailable) { |
+ var cr = _httpClient._findProxyCredentials(proxy); |
+ return retryWithProxyCredentials(cr); |
+ } else { |
+ // No credentials available, complete with original response. |
+ return this; |
+ } |
+ }); |
+ } |
+ |
// No credentials were found and the callback was not set. |
return new Future.immediate(this); |
} |
@@ -705,7 +762,7 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse> |
final Completer<HttpClientResponse> _responseCompleter |
= new Completer<HttpClientResponse>(); |
- final bool _usingProxy; |
+ final _Proxy _proxy; |
Future<HttpClientResponse> _response; |
@@ -719,7 +776,7 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse> |
_HttpClientRequest(_HttpOutgoing outgoing, |
Uri this.uri, |
String this.method, |
- bool this._usingProxy, |
+ _Proxy this._proxy, |
_HttpClient this._httpClient, |
_HttpClientConnection this._httpClientConnection) |
: super("1.1", outgoing) { |
@@ -777,6 +834,8 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse> |
.then((_) => new Future.immediateError( |
new RedirectLimitExceededException(response.redirects))); |
} |
+ } else if (response._shouldAuthenticateProxy) { |
+ future = response._authenticateProxy(); |
} else if (response._shouldAuthenticate) { |
future = response._authenticate(); |
} else { |
@@ -802,7 +861,7 @@ class _HttpClientRequest extends _HttpOutboundMessage<HttpClientResponse> |
writeSP(); |
// Send the path for direct connections and the whole URL for |
// proxy connections. |
- if (!_usingProxy) { |
+ if (_proxy.isDirect) { |
String path = uri.path; |
if (path.length == 0) path = "/"; |
if (uri.query != "") { |
@@ -980,7 +1039,7 @@ class _HttpClientConnection { |
}); |
} |
- _HttpClientRequest send(Uri uri, int port, String method, bool isDirect) { |
+ _HttpClientRequest send(Uri uri, int port, String method, _Proxy proxy) { |
// Start with pausing the parser. |
_subscription.pause(); |
var outgoing = new _HttpOutgoing(); |
@@ -988,15 +1047,27 @@ class _HttpClientConnection { |
var request = new _HttpClientRequest(outgoing, |
uri, |
method, |
- !isDirect, |
+ proxy, |
_httpClient, |
this); |
request.headers.host = uri.domain; |
request.headers.port = port; |
request.headers.set(HttpHeaders.ACCEPT_ENCODING, "gzip"); |
+ if (proxy.isAuthenticated) { |
+ // If the proxy configuration contains user information use that |
+ // for proxy basic authorization. |
+ String auth = CryptoUtils.bytesToBase64( |
+ _encodeString("${proxy.username}:${proxy.password}")); |
+ request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, "Basic $auth"); |
+ } else if (!proxy.isDirect && _httpClient._proxyCredentials.length > 0) { |
+ var cr = _httpClient._findProxyCredentials(proxy); |
+ if (cr != null) { |
+ cr.authorize(request); |
+ } |
+ } |
if (uri.userInfo != null && !uri.userInfo.isEmpty) { |
// If the URL contains user information use that for basic |
- // authorization |
+ // authorization. |
String auth = |
CryptoUtils.bytesToBase64(_encodeString(uri.userInfo)); |
request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth"); |
@@ -1095,7 +1166,9 @@ class _HttpClient implements HttpClient { |
final Set<_HttpClientConnection> _activeConnections |
= new Set<_HttpClientConnection>(); |
final List<_Credentials> _credentials = []; |
+ final List<_ProxyCredentials> _proxyCredentials = []; |
Function _authenticate; |
+ Function _authenticateProxy; |
Function _findProxy = HttpClient.findProxyFromEnvironment; |
Future<HttpClientRequest> open(String method, |
@@ -1163,6 +1236,18 @@ class _HttpClient implements HttpClient { |
_credentials.add(new _Credentials(url, realm, cr)); |
} |
+ set authenticateProxy( |
+ Future<bool> f(String host, int port, String scheme, String realm)) { |
+ _authenticateProxy = f; |
+ } |
+ |
+ void addProxyCredentials(String host, |
+ int port, |
+ String realm, |
+ HttpClientCredentials cr) { |
+ _proxyCredentials.add(new _ProxyCredentials(host, port, realm, cr)); |
+ } |
+ |
set findProxy(String f(Uri uri)) => _findProxy = f; |
Future<HttpClientRequest> _openUrl(String method, Uri uri) { |
@@ -1196,7 +1281,7 @@ class _HttpClient implements HttpClient { |
return info.connection.send(uri, |
port, |
method.toUpperCase(), |
- info.proxy.isDirect); |
+ info.proxy); |
}); |
} |
@@ -1208,7 +1293,7 @@ class _HttpClient implements HttpClient { |
request.followRedirects = previous.followRedirects; |
// Allow same number of redirects. |
request.maxRedirects = previous.maxRedirects; |
- // Copy headers |
+ // Copy headers. |
for (var header in previous.headers._headers.keys) { |
if (request.headers[header] == null) { |
request.headers.set(header, previous.headers[header]); |
@@ -1299,6 +1384,16 @@ class _HttpClient implements HttpClient { |
return cr; |
} |
+ _ProxyCredentials _findProxyCredentials(_Proxy proxy) { |
+ // Look for credentials. |
+ var it = _proxyCredentials.iterator; |
+ while (it.moveNext()) { |
+ if (it.current.applies(proxy, _AuthenticationScheme.BASIC)) { |
+ return it.current; |
+ } |
+ } |
+ } |
+ |
void _removeCredentials(_Credentials cr) { |
int index = _credentials.indexOf(cr); |
if (index != -1) { |
@@ -1590,13 +1685,30 @@ class _ProxyConfiguration { |
proxy = proxy.trim(); |
if (!proxy.isEmpty) { |
if (proxy.startsWith(PROXY_PREFIX)) { |
+ String username; |
+ String password; |
+ // Skip the "PROXY " prefix. |
+ proxy = proxy.substring(PROXY_PREFIX.length).trim(); |
+ // Look for proxy authentication. |
+ int at = proxy.indexOf("@"); |
+ if (at != -1) { |
+ String userinfo = proxy.substring(0, at).trim(); |
+ proxy = proxy.substring(at + 1).trim(); |
+ int colon = userinfo.indexOf(":"); |
+ if (colon == -1 || colon == 0 || colon == proxy.length - 1) { |
+ throw new HttpException( |
+ "Invalid proxy configuration $configuration"); |
+ } |
+ username = userinfo.substring(0, colon).trim(); |
+ password = userinfo.substring(colon + 1).trim(); |
+ } |
+ // Look for proxy host and port. |
int colon = proxy.indexOf(":"); |
if (colon == -1 || colon == 0 || colon == proxy.length - 1) { |
throw new HttpException( |
"Invalid proxy configuration $configuration"); |
} |
- // Skip the "PROXY " prefix. |
- String host = proxy.substring(PROXY_PREFIX.length, colon).trim(); |
+ String host = proxy.substring(0, colon).trim(); |
String portString = proxy.substring(colon + 1).trim(); |
int port; |
try { |
@@ -1606,7 +1718,7 @@ class _ProxyConfiguration { |
"Invalid proxy configuration $configuration, " |
"invalid port '$portString'"); |
} |
- proxies.add(new _Proxy(host, port)); |
+ proxies.add(new _Proxy(host, port, username, password)); |
} else if (proxy.trim() == DIRECT_PREFIX) { |
proxies.add(new _Proxy.direct()); |
} else { |
@@ -1624,11 +1736,17 @@ class _ProxyConfiguration { |
class _Proxy { |
- const _Proxy(this.host, this.port) : isDirect = false; |
- const _Proxy.direct() : host = null, port = null, isDirect = true; |
+ const _Proxy( |
+ this.host, this.port, this.username, this.password) : isDirect = false; |
+ const _Proxy.direct() : host = null, port = null, |
+ username = null, password = null, isDirect = true; |
+ |
+ bool get isAuthenticated => username != null; |
final String host; |
final int port; |
+ final String username; |
+ final String password; |
final bool isDirect; |
} |
@@ -1775,6 +1893,26 @@ class _Credentials { |
} |
+class _ProxyCredentials { |
+ _ProxyCredentials(this.host, this.port, this.realm, this.credentials); |
+ |
+ _AuthenticationScheme get scheme => credentials.scheme; |
+ |
+ bool applies(_Proxy proxy, _AuthenticationScheme scheme) { |
+ return proxy.host == host && proxy.port == port; |
+ } |
+ |
+ void authorize(HttpClientRequest request) { |
+ credentials.authorizeProxy(this, request); |
+ } |
+ |
+ String host; |
+ int port; |
+ String realm; |
+ _HttpClientCredentials credentials; |
+} |
+ |
+ |
abstract class _HttpClientCredentials implements HttpClientCredentials { |
_AuthenticationScheme get scheme; |
void authorize(_Credentials credentials, HttpClientRequest request); |
@@ -1789,7 +1927,7 @@ class _HttpClientBasicCredentials |
_AuthenticationScheme get scheme => _AuthenticationScheme.BASIC; |
- void authorize(_Credentials _, HttpClientRequest request) { |
+ String authorization() { |
// 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 |
@@ -1798,7 +1936,15 @@ class _HttpClientBasicCredentials |
// now always use UTF-8 encoding. |
String auth = |
CryptoUtils.bytesToBase64(_encodeString("$username:$password")); |
- request.headers.set(HttpHeaders.AUTHORIZATION, "Basic $auth"); |
+ return "Basic $auth"; |
+ } |
+ |
+ void authorize(_Credentials _, HttpClientRequest request) { |
+ request.headers.set(HttpHeaders.AUTHORIZATION, authorization()); |
+ } |
+ |
+ void authorizeProxy(_ProxyCredentials _, HttpClientRequest request) { |
+ request.headers.set(HttpHeaders.PROXY_AUTHORIZATION, authorization()); |
} |
String username; |
@@ -1819,6 +1965,11 @@ class _HttpClientDigestCredentials |
throw new UnsupportedError("Digest authentication not yet supported"); |
} |
+ void authorizeProxy(_Credentials credentials, HttpClientRequest request) { |
+ // TODO(sgjesse): Implement!!! |
+ throw new UnsupportedError("Digest authentication not yet supported"); |
+ } |
+ |
String username; |
String password; |
} |