Index: net/url_request/url_request_unittest.cc |
diff --git a/net/url_request/url_request_unittest.cc b/net/url_request/url_request_unittest.cc |
index 63e7c757244ddf6c31f28adcd369cd86c2a0ef48..1bd981e37e6713b0258f3a752ce35746b7f36769 100644 |
--- a/net/url_request/url_request_unittest.cc |
+++ b/net/url_request/url_request_unittest.cc |
@@ -338,6 +338,9 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
void set_redirect_url(const GURL& url) { |
redirect_url_ = url; |
} |
+ void set_redirect_url_on_headers_received(const GURL& url) { |
+ redirect_url_on_headers_received_ = url; |
+ } |
void set_block_on(int block_on) { |
block_on_ = block_on; |
@@ -367,7 +370,8 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
URLRequest* request, |
const CompletionCallback& callback, |
const HttpResponseHeaders* original_response_headers, |
- scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE; |
+ scoped_refptr<HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) OVERRIDE; |
virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired( |
URLRequest* request, |
@@ -390,7 +394,8 @@ class BlockingNetworkDelegate : public TestNetworkDelegate { |
int retval_; // To be returned in non-auth stages. |
AuthRequiredResponse auth_retval_; |
- GURL redirect_url_; // Used if non-empty. |
+ GURL redirect_url_; // Used if non-empty during OnBeforeURLRequest. |
+ GURL redirect_url_on_headers_received_; // Used if non-empty. |
int block_on_; // Bit mask: in which stages to block. |
// |auth_credentials_| will be copied to |*target_auth_credential_| on |
@@ -482,10 +487,26 @@ int BlockingNetworkDelegate::OnHeadersReceived( |
URLRequest* request, |
const CompletionCallback& callback, |
const HttpResponseHeaders* original_response_headers, |
- scoped_refptr<HttpResponseHeaders>* override_response_headers) { |
+ scoped_refptr<HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) { |
TestNetworkDelegate::OnHeadersReceived( |
request, callback, original_response_headers, |
- override_response_headers); |
+ override_response_headers, allowed_unsafe_redirect_url); |
+ |
+ if (!redirect_url_on_headers_received_.is_empty()) { |
+ if (override_response_headers->get() == NULL) { |
+ *override_response_headers = new net::HttpResponseHeaders( |
+ original_response_headers->raw_headers()); |
+ } |
+ (*override_response_headers)->ReplaceStatusLine( |
+ "HTTP/1.1 307 Temporary Redirect"); |
+ (*override_response_headers)->RemoveHeader("location"); |
+ (*override_response_headers)->AddHeader( |
+ "Location: " + redirect_url_on_headers_received_.spec()); |
+ *allowed_unsafe_redirect_url = redirect_url_on_headers_received_; |
+ // Redirect just once. |
+ redirect_url_on_headers_received_ = GURL(); |
+ } |
return MaybeBlockStage(ON_HEADERS_RECEIVED, callback); |
} |
@@ -2416,8 +2437,8 @@ class FixedDateNetworkDelegate : public TestNetworkDelegate { |
net::URLRequest* request, |
const net::CompletionCallback& callback, |
const net::HttpResponseHeaders* original_response_headers, |
- scoped_refptr<net::HttpResponseHeaders>* override_response_headers) |
- OVERRIDE; |
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) OVERRIDE; |
private: |
std::string fixed_date_; |
@@ -2429,7 +2450,8 @@ int FixedDateNetworkDelegate::OnHeadersReceived( |
net::URLRequest* request, |
const net::CompletionCallback& callback, |
const net::HttpResponseHeaders* original_response_headers, |
- scoped_refptr<net::HttpResponseHeaders>* override_response_headers) { |
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) { |
net::HttpResponseHeaders* new_response_headers = |
new net::HttpResponseHeaders(original_response_headers->raw_headers()); |
@@ -2440,7 +2462,8 @@ int FixedDateNetworkDelegate::OnHeadersReceived( |
return TestNetworkDelegate::OnHeadersReceived(request, |
callback, |
original_response_headers, |
- override_response_headers); |
+ override_response_headers, |
+ allowed_unsafe_redirect_url); |
} |
// Test that cookie expiration times are adjusted for server/client clock |
@@ -2696,6 +2719,196 @@ class URLRequestTestHTTP : public URLRequestTest { |
LocalHttpTestServer test_server_; |
}; |
+namespace { |
+ |
+// RedirectOnHeadersReceivedNetworkDelegate redirects to |redirect_url|, |
+// optionally with an URL that has been marked safe for redirection. |
+class RedirectOnHeadersReceivedNetworkDelegate : public TestNetworkDelegate { |
+ public: |
+ explicit RedirectOnHeadersReceivedNetworkDelegate(const GURL& redirect_url) |
+ : redirect_url_(redirect_url) {} |
+ virtual ~RedirectOnHeadersReceivedNetworkDelegate() {} |
+ |
+ // net::NetworkDelegate implementation |
+ virtual int OnHeadersReceived( |
+ net::URLRequest* request, |
+ const net::CompletionCallback& callback, |
+ const net::HttpResponseHeaders* original_response_headers, |
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) OVERRIDE; |
+ |
+ void set_url_marked_safe_for_redirect(const GURL& safe_url) { |
+ safe_url_ = safe_url; |
+ } |
+ |
+ private: |
+ GURL redirect_url_; |
+ GURL safe_url_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(RedirectOnHeadersReceivedNetworkDelegate); |
+}; |
+ |
+int RedirectOnHeadersReceivedNetworkDelegate::OnHeadersReceived( |
+ net::URLRequest* request, |
+ const net::CompletionCallback& callback, |
+ const net::HttpResponseHeaders* original_response_headers, |
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) { |
+ |
+ if (!redirect_url_.is_empty()) { |
+ net::HttpResponseHeaders* new_response_headers = |
+ new net::HttpResponseHeaders(original_response_headers->raw_headers()); |
+ |
+ new_response_headers->ReplaceStatusLine("HTTP/1.1 302 Found"); |
+ new_response_headers->RemoveHeader("Location"); |
+ new_response_headers->AddHeader("Location: " + redirect_url_.spec()); |
+ |
+ *override_response_headers = new_response_headers; |
+ *allowed_unsafe_redirect_url = safe_url_; |
+ redirect_url_ = GURL(); |
+ } |
+ return TestNetworkDelegate::OnHeadersReceived(request, |
+ callback, |
+ original_response_headers, |
+ override_response_headers, |
+ allowed_unsafe_redirect_url); |
+} |
+ |
+} // namespace |
+ |
+// Tests that the allowed redirect URL is preserved when the Location header |
+// has been overwritten. |
+TEST_F(URLRequestTestHTTP, UnsafeRedirectToWhitelistedUnsafeURL) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ GURL unsafe_url("data:text/html,this-is-considered-an-unsafe-url"); |
+ RedirectOnHeadersReceivedNetworkDelegate network_delegate(unsafe_url); |
+ network_delegate.set_url_marked_safe_for_redirect(unsafe_url); |
+ default_context_.set_network_delegate(&network_delegate); |
+ TestDelegate d; |
+ { |
+ URLRequest r(test_server_.GetURL("whatever"), |
+ DEFAULT_PRIORITY, |
+ &d, |
+ &default_context_); |
+ |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); |
+ |
+ EXPECT_EQ(2U, r.url_chain().size()); |
+ EXPECT_EQ(0, r.status().error()); |
+ EXPECT_EQ(unsafe_url, r.url()); |
+ } |
+} |
+ |
+// Tests that a redirect to a different unsafe URL is blocked, even after adding |
+// some other URL to the whitelist. |
+TEST_F(URLRequestTestHTTP, UnsafeRedirectToDifferentUnsafeURL) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ GURL unsafe_url("data:text/html,something"); |
+ GURL different_unsafe_url("data:text/html,something-else"); |
+ RedirectOnHeadersReceivedNetworkDelegate network_delegate(unsafe_url); |
+ network_delegate.set_url_marked_safe_for_redirect(different_unsafe_url); |
+ default_context_.set_network_delegate(&network_delegate); |
+ TestDelegate d; |
+ { |
+ URLRequest r(test_server_.GetURL("whatever"), |
+ DEFAULT_PRIORITY, |
+ &d, |
+ &default_context_); |
+ |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status()); |
+ EXPECT_EQ(ERR_UNSAFE_REDIRECT, r.status().error()); |
+ } |
+} |
+ |
+// Tests that a redirect to a safe URL is allowed, regardless of whether an |
+// unsafe URL was whitelisted. |
+TEST_F(URLRequestTestHTTP, UnsafeRedirectToSafeURL) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ GURL safe_url(test_server_.GetURL("echoheader?")); |
+ GURL unrelated_unsafe_url("data:text/html,does-not-match-safe-url"); |
+ RedirectOnHeadersReceivedNetworkDelegate network_delegate(safe_url); |
+ network_delegate.set_url_marked_safe_for_redirect(unrelated_unsafe_url); |
+ default_context_.set_network_delegate(&network_delegate); |
+ TestDelegate d; |
+ { |
+ URLRequest r(test_server_.GetURL("whatever"), |
+ DEFAULT_PRIORITY, |
+ &d, |
+ &default_context_); |
+ |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(2U, r.url_chain().size()); |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); |
+ EXPECT_EQ(0, r.status().error()); |
+ EXPECT_EQ(safe_url, r.url()); |
+ EXPECT_EQ("", d.data_received()); |
+ } |
+} |
mmenke
2014/03/21 18:15:04
This test is indeed probably overkill.
robwu
2014/03/25 00:04:00
Indeed, removed; This case is already covered by t
|
+ |
+// Redirects from an URL with fragment to an unsafe URL without fragment should |
+// be allowed. |
+TEST_F(URLRequestTestHTTP, UnsafeRedirectWithReferenceFragment) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ GURL original_url(test_server_.GetURL("original#fragment")); |
+ GURL unsafe_url("data:,url-marked-safe-and-used-in-redirect"); |
+ GURL expected_url("data:,url-marked-safe-and-used-in-redirect#fragment"); |
mmenke
2014/03/21 18:15:04
Suggest a linebreak after the GURLs in all of thes
robwu
2014/03/25 00:04:00
Done.
|
+ RedirectOnHeadersReceivedNetworkDelegate network_delegate(unsafe_url); |
+ network_delegate.set_url_marked_safe_for_redirect(unsafe_url); |
+ default_context_.set_network_delegate(&network_delegate); |
+ TestDelegate d; |
+ { |
+ URLRequest r(original_url, DEFAULT_PRIORITY, &d, &default_context_); |
+ |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(2U, r.url_chain().size()); |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); |
+ EXPECT_EQ(0, r.status().error()); |
+ EXPECT_EQ(original_url, r.original_url()); |
+ EXPECT_EQ(expected_url, r.url()); |
+ } |
+} |
+ |
+// When a delegate has specified a safe redirect URL, but it does not match the |
+// redirect target, then do not prevent the reference fragment from being added. |
+TEST_F(URLRequestTestHTTP, RedirectWithReferenceFragment) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ GURL original_url(test_server_.GetURL("original#expected-fragment")); |
+ GURL unsafe_url("data:text/html,this-url-does-not-match-redirect-url"); |
+ GURL redirect_url(test_server_.GetURL("target")); |
+ GURL expected_redirect_url(test_server_.GetURL("target#expected-fragment")); |
+ RedirectOnHeadersReceivedNetworkDelegate network_delegate(redirect_url); |
+ network_delegate.set_url_marked_safe_for_redirect(unsafe_url); |
+ default_context_.set_network_delegate(&network_delegate); |
+ TestDelegate d; |
+ { |
+ URLRequest r(original_url, DEFAULT_PRIORITY, &d, &default_context_); |
+ |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(2U, r.url_chain().size()); |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); |
+ EXPECT_EQ(0, r.status().error()); |
+ EXPECT_EQ(original_url, r.original_url()); |
+ EXPECT_EQ(expected_redirect_url, r.url()); |
+ } |
+} |
mmenke
2014/03/21 18:15:04
I don't think we need this test. But I do think w
robwu
2014/03/25 00:04:00
Currently, the test is indeed redundant (it merely
|
+ |
// In this unit test, we're using the HTTPTestServer as a proxy server and |
// issuing a CONNECT request with the magic host name "www.redirect.com". |
// The HTTPTestServer will return a 302 response, which we should not |
@@ -3007,6 +3220,83 @@ TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequestPost) { |
EXPECT_EQ(1, network_delegate.destroyed_requests()); |
} |
+// Tests that the network delegate can block and redirect a request to a new |
+// URL during OnHeadersReceived. |
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequestOnHeadersReceived) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ TestDelegate d; |
+ BlockingNetworkDelegate network_delegate( |
+ BlockingNetworkDelegate::AUTO_CALLBACK); |
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_HEADERS_RECEIVED); |
+ GURL redirect_url(test_server_.GetURL("simple.html")); |
+ network_delegate.set_redirect_url_on_headers_received(redirect_url); |
+ |
+ TestURLRequestContextWithProxy context( |
+ test_server_.host_port_pair().ToString(), &network_delegate); |
+ |
+ { |
+ GURL original_url(test_server_.GetURL("empty.html")); |
+ URLRequest r(original_url, DEFAULT_PRIORITY, &d, &context); |
+ |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); |
+ EXPECT_EQ(0, r.status().error()); |
+ EXPECT_EQ(redirect_url, r.url()); |
+ EXPECT_EQ(original_url, r.original_url()); |
+ EXPECT_EQ(2U, r.url_chain().size()); |
+ EXPECT_EQ(2, network_delegate.created_requests()); |
+ EXPECT_EQ(0, network_delegate.destroyed_requests()); |
+ } |
+ EXPECT_EQ(1, network_delegate.destroyed_requests()); |
+} |
+ |
+// Tests that redirects caused by the network delegate during OnHeadersReceived |
+// preserve POST data. |
mmenke
2014/03/21 18:15:04
Both these test are probably overkill as well. Mo
robwu
2014/03/25 00:04:00
You're right. What I actually wanted to do was to
|
+TEST_F(URLRequestTestHTTP, |
+ NetworkDelegateRedirectRequestOnHeadersReceivedPost) { |
+ ASSERT_TRUE(test_server_.Start()); |
+ |
+ const char kData[] = "hello world"; |
+ |
+ TestDelegate d; |
+ BlockingNetworkDelegate network_delegate( |
+ BlockingNetworkDelegate::AUTO_CALLBACK); |
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_HEADERS_RECEIVED); |
+ GURL redirect_url(test_server_.GetURL("echo")); |
+ network_delegate.set_redirect_url_on_headers_received(redirect_url); |
+ |
+ TestURLRequestContext context(true); |
+ context.set_network_delegate(&network_delegate); |
+ context.Init(); |
+ |
+ { |
+ GURL original_url(test_server_.GetURL("empty.html")); |
+ URLRequest r(original_url, DEFAULT_PRIORITY, &d, &context); |
+ r.set_method("POST"); |
+ r.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData))); |
+ HttpRequestHeaders headers; |
+ headers.SetHeader(HttpRequestHeaders::kContentLength, |
+ base::UintToString(arraysize(kData) - 1)); |
+ r.SetExtraRequestHeaders(headers); |
+ r.Start(); |
+ base::RunLoop().Run(); |
+ |
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status()); |
+ EXPECT_EQ(0, r.status().error()); |
+ EXPECT_EQ(redirect_url, r.url()); |
+ EXPECT_EQ(original_url, r.original_url()); |
+ EXPECT_EQ(2U, r.url_chain().size()); |
+ EXPECT_EQ(2, network_delegate.created_requests()); |
+ EXPECT_EQ(0, network_delegate.destroyed_requests()); |
+ EXPECT_EQ("POST", r.method()); |
+ EXPECT_EQ(kData, d.data_received()); |
+ } |
+ EXPECT_EQ(1, network_delegate.destroyed_requests()); |
+} |
+ |
// Tests that the network delegate can synchronously complete OnAuthRequired |
// by taking no action. This indicates that the NetworkDelegate does not want to |
// handle the challenge, and is passing the buck along to the |
@@ -3919,10 +4209,12 @@ class AsyncLoggingNetworkDelegate : public TestNetworkDelegate { |
URLRequest* request, |
const CompletionCallback& callback, |
const HttpResponseHeaders* original_response_headers, |
- scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE { |
+ scoped_refptr<HttpResponseHeaders>* override_response_headers, |
+ GURL* allowed_unsafe_redirect_url) OVERRIDE { |
TestNetworkDelegate::OnHeadersReceived(request, callback, |
original_response_headers, |
- override_response_headers); |
+ override_response_headers, |
+ allowed_unsafe_redirect_url); |
return RunCallbackAsynchronously(request, callback); |
} |