diff options
Diffstat (limited to 'net')
-rw-r--r-- | net/base/address_list.cc | 2 | ||||
-rw-r--r-- | net/http/http_auth_handler.cc | 9 | ||||
-rw-r--r-- | net/http/http_auth_handler.h | 20 | ||||
-rw-r--r-- | net/http/http_auth_handler_factory.h | 10 | ||||
-rw-r--r-- | net/http/http_auth_handler_negotiate.h | 40 | ||||
-rw-r--r-- | net/http/http_auth_handler_negotiate_posix.cc | 15 | ||||
-rw-r--r-- | net/http/http_auth_handler_negotiate_win.cc | 118 | ||||
-rw-r--r-- | net/http/http_auth_handler_ntlm.cc | 12 | ||||
-rw-r--r-- | net/http/http_auth_handler_ntlm.h | 3 | ||||
-rw-r--r-- | net/http/http_auth_handler_ntlm_win.cc | 2 | ||||
-rw-r--r-- | net/http/http_auth_sspi_win.cc | 15 | ||||
-rw-r--r-- | net/http/http_auth_sspi_win.h | 8 | ||||
-rw-r--r-- | net/http/http_network_transaction.cc | 30 | ||||
-rw-r--r-- | net/http/http_network_transaction.h | 4 | ||||
-rw-r--r-- | net/http/http_network_transaction_unittest.cc | 198 |
15 files changed, 454 insertions, 32 deletions
diff --git a/net/base/address_list.cc b/net/base/address_list.cc index 55a5992..89c9496 100644 --- a/net/base/address_list.cc +++ b/net/base/address_list.cc @@ -135,7 +135,7 @@ int AddressList::GetPort() const { bool AddressList::GetCanonicalName(std::string* canonical_name) const { DCHECK(canonical_name); - if (!data_->head || !data_->head->ai_canonname) + if (!data_ || !data_->head->ai_canonname) return false; canonical_name->assign(data_->head->ai_canonname); return true; diff --git a/net/http/http_auth_handler.cc b/net/http/http_auth_handler.cc index e61de2f..7aa114e 100644 --- a/net/http/http_auth_handler.cc +++ b/net/http/http_auth_handler.cc @@ -5,6 +5,7 @@ #include "net/http/http_auth_handler.h" #include "base/logging.h" +#include "net/base/net_errors.h" namespace net { @@ -28,4 +29,12 @@ bool HttpAuthHandler::InitFromChallenge( return ok; } +int HttpAuthHandler::ResolveCanonicalName(net::HostResolver* host_resolver, + CompletionCallback* callback, + const BoundNetLog& net_log) { + NOTREACHED(); + LOG(ERROR) << ErrorToString(ERR_NOT_IMPLEMENTED); + return ERR_NOT_IMPLEMENTED; +} + } // namespace net diff --git a/net/http/http_auth_handler.h b/net/http/http_auth_handler.h index d2eae13..663e04a 100644 --- a/net/http/http_auth_handler.h +++ b/net/http/http_auth_handler.h @@ -8,10 +8,13 @@ #include <string> #include "base/ref_counted.h" +#include "net/base/completion_callback.h" #include "net/http/http_auth.h" namespace net { +class BoundNetLog; +class HostResolver; class HttpRequestInfo; class ProxyInfo; @@ -81,6 +84,11 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> { // TODO(cbentzel): Add a pointer to Firefox documentation about risk. virtual bool SupportsDefaultCredentials() { return false; } + // Returns whether the canonical DNS name for the origin host needs to be + // resolved. The Negotiate auth scheme typically uses the canonical DNS + // name when constructing the Kerberos SPN. + virtual bool NeedsCanonicalName() { return false; } + // TODO(cbentzel): Separate providing credentials from generating the // authentication token in the API. @@ -103,6 +111,14 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> { const ProxyInfo* proxy, std::string* auth_token) = 0; + // Resolves the canonical name for the |origin_| host. The canonical + // name is used by the Negotiate scheme to generate a valid Kerberos + // SPN. + // The return value is a net error code. + virtual int ResolveCanonicalName(HostResolver* host_resolver, + CompletionCallback* callback, + const BoundNetLog& net_log); + protected: enum Property { ENCRYPTS_IDENTITY = 1 << 0, @@ -121,14 +137,14 @@ class HttpAuthHandler : public base::RefCounted<HttpAuthHandler> { // scheme_, realm_, score_, properties_ virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) = 0; - // The lowercase auth-scheme {"basic", "digest", "ntlm", ...} + // The lowercase auth-scheme {"basic", "digest", "ntlm", "negotiate"} std::string scheme_; // The realm. Used by "basic" and "digest". std::string realm_; // The {scheme, host, port} for the authentication target. Used by "ntlm" - // to construct the service principal name. + // and "negotiate" to construct the service principal name. GURL origin_; // The score for this challenge. Higher numbers are better. diff --git a/net/http/http_auth_handler_factory.h b/net/http/http_auth_handler_factory.h index f677455..587bb30 100644 --- a/net/http/http_auth_handler_factory.h +++ b/net/http/http_auth_handler_factory.h @@ -105,6 +105,13 @@ class HttpAuthHandlerRegistryFactory : public HttpAuthHandlerFactory { void RegisterSchemeFactory(const std::string& scheme, HttpAuthHandlerFactory* factory); + // Retrieve the factory for the specified |scheme|. If no factory exists + // for the |scheme|, NULL is returned. The returned factory must not be + // deleted by the caller, and it is guaranteed to be valid until either + // a new factory is registered for the same scheme, or until this + // registry factory is destroyed. + HttpAuthHandlerFactory* GetSchemeFactory(const std::string& scheme) const; + // Creates an auth handler by dispatching out to the registered factories // based on the first token in |challenge|. virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge, @@ -113,9 +120,6 @@ class HttpAuthHandlerRegistryFactory : public HttpAuthHandlerFactory { scoped_refptr<HttpAuthHandler>* handler); private: - // Retrieve the factory for the specified |scheme| - HttpAuthHandlerFactory* GetSchemeFactory(const std::string& scheme) const; - typedef std::map<std::string, HttpAuthHandlerFactory*> FactoryMap; FactoryMap factory_map_; diff --git a/net/http/http_auth_handler_negotiate.h b/net/http/http_auth_handler_negotiate.h index f34664c..3450fc4 100644 --- a/net/http/http_auth_handler_negotiate.h +++ b/net/http/http_auth_handler_negotiate.h @@ -9,6 +9,7 @@ #include <string> +#include "net/base/address_list.h" #include "net/http/http_auth_handler.h" #include "net/http/http_auth_handler_factory.h" @@ -18,6 +19,8 @@ namespace net { +class SingleRequestHostResolver; + // Handler for WWW-Authenticate: Negotiate protocol. // // See http://tools.ietf.org/html/rfc4178 and http://tools.ietf.org/html/rfc4559 @@ -30,6 +33,22 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler { Factory(); virtual ~Factory(); + // |disable_cname_lookup()| and |set_disable_cname_lookup()| get/set whether + // the auth handlers generated by this factory should skip looking up the + // canonical DNS name of the the host that they are authenticating to when + // generating the SPN. The default value is false. + bool disable_cname_lookup() const { return disable_cname_lookup_; } + void set_disable_cname_lookup(bool disable_cname_lookup) { + disable_cname_lookup_ = disable_cname_lookup; + } + + // |use_port()| and |set_use_port()| get/set whether the auth handlers + // generated by this factory should include the port number of the server + // they are authenticating to when constructing a Kerberos SPN. The default + // value is false. + bool use_port() const { return use_port_; } + void set_use_port(bool use_port) { use_port_ = use_port; } + virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge, HttpAuth::Target target, const GURL& origin, @@ -47,6 +66,8 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler { } #endif // defined(OS_WIN) private: + bool disable_cname_lookup_; + bool use_port_; #if defined(OS_WIN) ULONG max_token_length_; bool first_creation_; @@ -56,7 +77,8 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler { }; #if defined(OS_WIN) - HttpAuthHandlerNegotiate(SSPILibrary* sspi_library, ULONG max_token_length); + HttpAuthHandlerNegotiate(SSPILibrary* sspi_library, ULONG max_token_length, + bool disable_cname_lookup, bool use_port); #else HttpAuthHandlerNegotiate(); #endif @@ -67,6 +89,8 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler { virtual bool SupportsDefaultCredentials(); + virtual bool NeedsCanonicalName(); + virtual int GenerateAuthToken(const std::wstring& username, const std::wstring& password, const HttpRequestInfo* request, @@ -77,6 +101,10 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler { const ProxyInfo* proxy, std::string* auth_token); + virtual int ResolveCanonicalName(HostResolver* host_resolver, + CompletionCallback* callback, + const BoundNetLog& net_log); + protected: virtual bool Init(HttpAuth::ChallengeTokenizer* challenge); @@ -84,7 +112,17 @@ class HttpAuthHandlerNegotiate : public HttpAuthHandler { ~HttpAuthHandlerNegotiate(); #if defined(OS_WIN) + void OnResolveCanonicalName(int result); + std::wstring CreateSPN(const AddressList& address_list, const GURL& orign); + HttpAuthSSPI auth_sspi_; + AddressList address_list_; + scoped_ptr<SingleRequestHostResolver> single_resolve_; + CompletionCallback* user_callback_; + CompletionCallbackImpl<HttpAuthHandlerNegotiate> resolve_cname_callback_; + bool disable_cname_lookup_; + bool use_port_; + std::wstring spn_; #endif }; diff --git a/net/http/http_auth_handler_negotiate_posix.cc b/net/http/http_auth_handler_negotiate_posix.cc index 2b96597..7b57853 100644 --- a/net/http/http_auth_handler_negotiate_posix.cc +++ b/net/http/http_auth_handler_negotiate_posix.cc @@ -61,7 +61,20 @@ int HttpAuthHandlerNegotiate::GenerateDefaultAuthToken( return ERR_NOT_IMPLEMENTED; } -HttpAuthHandlerNegotiate::Factory::Factory() { +bool HttpAuthHandlerNegotiate::NeedsCanonicalName() { + return false; +} + +int HttpAuthHandlerNegotiate::ResolveCanonicalName(HostResolver* host_resolver, + CompletionCallback* callback, + const BoundNetLog& net_log) { + NOTREACHED(); + LOG(ERROR) << ErrorToString(ERR_NOT_IMPLEMENTED); + return ERR_NOT_IMPLEMENTED; +} + +HttpAuthHandlerNegotiate::Factory::Factory() + : disable_cname_lookup_(false), use_port_(false) { } HttpAuthHandlerNegotiate::Factory::~Factory() { diff --git a/net/http/http_auth_handler_negotiate_win.cc b/net/http/http_auth_handler_negotiate_win.cc index fb849d4..6c6e767 100644 --- a/net/http/http_auth_handler_negotiate_win.cc +++ b/net/http/http_auth_handler_negotiate_win.cc @@ -5,14 +5,23 @@ #include "net/http/http_auth_handler_negotiate.h" #include "base/logging.h" +#include "net/base/address_family.h" +#include "net/base/host_resolver.h" #include "net/base/net_errors.h" #include "net/http/http_auth_filter.h" namespace net { HttpAuthHandlerNegotiate::HttpAuthHandlerNegotiate(SSPILibrary* library, - ULONG max_token_length) - : auth_sspi_(library, "Negotiate", NEGOSSP_NAME, max_token_length) { + ULONG max_token_length, + bool disable_cname_lookup, + bool use_port) + : auth_sspi_(library, "Negotiate", NEGOSSP_NAME, max_token_length), + user_callback_(NULL), + ALLOW_THIS_IN_INITIALIZER_LIST(resolve_cname_callback_( + this, &HttpAuthHandlerNegotiate::OnResolveCanonicalName)), + disable_cname_lookup_(disable_cname_lookup), + use_port_(use_port) { } HttpAuthHandlerNegotiate::~HttpAuthHandlerNegotiate() { @@ -27,7 +36,7 @@ int HttpAuthHandlerNegotiate::GenerateAuthToken( return auth_sspi_.GenerateAuthToken( &username, &password, - origin_, + spn_, request, proxy, auth_token); @@ -55,6 +64,100 @@ bool HttpAuthHandlerNegotiate::SupportsDefaultCredentials() { return true; } +bool HttpAuthHandlerNegotiate::NeedsCanonicalName() { + if (!spn_.empty()) + return false; + if (disable_cname_lookup_) { + spn_ = CreateSPN(address_list_, origin_); + address_list_.Reset(); + return false; + } + return true; +} + +int HttpAuthHandlerNegotiate::ResolveCanonicalName(HostResolver* resolver, + CompletionCallback* callback, + const BoundNetLog& net_log) { + // TODO(cbentzel): Add reverse DNS lookup for numeric addresses. + DCHECK(!single_resolve_.get()); + DCHECK(!disable_cname_lookup_); + DCHECK(callback); + + HostResolver::RequestInfo info(origin_.host(), 0); + info.set_host_resolver_flags(HOST_RESOLVER_CANONNAME); + single_resolve_.reset(new SingleRequestHostResolver(resolver)); + int rv = single_resolve_->Resolve(info, &address_list_, + &resolve_cname_callback_, + net_log); + if (rv == ERR_IO_PENDING) { + user_callback_ = callback; + return rv; + } + OnResolveCanonicalName(rv); + // Always return OK. OnResolveCanonicalName logs the error code if not + // OK and attempts to use the original origin_ hostname rather than failing + // the auth attempt completely. + return OK; +} + +void HttpAuthHandlerNegotiate::OnResolveCanonicalName(int result) { + if (result != OK) { + // Even in the error case, try to use origin_.host instead of + // passing the failure on to the caller. + LOG(INFO) << "Problem finding canonical name for SPN for host " + << origin_.host() << ": " << ErrorToString(result); + result = OK; + } + spn_ = CreateSPN(address_list_, origin_); + address_list_.Reset(); + if (user_callback_) { + CompletionCallback* callback = user_callback_; + user_callback_ = NULL; + callback->Run(result); + } +} + +std::wstring HttpAuthHandlerNegotiate::CreateSPN( + const AddressList& address_list, const GURL& origin) { + // Kerberos SPNs are in the form HTTP/<host>:<port> + // http://msdn.microsoft.com/en-us/library/ms677601%28VS.85%29.aspx + // + // However, reality differs from the specification. A good description of + // the problems can be found here: + // http://blog.michelbarneveld.nl/michel/archive/2009/11/14/the-reason-why-kb911149-and-kb908209-are-not-the-soluton.aspx + // + // Typically the <host> portion should be the canonical FQDN for the service. + // If this could not be resolved, the original hostname in the URL will be + // attempted instead. However, some intranets register SPNs using aliases + // for the same canonical DNS name to allow multiple web services to reside + // on the same host machine without requiring different ports. IE6 and IE7 + // have hotpatches that allow the default behavior to be overridden. + // http://support.microsoft.com/kb/911149 + // http://support.microsoft.com/kb/938305 + // + // According to the spec, the <port> option should be included if it is a + // non-standard port (i.e. not 80 or 443 in the HTTP case). However, + // historically browsers have not included the port, even on non-standard + // ports. IE6 required a hotpatch and a registry setting to enable + // including non-standard ports, and IE7 and IE8 also require the same + // registry setting, but no hotpatch. Firefox does not appear to have an + // option to include non-standard ports as of 3.6. + // http://support.microsoft.com/kb/908209 + // + // Without any command-line flags, Chrome matches the behavior of Firefox + // and IE. Users can override the behavior so aliases are allowed and + // non-standard ports are included. + int port = origin.EffectiveIntPort(); + std::string server; + if (!address_list.GetCanonicalName(&server)) + server = origin.host(); + if (port != 80 && port != 443 && use_port_) { + return ASCIIToWide(StringPrintf("HTTP/%s:%d", server.c_str(), port)); + } else { + return ASCIIToWide(StringPrintf("HTTP/%s", server.c_str())); + } +} + int HttpAuthHandlerNegotiate::GenerateDefaultAuthToken( const HttpRequestInfo* request, const ProxyInfo* proxy, @@ -62,14 +165,16 @@ int HttpAuthHandlerNegotiate::GenerateDefaultAuthToken( return auth_sspi_.GenerateAuthToken( NULL, // username NULL, // password - origin_, + spn_, request, proxy, auth_token); } HttpAuthHandlerNegotiate::Factory::Factory() - : max_token_length_(0), + : disable_cname_lookup_(false), + use_port_(false), + max_token_length_(0), first_creation_(true), is_unsupported_(false), sspi_library_(SSPILibrary::GetDefault()) { @@ -96,7 +201,8 @@ int HttpAuthHandlerNegotiate::Factory::CreateAuthHandler( // TODO(cbentzel): Move towards model of parsing in the factory // method and only constructing when valid. scoped_refptr<HttpAuthHandler> tmp_handler( - new HttpAuthHandlerNegotiate(sspi_library_, max_token_length_)); + new HttpAuthHandlerNegotiate(sspi_library_, max_token_length_, + disable_cname_lookup_, use_port_)); if (!tmp_handler->InitFromChallenge(challenge, target, origin)) return ERR_INVALID_RESPONSE; handler->swap(tmp_handler); diff --git a/net/http/http_auth_handler_ntlm.cc b/net/http/http_auth_handler_ntlm.cc index dab239d..ed3eb3a 100644 --- a/net/http/http_auth_handler_ntlm.cc +++ b/net/http/http_auth_handler_ntlm.cc @@ -9,6 +9,7 @@ #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "net/base/net_errors.h" +#include "net/base/net_util.h" namespace net { @@ -22,7 +23,7 @@ int HttpAuthHandlerNTLM::GenerateAuthToken( return auth_sspi_.GenerateAuthToken( &username, &password, - origin_, + CreateSPN(origin_), request, proxy, auth_token); @@ -109,4 +110,13 @@ bool HttpAuthHandlerNTLM::ParseChallenge( #endif // defined(NTLM_SSPI) } +// static +std::wstring HttpAuthHandlerNTLM::CreateSPN(const GURL& origin) { + // The service principal name of the destination server. See + // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx + std::wstring target(L"HTTP/"); + target.append(ASCIIToWide(GetHostAndPort(origin))); + return target; +} + } // namespace net diff --git a/net/http/http_auth_handler_ntlm.h b/net/http/http_auth_handler_ntlm.h index 542bf92..f027471 100644 --- a/net/http/http_auth_handler_ntlm.h +++ b/net/http/http_auth_handler_ntlm.h @@ -145,6 +145,9 @@ class HttpAuthHandlerNTLM : public HttpAuthHandler { void** out_token, uint32* out_token_len); + // Create an NTLM SPN to identify the |origin| server. + static std::wstring CreateSPN(const GURL& origin); + #if defined(NTLM_SSPI) HttpAuthSSPI auth_sspi_; #endif diff --git a/net/http/http_auth_handler_ntlm_win.cc b/net/http/http_auth_handler_ntlm_win.cc index baa7f86..04c53f3 100644 --- a/net/http/http_auth_handler_ntlm_win.cc +++ b/net/http/http_auth_handler_ntlm_win.cc @@ -47,7 +47,7 @@ int HttpAuthHandlerNTLM::GenerateDefaultAuthToken( return auth_sspi_.GenerateAuthToken( NULL, // username NULL, // password - origin_, + CreateSPN(origin_), request, proxy, auth_token); diff --git a/net/http/http_auth_sspi_win.cc b/net/http/http_auth_sspi_win.cc index 5349e76..1f3165d 100644 --- a/net/http/http_auth_sspi_win.cc +++ b/net/http/http_auth_sspi_win.cc @@ -12,7 +12,6 @@ #include "base/singleton.h" #include "base/string_util.h" #include "net/base/net_errors.h" -#include "net/base/net_util.h" #include "net/http/http_auth.h" namespace net { @@ -162,7 +161,7 @@ bool HttpAuthSSPI::ParseChallenge(HttpAuth::ChallengeTokenizer* tok) { int HttpAuthSSPI::GenerateAuthToken(const std::wstring* username, const std::wstring* password, - const GURL& origin, + const std::wstring& spn, const HttpRequestInfo* request, const ProxyInfo* proxy, std::string* auth_token) { @@ -178,7 +177,7 @@ int HttpAuthSSPI::GenerateAuthToken(const std::wstring* username, void* out_buf; int out_buf_len; int rv = GetNextSecurityToken( - origin, + spn, static_cast<void *>(const_cast<char *>( decoded_server_auth_token_.c_str())), decoded_server_auth_token_.length(), @@ -223,7 +222,7 @@ int HttpAuthSSPI::OnFirstRound(const std::wstring* username, } int HttpAuthSSPI::GetNextSecurityToken( - const GURL& origin, + const std::wstring& spn, const void * in_token, int in_token_len, void** out_token, @@ -269,17 +268,11 @@ int HttpAuthSSPI::GetNextSecurityToken( if (!out_buffer.pvBuffer) return ERR_OUT_OF_MEMORY; - // The service principal name of the destination server. See - // http://msdn.microsoft.com/en-us/library/ms677949%28VS.85%29.aspx - std::wstring target(L"HTTP/"); - target.append(ASCIIToWide(GetHostAndPort(origin))); - wchar_t* target_name = const_cast<wchar_t*>(target.c_str()); - // This returns a token that is passed to the remote server. status = library_->InitializeSecurityContext( &cred_, // phCredential ctxt_ptr, // phContext - target_name, // pszTargetName + const_cast<wchar_t *>(spn.c_str()), // pszTargetName 0, // fContextReq 0, // Reserved1 (must be 0) SECURITY_NATIVE_DREP, // TargetDataRep diff --git a/net/http/http_auth_sspi_win.h b/net/http/http_auth_sspi_win.h index c925920..a6fb49e 100644 --- a/net/http/http_auth_sspi_win.h +++ b/net/http/http_auth_sspi_win.h @@ -18,8 +18,6 @@ #include "net/http/http_auth.h" -class GURL; - namespace net { class HttpRequestInfo; @@ -89,12 +87,14 @@ class HttpAuthSSPI { // Generates an authentication token. // The return value is an error code. If it's not |OK|, the value of // |*auth_token| is unspecified. + // |spn| is the Service Principal Name of the server that the token is + // being generated for. // If this is the first round of a multiple round scheme, credentials are // obtained using |*username| and |*password|. If |username| and |password| // are NULL, the default credentials are used instead. int GenerateAuthToken(const std::wstring* username, const std::wstring* password, - const GURL& origin, + const std::wstring& spn, const HttpRequestInfo* request, const ProxyInfo* proxy, std::string* auth_token); @@ -104,7 +104,7 @@ class HttpAuthSSPI { const std::wstring* password); int GetNextSecurityToken( - const GURL& origin, + const std::wstring& spn, const void* in_token, int in_token_len, void** out_token, diff --git a/net/http/http_network_transaction.cc b/net/http/http_network_transaction.cc index 467f63c..9078f35 100644 --- a/net/http/http_network_transaction.cc +++ b/net/http/http_network_transaction.cc @@ -574,6 +574,12 @@ int HttpNetworkTransaction::DoLoop(int result) { TRACE_EVENT_END("http.read_headers", request_, request_->url.spec()); net_log_.EndEvent(NetLog::TYPE_HTTP_TRANSACTION_READ_HEADERS); break; + case STATE_RESOLVE_CANONICAL_NAME: + rv = DoResolveCanonicalName(); + break; + case STATE_RESOLVE_CANONICAL_NAME_COMPLETE: + rv = DoResolveCanonicalNameComplete(rv); + break; case STATE_READ_BODY: DCHECK_EQ(OK, rv); TRACE_EVENT_BEGIN("http.read_body", request_, request_->url.spec()); @@ -1116,6 +1122,23 @@ int HttpNetworkTransaction::DoReadHeadersComplete(int result) { return OK; } +int HttpNetworkTransaction::DoResolveCanonicalName() { + HttpAuthHandler* auth_handler = auth_handler_[pending_auth_target_]; + DCHECK(auth_handler); + next_state_ = STATE_RESOLVE_CANONICAL_NAME_COMPLETE; + return auth_handler->ResolveCanonicalName(session_->host_resolver(), + &io_callback_, net_log_); +} + +int HttpNetworkTransaction::DoResolveCanonicalNameComplete(int result) { + // The STATE_RESOLVE_CANONICAL_NAME state ends the Start sequence when the + // canonical name of the server needs to be determined. Normally + // DoReadHeadersComplete completes the sequence. The next state is + // intentionally not set as it should be STATE_NONE; + DCHECK_EQ(STATE_NONE, next_state_); + return result; +} + int HttpNetworkTransaction::DoReadBody() { DCHECK(read_buf_); DCHECK_GT(read_buf_len_, 0); @@ -1917,6 +1940,13 @@ int HttpNetworkTransaction::HandleAuthChallenge() { // pass the challenge information back to the client. PopulateAuthChallenge(target, auth_origin); } + + // SPN determination (for Negotiate) requires a DNS lookup to find the + // canonical name. This needs to be done asynchronously to prevent blocking + // the IO thread. + if (auth_handler_[target]->NeedsCanonicalName()) + next_state_ = STATE_RESOLVE_CANONICAL_NAME; + return OK; } diff --git a/net/http/http_network_transaction.h b/net/http/http_network_transaction.h index 113c0b5..220e789 100644 --- a/net/http/http_network_transaction.h +++ b/net/http/http_network_transaction.h @@ -84,6 +84,8 @@ class HttpNetworkTransaction : public HttpTransaction { STATE_SEND_REQUEST_COMPLETE, STATE_READ_HEADERS, STATE_READ_HEADERS_COMPLETE, + STATE_RESOLVE_CANONICAL_NAME, + STATE_RESOLVE_CANONICAL_NAME_COMPLETE, STATE_READ_BODY, STATE_READ_BODY_COMPLETE, STATE_DRAIN_BODY_FOR_AUTH_RESTART, @@ -130,6 +132,8 @@ class HttpNetworkTransaction : public HttpTransaction { int DoSendRequestComplete(int result); int DoReadHeaders(); int DoReadHeadersComplete(int result); + int DoResolveCanonicalName(); + int DoResolveCanonicalNameComplete(int result); int DoReadBody(); int DoReadBodyComplete(int result); int DoDrainBodyForAuthRestart(); diff --git a/net/http/http_network_transaction_unittest.cc b/net/http/http_network_transaction_unittest.cc index aa18df5..b9458d5 100644 --- a/net/http/http_network_transaction_unittest.cc +++ b/net/http/http_network_transaction_unittest.cc @@ -65,7 +65,6 @@ ProxyService* CreateFixedProxyService(const std::string& proxy) { return ProxyService::CreateFixed(proxy_config); } - HttpNetworkSession* CreateSession(SessionDependencies* session_deps) { return new HttpNetworkSession(NULL, session_deps->host_resolver, @@ -4679,4 +4678,201 @@ TEST_F(HttpNetworkTransactionTest, FailNpnSpdyAndFallback) { EXPECT_EQ("hello world", response_data); } +// MockAuthHandlerCanonical is used by the ResolveCanonicalName +// HttpNetworkTransaction unit test below. Callers set up expectations for +// whether the canonical name needs to be resolved. +class MockAuthHandlerCanonical : public HttpAuthHandler { + public: + enum Resolve { + RESOLVE_INIT, + RESOLVE_SKIP, + RESOLVE_SYNC, + RESOLVE_ASYNC, + RESOLVE_TESTED, + }; + + MockAuthHandlerCanonical() : resolve_(RESOLVE_INIT), user_callback_(NULL) {} + virtual ~MockAuthHandlerCanonical() {} + + void SetResolveExpectation(Resolve resolve) { + EXPECT_EQ(RESOLVE_INIT, resolve_); + resolve_ = resolve; + } + + void ResetResolveExpectation() { + EXPECT_EQ(RESOLVE_TESTED, resolve_); + resolve_ = RESOLVE_INIT; + } + + virtual bool NeedsCanonicalName() { + switch (resolve_) { + case RESOLVE_SYNC: + case RESOLVE_ASYNC: + return true; + case RESOLVE_SKIP: + resolve_ = RESOLVE_TESTED; + return false; + default: + NOTREACHED(); + return false; + } + } + + virtual int ResolveCanonicalName(HostResolver* host_resolver, + CompletionCallback* callback, + const BoundNetLog& net_log) { + EXPECT_NE(RESOLVE_TESTED, resolve_); + int rv = OK; + switch (resolve_) { + case RESOLVE_SYNC: + resolve_ = RESOLVE_TESTED; + break; + case RESOLVE_ASYNC: + EXPECT_TRUE(user_callback_ == NULL); + rv = ERR_IO_PENDING; + user_callback_ = callback; + MessageLoop::current()->PostTask( + FROM_HERE, + NewRunnableMethod( + this, &MockAuthHandlerCanonical::OnResolveCanonicalName)); + break; + default: + NOTREACHED(); + break; + } + return rv; + } + + void OnResolveCanonicalName() { + EXPECT_EQ(RESOLVE_ASYNC, resolve_); + EXPECT_TRUE(user_callback_ != NULL); + resolve_ = RESOLVE_TESTED; + CompletionCallback* callback = user_callback_; + user_callback_ = NULL; + callback->Run(OK); + } + + virtual bool Init(HttpAuth::ChallengeTokenizer* challenge) { + scheme_ = "mock"; + score_ = 1; + properties_ = 0; + return true; + } + + virtual int GenerateAuthToken(const std::wstring& username, + const std::wstring& password, + const HttpRequestInfo* request, + const ProxyInfo* proxy, + std::string* auth_token) { + auth_token->assign("Mock AUTH myserver.example.com"); + return OK; + } + + virtual int GenerateDefaultAuthToken(const HttpRequestInfo* request, + const ProxyInfo* proxy, + std::string* auth_token) { + auth_token->assign("Mock DEFAULT_AUTH myserver.example.com"); + return OK; + } + + // The Factory class simply returns the same handler each time + // CreateAuthHandler is called. + class Factory : public HttpAuthHandlerFactory { + public: + Factory() {} + virtual ~Factory() {} + + void set_mock_handler(MockAuthHandlerCanonical* mock_handler) { + mock_handler_ = mock_handler; + } + MockAuthHandlerCanonical* mock_handler() const { + return mock_handler_.get(); + } + + virtual int CreateAuthHandler(HttpAuth::ChallengeTokenizer* challenge, + HttpAuth::Target target, + const GURL& origin, + scoped_refptr<HttpAuthHandler>* handler) { + *handler = mock_handler_; + return OK; + } + + private: + scoped_refptr<MockAuthHandlerCanonical> mock_handler_; + }; + + private: + Resolve resolve_; + CompletionCallback* user_callback_; +}; + +// Tests that ResolveCanonicalName is handled correctly by the +// HttpNetworkTransaction. +TEST_F(HttpNetworkTransactionTest, ResolveCanonicalName) { + SessionDependencies session_deps; + scoped_refptr<MockAuthHandlerCanonical> auth_handler( + new MockAuthHandlerCanonical()); + auth_handler->Init(NULL); + MockAuthHandlerCanonical::Factory* auth_factory( + new MockAuthHandlerCanonical::Factory()); + auth_factory->set_mock_handler(auth_handler); + session_deps.http_auth_handler_factory.reset(auth_factory); + + for (int i = 0; i < 2; ++i) { + scoped_ptr<HttpTransaction> trans( + new HttpNetworkTransaction(CreateSession(&session_deps))); + + // Set up expectations for this pass of the test. Many of the EXPECT calls + // are contained inside the MockAuthHandlerCanonical codebase in response to + // the expectations. + MockAuthHandlerCanonical::Resolve resolve = (i == 0) ? + MockAuthHandlerCanonical::RESOLVE_SYNC : + MockAuthHandlerCanonical::RESOLVE_ASYNC; + auth_handler->SetResolveExpectation(resolve); + HttpRequestInfo request; + request.method = "GET"; + request.url = GURL("http://myserver/"); + request.load_flags = 0; + + MockWrite data_writes1[] = { + MockWrite("GET / HTTP/1.1\r\n" + "Host: myserver\r\n" + "Connection: keep-alive\r\n\r\n"), + }; + + MockRead data_reads1[] = { + MockRead("HTTP/1.1 401 Unauthorized\r\n"), + MockRead("WWW-Authenticate: Mock myserver.example.com\r\n"), + MockRead("Content-Type: text/html; charset=iso-8859-1\r\n"), + MockRead("Content-Length: 14\r\n\r\n"), + MockRead("Unauthorized\r\n"), + }; + + StaticSocketDataProvider data1(data_reads1, arraysize(data_reads1), + data_writes1, arraysize(data_writes1)); + session_deps.socket_factory.AddSocketDataProvider(&data1); + + TestCompletionCallback callback1; + + int rv = trans->Start(&request, &callback1, NULL); + EXPECT_EQ(ERR_IO_PENDING, rv); + + rv = callback1.WaitForResult(); + EXPECT_EQ(OK, rv); + + const HttpResponseInfo* response = trans->GetResponseInfo(); + EXPECT_FALSE(response == NULL); + + // The password prompt is set after the canonical name is resolved. + // If it isn't present or is incorrect, it indicates that the scheme + // did not complete correctly. + EXPECT_FALSE(response->auth_challenge.get() == NULL); + + EXPECT_EQ(L"myserver:80", response->auth_challenge->host_and_port); + EXPECT_EQ(L"", response->auth_challenge->realm); + EXPECT_EQ(L"mock", response->auth_challenge->scheme); + auth_handler->ResetResolveExpectation(); + } +} + } // namespace net |