// Copyright 2014 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/command_line.h" #include "base/macros.h" #include "base/memory/ref_counted.h" #include "base/memory/scoped_ptr.h" #include "base/run_loop.h" #include "content/browser/frame_host/navigation_request_info.h" #include "content/browser/loader/navigation_url_loader_delegate.h" #include "content/browser/loader/navigation_url_loader_impl.h" #include "content/browser/loader/resource_dispatcher_host_impl.h" #include "content/browser/streams/stream.h" #include "content/browser/streams/stream_context.h" #include "content/browser/streams/stream_registry.h" #include "content/browser/streams/stream_url_request_job.h" #include "content/common/navigation_params.h" #include "content/public/browser/browser_context.h" #include "content/public/browser/resource_context.h" #include "content/public/browser/resource_dispatcher_host_delegate.h" #include "content/public/browser/stream_handle.h" #include "content/public/common/content_switches.h" #include "content/public/common/resource_response.h" #include "content/public/test/test_browser_context.h" #include "content/public/test/test_browser_thread_bundle.h" #include "net/base/net_errors.h" #include "net/http/http_response_headers.h" #include "net/url_request/redirect_info.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_job_factory_impl.h" #include "net/url_request/url_request_test_job.h" #include "net/url_request/url_request_test_util.h" #include "testing/gtest/include/gtest/gtest.h" namespace content { namespace { class StreamProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: StreamProtocolHandler(StreamRegistry* registry) : registry_(registry) {} // net::URLRequestJobFactory::ProtocolHandler implementation. net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const override { scoped_refptr stream = registry_->GetStream(request->url()); if (stream.get()) return new StreamURLRequestJob(request, network_delegate, stream); return nullptr; } private: StreamRegistry* registry_; DISALLOW_COPY_AND_ASSIGN(StreamProtocolHandler); }; class TestNavigationURLLoaderDelegate : public NavigationURLLoaderDelegate { public: TestNavigationURLLoaderDelegate() : net_error_(0) { } const net::RedirectInfo& redirect_info() const { return redirect_info_; } ResourceResponse* redirect_response() const { return redirect_response_.get(); } ResourceResponse* response() const { return response_.get(); } StreamHandle* body() const { return body_.get(); } int net_error() const { return net_error_; } void WaitForRequestRedirected() { request_redirected_.reset(new base::RunLoop); request_redirected_->Run(); request_redirected_.reset(); } void WaitForResponseStarted() { response_started_.reset(new base::RunLoop); response_started_->Run(); response_started_.reset(); } void WaitForRequestFailed() { request_failed_.reset(new base::RunLoop); request_failed_->Run(); request_failed_.reset(); } void ReleaseBody() { body_.reset(); } // NavigationURLLoaderDelegate implementation. void OnRequestRedirected( const net::RedirectInfo& redirect_info, const scoped_refptr& response) override { redirect_info_ = redirect_info; redirect_response_ = response; ASSERT_TRUE(request_redirected_); request_redirected_->Quit(); } void OnResponseStarted(const scoped_refptr& response, scoped_ptr body) override { response_ = response; body_ = body.Pass(); ASSERT_TRUE(response_started_); response_started_->Quit(); } void OnRequestFailed(int net_error) override { net_error_ = net_error; ASSERT_TRUE(request_failed_); request_failed_->Quit(); } private: net::RedirectInfo redirect_info_; scoped_refptr redirect_response_; scoped_refptr response_; scoped_ptr body_; int net_error_; scoped_ptr request_redirected_; scoped_ptr response_started_; scoped_ptr request_failed_; }; class RequestBlockingResourceDispatcherHostDelegate : public ResourceDispatcherHostDelegate { public: // ResourceDispatcherHostDelegate implementation: bool ShouldBeginRequest(const std::string& method, const GURL& url, ResourceType resource_type, ResourceContext* resource_context) override { return false; } }; } // namespace class NavigationURLLoaderTest : public testing::Test { public: NavigationURLLoaderTest() : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), browser_context_(new TestBrowserContext) { BrowserContext::EnsureResourceContextInitialized(browser_context_.get()); base::RunLoop().RunUntilIdle(); net::URLRequestContext* request_context = browser_context_->GetResourceContext()->GetRequestContext(); // Attach URLRequestTestJob and make streams work. job_factory_.SetProtocolHandler( "test", net::URLRequestTestJob::CreateProtocolHandler()); job_factory_.SetProtocolHandler( "blob", new StreamProtocolHandler( StreamContext::GetFor(browser_context_.get())->registry())); request_context->set_job_factory(&job_factory_); // NavigationURLLoader is only used for browser-side navigations. base::CommandLine::ForCurrentProcess()->AppendSwitch( switches::kEnableBrowserSideNavigation); } scoped_ptr MakeTestLoader( const GURL& url, NavigationURLLoaderDelegate* delegate) { FrameHostMsg_BeginNavigation_Params begin_params; CommonNavigationParams common_params; begin_params.method = "GET"; common_params.url = url; scoped_ptr request_info( new NavigationRequestInfo(begin_params)); request_info->first_party_for_cookies = url; request_info->is_main_frame = true; return NavigationURLLoader::Create( browser_context_.get(), 0, common_params, request_info.Pass(), nullptr, delegate); } // Helper function for fetching the body of a URL to a string. std::string FetchURL(const GURL& url) { net::TestDelegate delegate; net::URLRequestContext* request_context = browser_context_->GetResourceContext()->GetRequestContext(); scoped_ptr request(request_context->CreateRequest( url, net::DEFAULT_PRIORITY, &delegate, nullptr)); request->Start(); base::RunLoop().Run(); EXPECT_TRUE(request->status().is_success()); EXPECT_EQ(200, request->response_headers()->response_code()); return delegate.data_received(); } protected: TestBrowserThreadBundle thread_bundle_; net::URLRequestJobFactoryImpl job_factory_; scoped_ptr browser_context_; ResourceDispatcherHostImpl host_; }; // Tests that a basic request works. TEST_F(NavigationURLLoaderTest, Basic) { TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_1(), &delegate); // Wait for the response to come back. delegate.WaitForResponseStarted(); // Check the response is correct. EXPECT_EQ("text/html", delegate.response()->head.mime_type); EXPECT_EQ(200, delegate.response()->head.headers->response_code()); // Check the body is correct. EXPECT_EQ(net::URLRequestTestJob::test_data_1(), FetchURL(delegate.body()->GetURL())); } // Tests that request failures are propagated correctly. TEST_F(NavigationURLLoaderTest, RequestFailed) { TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(GURL("bogus:bogus"), &delegate); // Wait for the request to fail as expected. delegate.WaitForRequestFailed(); EXPECT_EQ(net::ERR_UNKNOWN_URL_SCHEME, delegate.net_error()); } // Test that redirects are sent to the delegate. TEST_F(NavigationURLLoaderTest, RequestRedirected) { // Fake a top-level request. Choose a URL which redirects so the request can // be paused before the response comes in. TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate); // Wait for the request to redirect. delegate.WaitForRequestRedirected(); EXPECT_EQ(net::URLRequestTestJob::test_url_2(), delegate.redirect_info().new_url); EXPECT_EQ("GET", delegate.redirect_info().new_method); EXPECT_EQ(net::URLRequestTestJob::test_url_2(), delegate.redirect_info().new_first_party_for_cookies); EXPECT_EQ(302, delegate.redirect_response()->head.headers->response_code()); // Wait for the response to complete. loader->FollowRedirect(); delegate.WaitForResponseStarted(); // Check the response is correct. EXPECT_EQ("text/html", delegate.response()->head.mime_type); EXPECT_EQ(200, delegate.response()->head.headers->response_code()); // Release the body and check it is correct. EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); EXPECT_EQ(net::URLRequestTestJob::test_data_2(), FetchURL(delegate.body()->GetURL())); } // Tests that the destroying the loader cancels the request. TEST_F(NavigationURLLoaderTest, CancelOnDestruct) { // Fake a top-level request. Choose a URL which redirects so the request can // be paused before the response comes in. TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate); // Wait for the request to redirect. delegate.WaitForRequestRedirected(); // Destroy the loader and verify that URLRequestTestJob no longer has anything // paused. loader.reset(); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage()); } // Test that the delegate is not called if OnResponseStarted and destroying the // loader race. TEST_F(NavigationURLLoaderTest, CancelResponseRace) { TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate); // Wait for the request to redirect. delegate.WaitForRequestRedirected(); // In the same event loop iteration, follow the redirect (allowing the // response to go through) and destroy the loader. loader->FollowRedirect(); loader.reset(); // Verify the URLRequestTestJob no longer has anything paused and that no // response body was received. base::RunLoop().RunUntilIdle(); EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage()); EXPECT_FALSE(delegate.body()); } // Tests that the loader may be canceled by context. TEST_F(NavigationURLLoaderTest, CancelByContext) { TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_redirect_to_url_2(), &delegate); // Wait for the request to redirect. delegate.WaitForRequestRedirected(); // Cancel all requests. host_.CancelRequestsForContext(browser_context_->GetResourceContext()); // Wait for the request to now be aborted. delegate.WaitForRequestFailed(); EXPECT_EQ(net::ERR_ABORTED, delegate.net_error()); } // Tests that, if the request is blocked by the ResourceDispatcherHostDelegate, // the caller is informed appropriately. TEST_F(NavigationURLLoaderTest, RequestBlocked) { RequestBlockingResourceDispatcherHostDelegate rdh_delegate; host_.SetDelegate(&rdh_delegate); TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_1(), &delegate); // Wait for the request to fail as expected. delegate.WaitForRequestFailed(); EXPECT_EQ(net::ERR_ABORTED, delegate.net_error()); host_.SetDelegate(nullptr); } // Tests that ownership leaves the loader once the response is received. TEST_F(NavigationURLLoaderTest, LoaderDetached) { // Fake a top-level request to a URL whose body does not load immediately. TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_2(), &delegate); // Wait for the response to come back. delegate.WaitForResponseStarted(); // Check the response is correct. EXPECT_EQ("text/html", delegate.response()->head.mime_type); EXPECT_EQ(200, delegate.response()->head.headers->response_code()); // Destroy the loader. loader.reset(); base::RunLoop().RunUntilIdle(); // Check the body can still be fetched through the StreamHandle. EXPECT_TRUE(net::URLRequestTestJob::ProcessOnePendingMessage()); EXPECT_EQ(net::URLRequestTestJob::test_data_2(), FetchURL(delegate.body()->GetURL())); } // Tests that the request is owned by the body StreamHandle. TEST_F(NavigationURLLoaderTest, OwnedByHandle) { // Fake a top-level request to a URL whose body does not load immediately. TestNavigationURLLoaderDelegate delegate; scoped_ptr loader = MakeTestLoader(net::URLRequestTestJob::test_url_2(), &delegate); // Wait for the response to come back. delegate.WaitForResponseStarted(); // Release the body. delegate.ReleaseBody(); base::RunLoop().RunUntilIdle(); // Verify that URLRequestTestJob no longer has anything paused. EXPECT_FALSE(net::URLRequestTestJob::ProcessOnePendingMessage()); } } // namespace content