|  | // Copyright (c) 2013 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 "content/browser/loader/resource_loader.h" | 
|  |  | 
|  | #include <stddef.h> | 
|  | #include <stdint.h> | 
|  |  | 
|  | #include <memory> | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/files/file.h" | 
|  | #include "base/files/file_util.h" | 
|  | #include "base/location.h" | 
|  | #include "base/macros.h" | 
|  | #include "base/memory/ptr_util.h" | 
|  | #include "base/memory/weak_ptr.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/single_thread_task_runner.h" | 
|  | #include "base/threading/thread_task_runner_handle.h" | 
|  | #include "content/browser/browser_thread_impl.h" | 
|  | #include "content/browser/loader/redirect_to_file_resource_handler.h" | 
|  | #include "content/browser/loader/resource_loader_delegate.h" | 
|  | #include "content/public/browser/client_certificate_delegate.h" | 
|  | #include "content/public/browser/resource_request_info.h" | 
|  | #include "content/public/common/content_paths.h" | 
|  | #include "content/public/common/resource_response.h" | 
|  | #include "content/public/common/resource_type.h" | 
|  | #include "content/public/test/mock_resource_context.h" | 
|  | #include "content/public/test/test_browser_context.h" | 
|  | #include "content/public/test/test_browser_thread_bundle.h" | 
|  | #include "content/public/test/test_renderer_host.h" | 
|  | #include "content/test/test_content_browser_client.h" | 
|  | #include "content/test/test_web_contents.h" | 
|  | #include "ipc/ipc_message.h" | 
|  | #include "net/base/chunked_upload_data_stream.h" | 
|  | #include "net/base/io_buffer.h" | 
|  | #include "net/base/mock_file_stream.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/base/request_priority.h" | 
|  | #include "net/base/upload_bytes_element_reader.h" | 
|  | #include "net/cert/x509_certificate.h" | 
|  | #include "net/nqe/effective_connection_type.h" | 
|  | #include "net/nqe/network_quality_estimator.h" | 
|  | #include "net/ssl/client_cert_store.h" | 
|  | #include "net/ssl/ssl_cert_request_info.h" | 
|  | #include "net/ssl/ssl_private_key.h" | 
|  | #include "net/test/cert_test_util.h" | 
|  | #include "net/test/embedded_test_server/embedded_test_server.h" | 
|  | #include "net/test/test_data_directory.h" | 
|  | #include "net/url_request/url_request.h" | 
|  | #include "net/url_request/url_request_filter.h" | 
|  | #include "net/url_request/url_request_interceptor.h" | 
|  | #include "net/url_request/url_request_job_factory.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 "storage/browser/blob/shareable_file_reference.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  |  | 
|  | using storage::ShareableFileReference; | 
|  |  | 
|  | namespace content { | 
|  | namespace { | 
|  |  | 
|  | // Stub client certificate store that returns a preset list of certificates for | 
|  | // each request and records the arguments of the most recent request for later | 
|  | // inspection. | 
|  | class ClientCertStoreStub : public net::ClientCertStore { | 
|  | public: | 
|  | // Creates a new ClientCertStoreStub that returns |response| on query. It | 
|  | // saves the number of requests and most recently certificate authorities list | 
|  | // in |requested_authorities| and |request_count|, respectively. The caller is | 
|  | // responsible for ensuring those pointers outlive the ClientCertStoreStub. | 
|  | // | 
|  | // TODO(ppi): Make the stub independent from the internal representation of | 
|  | // SSLCertRequestInfo. For now it seems that we can neither save the | 
|  | // scoped_refptr<> (since it is never passed to us) nor copy the entire | 
|  | // CertificateRequestInfo (since there is no copy constructor). | 
|  | ClientCertStoreStub(const net::CertificateList& response, | 
|  | int* request_count, | 
|  | std::vector<std::string>* requested_authorities) | 
|  | : response_(response), | 
|  | requested_authorities_(requested_authorities), | 
|  | request_count_(request_count) { | 
|  | requested_authorities_->clear(); | 
|  | *request_count_ = 0; | 
|  | } | 
|  |  | 
|  | ~ClientCertStoreStub() override {} | 
|  |  | 
|  | // net::ClientCertStore: | 
|  | void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info, | 
|  | net::CertificateList* selected_certs, | 
|  | const base::Closure& callback) override { | 
|  | *requested_authorities_ = cert_request_info.cert_authorities; | 
|  | ++(*request_count_); | 
|  |  | 
|  | *selected_certs = response_; | 
|  | callback.Run(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | const net::CertificateList response_; | 
|  | std::vector<std::string>* requested_authorities_; | 
|  | int* request_count_; | 
|  | }; | 
|  |  | 
|  | // Client certificate store which destroys its resource loader before the | 
|  | // asynchronous GetClientCerts callback is called. | 
|  | class LoaderDestroyingCertStore : public net::ClientCertStore { | 
|  | public: | 
|  | // Creates a client certificate store which, when looked up, posts a task to | 
|  | // reset |loader| and then call the callback. The caller is responsible for | 
|  | // ensuring the pointers remain valid until the process is complete. | 
|  | LoaderDestroyingCertStore(std::unique_ptr<ResourceLoader>* loader, | 
|  | const base::Closure& on_loader_deleted_callback) | 
|  | : loader_(loader), | 
|  | on_loader_deleted_callback_(on_loader_deleted_callback) {} | 
|  |  | 
|  | // net::ClientCertStore: | 
|  | void GetClientCerts(const net::SSLCertRequestInfo& cert_request_info, | 
|  | net::CertificateList* selected_certs, | 
|  | const base::Closure& cert_selected_callback) override { | 
|  | // Don't destroy |loader_| while it's on the stack. | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, base::Bind(&LoaderDestroyingCertStore::DoCallback, | 
|  | base::Unretained(loader_), | 
|  | cert_selected_callback, | 
|  | on_loader_deleted_callback_)); | 
|  | } | 
|  |  | 
|  | private: | 
|  | // This needs to be static because |loader| owns the | 
|  | // LoaderDestroyingCertStore (ClientCertStores are actually handles, and not | 
|  | // global cert stores). | 
|  | static void DoCallback(std::unique_ptr<ResourceLoader>* loader, | 
|  | const base::Closure& cert_selected_callback, | 
|  | const base::Closure& on_loader_deleted_callback) { | 
|  | loader->reset(); | 
|  | cert_selected_callback.Run(); | 
|  | on_loader_deleted_callback.Run(); | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ResourceLoader>* loader_; | 
|  | base::Closure on_loader_deleted_callback_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(LoaderDestroyingCertStore); | 
|  | }; | 
|  |  | 
|  | // A mock URLRequestJob which simulates an SSL client auth request. | 
|  | class MockClientCertURLRequestJob : public net::URLRequestTestJob { | 
|  | public: | 
|  | MockClientCertURLRequestJob(net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate) | 
|  | : net::URLRequestTestJob(request, network_delegate), | 
|  | weak_factory_(this) {} | 
|  |  | 
|  | static std::vector<std::string> test_authorities() { | 
|  | return std::vector<std::string>(1, "dummy"); | 
|  | } | 
|  |  | 
|  | // net::URLRequestTestJob: | 
|  | void Start() override { | 
|  | scoped_refptr<net::SSLCertRequestInfo> cert_request_info( | 
|  | new net::SSLCertRequestInfo); | 
|  | cert_request_info->cert_authorities = test_authorities(); | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind(&MockClientCertURLRequestJob::NotifyCertificateRequested, | 
|  | weak_factory_.GetWeakPtr(), | 
|  | base::RetainedRef(cert_request_info))); | 
|  | } | 
|  |  | 
|  | void ContinueWithCertificate(net::X509Certificate* cert, | 
|  | net::SSLPrivateKey* private_key) override { | 
|  | net::URLRequestTestJob::Start(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~MockClientCertURLRequestJob() override {} | 
|  |  | 
|  | base::WeakPtrFactory<MockClientCertURLRequestJob> weak_factory_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(MockClientCertURLRequestJob); | 
|  | }; | 
|  |  | 
|  | class MockClientCertJobProtocolHandler | 
|  | : public net::URLRequestJobFactory::ProtocolHandler { | 
|  | public: | 
|  | // URLRequestJobFactory::ProtocolHandler implementation: | 
|  | net::URLRequestJob* MaybeCreateJob( | 
|  | net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate) const override { | 
|  | return new MockClientCertURLRequestJob(request, network_delegate); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Set up dummy values to use in test HTTPS requests. | 
|  |  | 
|  | scoped_refptr<net::X509Certificate> GetTestCert() { | 
|  | return net::ImportCertFromFile(net::GetTestCertsDirectory(), | 
|  | "test_mail_google_com.pem"); | 
|  | } | 
|  |  | 
|  | const net::CertStatus kTestCertError = net::CERT_STATUS_DATE_INVALID; | 
|  | const int kTestSecurityBits = 256; | 
|  | // SSL3 TLS_DHE_RSA_WITH_AES_256_CBC_SHA | 
|  | const int kTestConnectionStatus = 0x300039; | 
|  |  | 
|  | // A mock URLRequestJob which simulates an HTTPS request. | 
|  | class MockHTTPSURLRequestJob : public net::URLRequestTestJob { | 
|  | public: | 
|  | MockHTTPSURLRequestJob(net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate, | 
|  | const std::string& response_headers, | 
|  | const std::string& response_data, | 
|  | bool auto_advance) | 
|  | : net::URLRequestTestJob(request, | 
|  | network_delegate, | 
|  | response_headers, | 
|  | response_data, | 
|  | auto_advance) {} | 
|  |  | 
|  | // net::URLRequestTestJob: | 
|  | void GetResponseInfo(net::HttpResponseInfo* info) override { | 
|  | // Get the original response info, but override the SSL info. | 
|  | net::URLRequestJob::GetResponseInfo(info); | 
|  | info->ssl_info.cert = GetTestCert(); | 
|  | info->ssl_info.cert_status = kTestCertError; | 
|  | info->ssl_info.security_bits = kTestSecurityBits; | 
|  | info->ssl_info.connection_status = kTestConnectionStatus; | 
|  | } | 
|  |  | 
|  | private: | 
|  | ~MockHTTPSURLRequestJob() override {} | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(MockHTTPSURLRequestJob); | 
|  | }; | 
|  |  | 
|  | const char kRedirectHeaders[] = | 
|  | "HTTP/1.1 302 Found\n" | 
|  | "Location: https://example.test\n" | 
|  | "\n"; | 
|  |  | 
|  | class MockHTTPSJobURLRequestInterceptor : public net::URLRequestInterceptor { | 
|  | public: | 
|  | MockHTTPSJobURLRequestInterceptor(bool redirect) : redirect_(redirect) {} | 
|  | ~MockHTTPSJobURLRequestInterceptor() override {} | 
|  |  | 
|  | // net::URLRequestInterceptor: | 
|  | net::URLRequestJob* MaybeInterceptRequest( | 
|  | net::URLRequest* request, | 
|  | net::NetworkDelegate* network_delegate) const override { | 
|  | std::string headers = | 
|  | redirect_ ? std::string(kRedirectHeaders, arraysize(kRedirectHeaders)) | 
|  | : net::URLRequestTestJob::test_headers(); | 
|  | return new MockHTTPSURLRequestJob(request, network_delegate, headers, | 
|  | "dummy response", true); | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool redirect_; | 
|  | }; | 
|  |  | 
|  | // Arbitrary read buffer size. | 
|  | const int kReadBufSize = 1024; | 
|  |  | 
|  | // Dummy implementation of ResourceHandler, instance of which is needed to | 
|  | // initialize ResourceLoader. | 
|  | class ResourceHandlerStub : public ResourceHandler { | 
|  | public: | 
|  | explicit ResourceHandlerStub(net::URLRequest* request) | 
|  | : ResourceHandler(request), | 
|  | read_buffer_(new net::IOBuffer(kReadBufSize)), | 
|  | defer_request_on_will_start_(false), | 
|  | expect_reads_(true), | 
|  | cancel_on_read_completed_(false), | 
|  | defer_eof_(false), | 
|  | received_on_will_read_(false), | 
|  | received_eof_(false), | 
|  | received_response_completed_(false), | 
|  | received_request_redirected_(false), | 
|  | total_bytes_downloaded_(0), | 
|  | observed_effective_connection_type_( | 
|  | net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {} | 
|  |  | 
|  | // If true, defers the resource load in OnWillStart. | 
|  | void set_defer_request_on_will_start(bool defer_request_on_will_start) { | 
|  | defer_request_on_will_start_ = defer_request_on_will_start; | 
|  | } | 
|  |  | 
|  | // If true, expect OnWillRead / OnReadCompleted pairs for handling | 
|  | // data. Otherwise, expect OnDataDownloaded. | 
|  | void set_expect_reads(bool expect_reads) { expect_reads_ = expect_reads; } | 
|  |  | 
|  | // If true, cancel the request in OnReadCompleted by returning false. | 
|  | void set_cancel_on_read_completed(bool cancel_on_read_completed) { | 
|  | cancel_on_read_completed_ = cancel_on_read_completed; | 
|  | } | 
|  |  | 
|  | // If true, cancel the request in OnReadCompleted by returning false. | 
|  | void set_defer_eof(bool defer_eof) { defer_eof_ = defer_eof; } | 
|  |  | 
|  | const GURL& start_url() const { return start_url_; } | 
|  | ResourceResponse* response() const { return response_.get(); } | 
|  | ResourceResponse* redirect_response() const { | 
|  | return redirect_response_.get(); | 
|  | } | 
|  | bool received_response_completed() const { | 
|  | return received_response_completed_; | 
|  | } | 
|  | bool received_request_redirected() const { | 
|  | return received_request_redirected_; | 
|  | } | 
|  | const net::URLRequestStatus& status() const { return status_; } | 
|  | int total_bytes_downloaded() const { return total_bytes_downloaded_; } | 
|  |  | 
|  | net::EffectiveConnectionType observed_effective_connection_type() const { | 
|  | return observed_effective_connection_type_; | 
|  | } | 
|  |  | 
|  | void Resume() { | 
|  | controller()->Resume(); | 
|  | } | 
|  |  | 
|  | bool OnRequestRedirected(const net::RedirectInfo& redirect_info, | 
|  | ResourceResponse* response, | 
|  | bool* defer) override { | 
|  | redirect_response_ = response; | 
|  | received_request_redirected_ = true; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OnResponseStarted(ResourceResponse* response, bool* defer) override { | 
|  | EXPECT_FALSE(response_.get()); | 
|  | response_ = response; | 
|  | observed_effective_connection_type_ = | 
|  | response->head.effective_connection_type; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OnWillStart(const GURL& url, bool* defer) override { | 
|  | EXPECT_TRUE(start_url_.is_empty()); | 
|  | start_url_ = url; | 
|  | if (defer_request_on_will_start_) { | 
|  | *defer = true; | 
|  | deferred_run_loop_.Quit(); | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OnWillRead(scoped_refptr<net::IOBuffer>* buf, | 
|  | int* buf_size, | 
|  | int min_size) override { | 
|  | EXPECT_TRUE(expect_reads_); | 
|  | EXPECT_FALSE(received_on_will_read_); | 
|  | EXPECT_FALSE(received_eof_); | 
|  | EXPECT_FALSE(received_response_completed_); | 
|  |  | 
|  | *buf = read_buffer_; | 
|  | *buf_size = kReadBufSize; | 
|  | received_on_will_read_ = true; | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool OnReadCompleted(int bytes_read, bool* defer) override { | 
|  | EXPECT_TRUE(received_on_will_read_); | 
|  | EXPECT_TRUE(expect_reads_); | 
|  | EXPECT_FALSE(received_response_completed_); | 
|  |  | 
|  | if (bytes_read == 0) { | 
|  | received_eof_ = true; | 
|  | if (defer_eof_) { | 
|  | defer_eof_ = false; | 
|  | *defer = true; | 
|  | deferred_run_loop_.Quit(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // Need another OnWillRead() call before seeing an OnReadCompleted(). | 
|  | received_on_will_read_ = false; | 
|  |  | 
|  | return !cancel_on_read_completed_; | 
|  | } | 
|  |  | 
|  | void OnResponseCompleted(const net::URLRequestStatus& status, | 
|  | bool* defer) override { | 
|  | EXPECT_FALSE(received_response_completed_); | 
|  | if (status.is_success() && expect_reads_) | 
|  | EXPECT_TRUE(received_eof_); | 
|  |  | 
|  | received_response_completed_ = true; | 
|  | status_ = status; | 
|  | response_completed_run_loop_.Quit(); | 
|  | } | 
|  |  | 
|  | void OnDataDownloaded(int bytes_downloaded) override { | 
|  | EXPECT_FALSE(expect_reads_); | 
|  | total_bytes_downloaded_ += bytes_downloaded; | 
|  | } | 
|  |  | 
|  | // Waits for the the first deferred step to run, if there is one. | 
|  | void WaitForDeferredStep() { | 
|  | DCHECK(defer_request_on_will_start_ || defer_eof_); | 
|  | deferred_run_loop_.Run(); | 
|  | } | 
|  |  | 
|  | // Waits until the response has completed. | 
|  | void WaitForResponseComplete() { | 
|  | response_completed_run_loop_.Run(); | 
|  | EXPECT_TRUE(received_response_completed_); | 
|  | } | 
|  |  | 
|  | private: | 
|  | scoped_refptr<net::IOBuffer> read_buffer_; | 
|  |  | 
|  | bool defer_request_on_will_start_; | 
|  | bool expect_reads_; | 
|  | bool cancel_on_read_completed_; | 
|  | bool defer_eof_; | 
|  |  | 
|  | GURL start_url_; | 
|  | scoped_refptr<ResourceResponse> response_; | 
|  | scoped_refptr<ResourceResponse> redirect_response_; | 
|  | bool received_on_will_read_; | 
|  | bool received_eof_; | 
|  | bool received_response_completed_; | 
|  | bool received_request_redirected_; | 
|  | net::URLRequestStatus status_; | 
|  | int total_bytes_downloaded_; | 
|  | base::RunLoop deferred_run_loop_; | 
|  | base::RunLoop response_completed_run_loop_; | 
|  | std::unique_ptr<base::RunLoop> wait_for_progress_run_loop_; | 
|  | net::EffectiveConnectionType observed_effective_connection_type_; | 
|  | }; | 
|  |  | 
|  | // Test browser client that captures calls to SelectClientCertificates and | 
|  | // records the arguments of the most recent call for later inspection. | 
|  | class SelectCertificateBrowserClient : public TestContentBrowserClient { | 
|  | public: | 
|  | SelectCertificateBrowserClient() : call_count_(0) {} | 
|  |  | 
|  | // Waits until the first call to SelectClientCertificate. | 
|  | void WaitForSelectCertificate() { | 
|  | select_certificate_run_loop_.Run(); | 
|  | // Process any pending messages - just so tests can check if | 
|  | // SelectClientCertificate was called more than once. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | void SelectClientCertificate( | 
|  | WebContents* web_contents, | 
|  | net::SSLCertRequestInfo* cert_request_info, | 
|  | std::unique_ptr<ClientCertificateDelegate> delegate) override { | 
|  | EXPECT_FALSE(delegate_.get()); | 
|  |  | 
|  | ++call_count_; | 
|  | passed_certs_ = cert_request_info->client_certs; | 
|  | delegate_ = std::move(delegate); | 
|  | select_certificate_run_loop_.Quit(); | 
|  | } | 
|  |  | 
|  | int call_count() { return call_count_; } | 
|  | net::CertificateList passed_certs() { return passed_certs_; } | 
|  |  | 
|  | void ContinueWithCertificate(net::X509Certificate* cert) { | 
|  | delegate_->ContinueWithCertificate(cert); | 
|  | delegate_.reset(); | 
|  | } | 
|  |  | 
|  | void CancelCertificateSelection() { delegate_.reset(); } | 
|  |  | 
|  | private: | 
|  | net::CertificateList passed_certs_; | 
|  | int call_count_; | 
|  | std::unique_ptr<ClientCertificateDelegate> delegate_; | 
|  |  | 
|  | base::RunLoop select_certificate_run_loop_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(SelectCertificateBrowserClient); | 
|  | }; | 
|  |  | 
|  | // Wraps a ChunkedUploadDataStream to behave as non-chunked to enable upload | 
|  | // progress reporting. | 
|  | class NonChunkedUploadDataStream : public net::UploadDataStream { | 
|  | public: | 
|  | explicit NonChunkedUploadDataStream(uint64_t size) | 
|  | : net::UploadDataStream(false, 0), stream_(0), size_(size) {} | 
|  |  | 
|  | void AppendData(const char* data) { | 
|  | stream_.AppendData(data, strlen(data), false); | 
|  | } | 
|  |  | 
|  | private: | 
|  | int InitInternal(const net::BoundNetLog& net_log) override { | 
|  | SetSize(size_); | 
|  | stream_.Init(base::Bind(&NonChunkedUploadDataStream::OnInitCompleted, | 
|  | base::Unretained(this)), | 
|  | net_log); | 
|  | return net::OK; | 
|  | } | 
|  |  | 
|  | int ReadInternal(net::IOBuffer* buf, int buf_len) override { | 
|  | return stream_.Read(buf, buf_len, | 
|  | base::Bind(&NonChunkedUploadDataStream::OnReadCompleted, | 
|  | base::Unretained(this))); | 
|  | } | 
|  |  | 
|  | void ResetInternal() override { stream_.Reset(); } | 
|  |  | 
|  | net::ChunkedUploadDataStream stream_; | 
|  | uint64_t size_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(NonChunkedUploadDataStream); | 
|  | }; | 
|  |  | 
|  | // Fails to create a temporary file with the given error. | 
|  | void CreateTemporaryError( | 
|  | base::File::Error error, | 
|  | const CreateTemporaryFileStreamCallback& callback) { | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind(callback, error, | 
|  | base::Passed(std::unique_ptr<net::FileStream>()), nullptr)); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class TestNetworkQualityEstimator : public net::NetworkQualityEstimator { | 
|  | public: | 
|  | TestNetworkQualityEstimator() | 
|  | : net::NetworkQualityEstimator(nullptr, | 
|  | std::map<std::string, std::string>()), | 
|  | type_(net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {} | 
|  | ~TestNetworkQualityEstimator() override {} | 
|  |  | 
|  | net::EffectiveConnectionType GetEffectiveConnectionType() const override { | 
|  | return type_; | 
|  | } | 
|  |  | 
|  | void set_effective_connection_type(net::EffectiveConnectionType type) { | 
|  | type_ = type; | 
|  | } | 
|  |  | 
|  | private: | 
|  | net::EffectiveConnectionType type_; | 
|  |  | 
|  | DISALLOW_COPY_AND_ASSIGN(TestNetworkQualityEstimator); | 
|  | }; | 
|  |  | 
|  | class ResourceLoaderTest : public testing::Test, | 
|  | public ResourceLoaderDelegate { | 
|  | protected: | 
|  | ResourceLoaderTest() | 
|  | : thread_bundle_(content::TestBrowserThreadBundle::IO_MAINLOOP), | 
|  | test_url_request_context_(true), | 
|  | resource_context_(&test_url_request_context_), | 
|  | raw_ptr_resource_handler_(NULL), | 
|  | raw_ptr_to_request_(NULL) { | 
|  | test_url_request_context_.set_job_factory(&job_factory_); | 
|  | test_url_request_context_.set_network_quality_estimator( | 
|  | &network_quality_estimator_); | 
|  | test_url_request_context_.Init(); | 
|  | } | 
|  |  | 
|  | GURL test_url() const { return net::URLRequestTestJob::test_url_1(); } | 
|  |  | 
|  | TestNetworkQualityEstimator* network_quality_estimator() { | 
|  | return &network_quality_estimator_; | 
|  | } | 
|  |  | 
|  | std::string test_data() const { | 
|  | return net::URLRequestTestJob::test_data_1(); | 
|  | } | 
|  |  | 
|  | virtual std::unique_ptr<net::URLRequestJobFactory::ProtocolHandler> | 
|  | CreateProtocolHandler() { | 
|  | return net::URLRequestTestJob::CreateProtocolHandler(); | 
|  | } | 
|  |  | 
|  | virtual std::unique_ptr<ResourceHandler> WrapResourceHandler( | 
|  | std::unique_ptr<ResourceHandlerStub> leaf_handler, | 
|  | net::URLRequest* request) { | 
|  | return std::move(leaf_handler); | 
|  | } | 
|  |  | 
|  | // Replaces loader_ with a new one for |request|. | 
|  | void SetUpResourceLoader(std::unique_ptr<net::URLRequest> request, | 
|  | ResourceType resource_type, | 
|  | bool belongs_to_main_frame) { | 
|  | raw_ptr_to_request_ = request.get(); | 
|  |  | 
|  | // A request marked as a main frame request must also belong to a main | 
|  | // frame. | 
|  | ASSERT_TRUE((resource_type != RESOURCE_TYPE_MAIN_FRAME) || | 
|  | belongs_to_main_frame); | 
|  |  | 
|  | RenderFrameHost* rfh = web_contents_->GetMainFrame(); | 
|  | ResourceRequestInfo::AllocateForTesting( | 
|  | request.get(), resource_type, &resource_context_, | 
|  | rfh->GetProcess()->GetID(), rfh->GetRenderViewHost()->GetRoutingID(), | 
|  | rfh->GetRoutingID(), belongs_to_main_frame, | 
|  | false /* parent_is_main_frame */, true /* allow_download */, | 
|  | false /* is_async */, false /* is_using_lofi_ */); | 
|  | std::unique_ptr<ResourceHandlerStub> resource_handler( | 
|  | new ResourceHandlerStub(request.get())); | 
|  | raw_ptr_resource_handler_ = resource_handler.get(); | 
|  | loader_.reset(new ResourceLoader( | 
|  | std::move(request), | 
|  | WrapResourceHandler(std::move(resource_handler), raw_ptr_to_request_), | 
|  | this)); | 
|  | } | 
|  |  | 
|  | void SetUp() override { | 
|  | job_factory_.SetProtocolHandler("test", CreateProtocolHandler()); | 
|  |  | 
|  | browser_context_.reset(new TestBrowserContext()); | 
|  | scoped_refptr<SiteInstance> site_instance = | 
|  | SiteInstance::Create(browser_context_.get()); | 
|  | web_contents_.reset( | 
|  | TestWebContents::Create(browser_context_.get(), site_instance.get())); | 
|  |  | 
|  | std::unique_ptr<net::URLRequest> request( | 
|  | resource_context_.GetRequestContext()->CreateRequest( | 
|  | test_url(), net::DEFAULT_PRIORITY, nullptr /* delegate */)); | 
|  | SetUpResourceLoader(std::move(request), RESOURCE_TYPE_MAIN_FRAME, true); | 
|  | } | 
|  |  | 
|  | void TearDown() override { | 
|  | // Destroy the WebContents and pump the event loop before destroying | 
|  | // |rvh_test_enabler_| and |thread_bundle_|. This lets asynchronous cleanup | 
|  | // tasks complete. | 
|  | web_contents_.reset(); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | void SetClientCertStore(std::unique_ptr<net::ClientCertStore> store) { | 
|  | dummy_cert_store_ = std::move(store); | 
|  | } | 
|  |  | 
|  | // ResourceLoaderDelegate: | 
|  | ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( | 
|  | ResourceLoader* loader, | 
|  | net::AuthChallengeInfo* auth_info) override { | 
|  | return NULL; | 
|  | } | 
|  | bool HandleExternalProtocol(ResourceLoader* loader, | 
|  | const GURL& url) override { | 
|  | return false; | 
|  | } | 
|  | void DidStartRequest(ResourceLoader* loader) override {} | 
|  | void DidReceiveRedirect(ResourceLoader* loader, | 
|  | const GURL& new_url, | 
|  | ResourceResponse* response) override {} | 
|  | void DidReceiveResponse(ResourceLoader* loader) override {} | 
|  | void DidFinishLoading(ResourceLoader* loader) override {} | 
|  | std::unique_ptr<net::ClientCertStore> CreateClientCertStore( | 
|  | ResourceLoader* loader) override { | 
|  | return std::move(dummy_cert_store_); | 
|  | } | 
|  |  | 
|  | TestBrowserThreadBundle thread_bundle_; | 
|  | RenderViewHostTestEnabler rvh_test_enabler_; | 
|  |  | 
|  | net::URLRequestJobFactoryImpl job_factory_; | 
|  | TestNetworkQualityEstimator network_quality_estimator_; | 
|  | net::TestURLRequestContext test_url_request_context_; | 
|  | MockResourceContext resource_context_; | 
|  | std::unique_ptr<TestBrowserContext> browser_context_; | 
|  | std::unique_ptr<TestWebContents> web_contents_; | 
|  | std::unique_ptr<net::ClientCertStore> dummy_cert_store_; | 
|  |  | 
|  | // The ResourceLoader owns the URLRequest and the ResourceHandler. | 
|  | ResourceHandlerStub* raw_ptr_resource_handler_; | 
|  | net::URLRequest* raw_ptr_to_request_; | 
|  | std::unique_ptr<ResourceLoader> loader_; | 
|  | }; | 
|  |  | 
|  | class ClientCertResourceLoaderTest : public ResourceLoaderTest { | 
|  | protected: | 
|  | std::unique_ptr<net::URLRequestJobFactory::ProtocolHandler> | 
|  | CreateProtocolHandler() override { | 
|  | return base::WrapUnique(new MockClientCertJobProtocolHandler); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // A ResourceLoaderTest that intercepts https://example.test and | 
|  | // https://example-redirect.test URLs and sets SSL info on the | 
|  | // responses. The latter serves a Location: header in the response. | 
|  | class HTTPSSecurityInfoResourceLoaderTest : public ResourceLoaderTest { | 
|  | public: | 
|  | HTTPSSecurityInfoResourceLoaderTest() | 
|  | : ResourceLoaderTest(), | 
|  | test_https_url_("https://example.test"), | 
|  | test_https_redirect_url_("https://example-redirect.test") {} | 
|  |  | 
|  | ~HTTPSSecurityInfoResourceLoaderTest() override {} | 
|  |  | 
|  | const GURL& test_https_url() const { return test_https_url_; } | 
|  | const GURL& test_https_redirect_url() const { | 
|  | return test_https_redirect_url_; | 
|  | } | 
|  |  | 
|  | protected: | 
|  | void SetUp() override { | 
|  | ResourceLoaderTest::SetUp(); | 
|  | net::URLRequestFilter::GetInstance()->ClearHandlers(); | 
|  | net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | 
|  | "https", "example.test", | 
|  | std::unique_ptr<net::URLRequestInterceptor>( | 
|  | new MockHTTPSJobURLRequestInterceptor(false /* redirect */))); | 
|  | net::URLRequestFilter::GetInstance()->AddHostnameInterceptor( | 
|  | "https", "example-redirect.test", | 
|  | std::unique_ptr<net::URLRequestInterceptor>( | 
|  | new MockHTTPSJobURLRequestInterceptor(true /* redirect */))); | 
|  | } | 
|  |  | 
|  | private: | 
|  | const GURL test_https_url_; | 
|  | const GURL test_https_redirect_url_; | 
|  | }; | 
|  |  | 
|  | // Tests that client certificates are requested with ClientCertStore lookup. | 
|  | TEST_F(ClientCertResourceLoaderTest, WithStoreLookup) { | 
|  | // Set up the test client cert store. | 
|  | int store_request_count; | 
|  | std::vector<std::string> store_requested_authorities; | 
|  | net::CertificateList dummy_certs(1, GetTestCert()); | 
|  | std::unique_ptr<ClientCertStoreStub> test_store(new ClientCertStoreStub( | 
|  | dummy_certs, &store_request_count, &store_requested_authorities)); | 
|  | SetClientCertStore(std::move(test_store)); | 
|  |  | 
|  | // Plug in test content browser client. | 
|  | SelectCertificateBrowserClient test_client; | 
|  | ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client); | 
|  |  | 
|  | // Start the request and wait for it to pause. | 
|  | loader_->StartRequest(); | 
|  | test_client.WaitForSelectCertificate(); | 
|  |  | 
|  | EXPECT_FALSE(raw_ptr_resource_handler_->received_response_completed()); | 
|  |  | 
|  | // Check if the test store was queried against correct |cert_authorities|. | 
|  | EXPECT_EQ(1, store_request_count); | 
|  | EXPECT_EQ(MockClientCertURLRequestJob::test_authorities(), | 
|  | store_requested_authorities); | 
|  |  | 
|  | // Check if the retrieved certificates were passed to the content browser | 
|  | // client. | 
|  | EXPECT_EQ(1, test_client.call_count()); | 
|  | EXPECT_EQ(dummy_certs, test_client.passed_certs()); | 
|  |  | 
|  | // Continue the request. | 
|  | test_client.ContinueWithCertificate(nullptr); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  | EXPECT_EQ(net::OK, raw_ptr_resource_handler_->status().error()); | 
|  |  | 
|  | // Restore the original content browser client. | 
|  | SetBrowserClientForTesting(old_client); | 
|  | } | 
|  |  | 
|  | // Tests that client certificates are requested on a platform with NULL | 
|  | // ClientCertStore. | 
|  | TEST_F(ClientCertResourceLoaderTest, WithNullStore) { | 
|  | // Plug in test content browser client. | 
|  | SelectCertificateBrowserClient test_client; | 
|  | ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client); | 
|  |  | 
|  | // Start the request and wait for it to pause. | 
|  | loader_->StartRequest(); | 
|  | test_client.WaitForSelectCertificate(); | 
|  |  | 
|  | // Check if the SelectClientCertificate was called on the content browser | 
|  | // client. | 
|  | EXPECT_EQ(1, test_client.call_count()); | 
|  | EXPECT_EQ(net::CertificateList(), test_client.passed_certs()); | 
|  |  | 
|  | // Continue the request. | 
|  | test_client.ContinueWithCertificate(nullptr); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  | EXPECT_EQ(net::OK, raw_ptr_resource_handler_->status().error()); | 
|  |  | 
|  | // Restore the original content browser client. | 
|  | SetBrowserClientForTesting(old_client); | 
|  | } | 
|  |  | 
|  | // Tests that the ContentBrowserClient may cancel a certificate request. | 
|  | TEST_F(ClientCertResourceLoaderTest, CancelSelection) { | 
|  | // Plug in test content browser client. | 
|  | SelectCertificateBrowserClient test_client; | 
|  | ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client); | 
|  |  | 
|  | // Start the request and wait for it to pause. | 
|  | loader_->StartRequest(); | 
|  | test_client.WaitForSelectCertificate(); | 
|  |  | 
|  | // Check if the SelectClientCertificate was called on the content browser | 
|  | // client. | 
|  | EXPECT_EQ(1, test_client.call_count()); | 
|  | EXPECT_EQ(net::CertificateList(), test_client.passed_certs()); | 
|  |  | 
|  | // Cancel the request. | 
|  | test_client.CancelCertificateSelection(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  | EXPECT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, | 
|  | raw_ptr_resource_handler_->status().error()); | 
|  |  | 
|  | // Restore the original content browser client. | 
|  | SetBrowserClientForTesting(old_client); | 
|  | } | 
|  |  | 
|  | // Verifies that requests without WebContents attached abort. | 
|  | TEST_F(ClientCertResourceLoaderTest, NoWebContents) { | 
|  | // Destroy the WebContents before starting the request. | 
|  | web_contents_.reset(); | 
|  |  | 
|  | // Plug in test content browser client. | 
|  | SelectCertificateBrowserClient test_client; | 
|  | ContentBrowserClient* old_client = SetBrowserClientForTesting(&test_client); | 
|  |  | 
|  | // Start the request and wait for it to complete. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // Check that SelectClientCertificate wasn't called and the request aborted. | 
|  | EXPECT_EQ(0, test_client.call_count()); | 
|  | EXPECT_EQ(net::ERR_SSL_CLIENT_AUTH_CERT_NEEDED, | 
|  | raw_ptr_resource_handler_->status().error()); | 
|  |  | 
|  | // Restore the original content browser client. | 
|  | SetBrowserClientForTesting(old_client); | 
|  | } | 
|  |  | 
|  | // Verifies that ClientCertStore's callback doesn't crash if called after the | 
|  | // loader is destroyed. | 
|  | TEST_F(ClientCertResourceLoaderTest, StoreAsyncCancel) { | 
|  | base::RunLoop loader_destroyed_run_loop; | 
|  | LoaderDestroyingCertStore* test_store = | 
|  | new LoaderDestroyingCertStore(&loader_, | 
|  | loader_destroyed_run_loop.QuitClosure()); | 
|  | SetClientCertStore(base::WrapUnique(test_store)); | 
|  |  | 
|  | loader_->StartRequest(); | 
|  | loader_destroyed_run_loop.Run(); | 
|  | EXPECT_FALSE(loader_); | 
|  |  | 
|  | // Pump the event loop to ensure nothing asynchronous crashes either. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | } | 
|  |  | 
|  | TEST_F(ResourceLoaderTest, ResumeCancelledRequest) { | 
|  | raw_ptr_resource_handler_->set_defer_request_on_will_start(true); | 
|  |  | 
|  | loader_->StartRequest(); | 
|  | loader_->CancelRequest(true); | 
|  | static_cast<ResourceController*>(loader_.get())->Resume(); | 
|  | } | 
|  |  | 
|  | // Tests that no invariants are broken if a ResourceHandler cancels during | 
|  | // OnReadCompleted. | 
|  | TEST_F(ResourceLoaderTest, CancelOnReadCompleted) { | 
|  | raw_ptr_resource_handler_->set_cancel_on_read_completed(true); | 
|  |  | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_EQ(net::URLRequestStatus::CANCELED, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | } | 
|  |  | 
|  | // Tests that no invariants are broken if a ResourceHandler defers EOF. | 
|  | TEST_F(ResourceLoaderTest, DeferEOF) { | 
|  | raw_ptr_resource_handler_->set_defer_eof(true); | 
|  |  | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForDeferredStep(); | 
|  |  | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_FALSE(raw_ptr_resource_handler_->received_response_completed()); | 
|  |  | 
|  | raw_ptr_resource_handler_->Resume(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  | EXPECT_EQ(net::URLRequestStatus::SUCCESS, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | } | 
|  |  | 
|  | class ResourceLoaderRedirectToFileTest : public ResourceLoaderTest { | 
|  | public: | 
|  | ResourceLoaderRedirectToFileTest() | 
|  | : file_stream_(NULL), | 
|  | redirect_to_file_resource_handler_(NULL) { | 
|  | } | 
|  |  | 
|  | ~ResourceLoaderRedirectToFileTest() override { | 
|  | // Releasing the loader should result in destroying the file asynchronously. | 
|  | file_stream_ = nullptr; | 
|  | deletable_file_ = nullptr; | 
|  | loader_.reset(); | 
|  |  | 
|  | // Wait for the task to delete the file to run, and make sure the file is | 
|  | // cleaned up. | 
|  | base::RunLoop().RunUntilIdle(); | 
|  | EXPECT_FALSE(base::PathExists(temp_path())); | 
|  | } | 
|  |  | 
|  | base::FilePath temp_path() const { return temp_path_; } | 
|  | ShareableFileReference* deletable_file() const { | 
|  | return deletable_file_.get(); | 
|  | } | 
|  | net::testing::MockFileStream* file_stream() const { return file_stream_; } | 
|  | RedirectToFileResourceHandler* redirect_to_file_resource_handler() const { | 
|  | return redirect_to_file_resource_handler_; | 
|  | } | 
|  |  | 
|  | std::unique_ptr<ResourceHandler> WrapResourceHandler( | 
|  | std::unique_ptr<ResourceHandlerStub> leaf_handler, | 
|  | net::URLRequest* request) override { | 
|  | leaf_handler->set_expect_reads(false); | 
|  |  | 
|  | // Make a temporary file. | 
|  | CHECK(base::CreateTemporaryFile(&temp_path_)); | 
|  | int flags = base::File::FLAG_WRITE | base::File::FLAG_TEMPORARY | | 
|  | base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_ASYNC; | 
|  | base::File file(temp_path_, flags); | 
|  | CHECK(file.IsValid()); | 
|  |  | 
|  | // Create mock file streams and a ShareableFileReference. | 
|  | std::unique_ptr<net::testing::MockFileStream> file_stream( | 
|  | new net::testing::MockFileStream(std::move(file), | 
|  | base::ThreadTaskRunnerHandle::Get())); | 
|  | file_stream_ = file_stream.get(); | 
|  | deletable_file_ = ShareableFileReference::GetOrCreate( | 
|  | temp_path_, ShareableFileReference::DELETE_ON_FINAL_RELEASE, | 
|  | BrowserThread::GetTaskRunnerForThread(BrowserThread::FILE).get()); | 
|  |  | 
|  | // Inject them into the handler. | 
|  | std::unique_ptr<RedirectToFileResourceHandler> handler( | 
|  | new RedirectToFileResourceHandler(std::move(leaf_handler), request)); | 
|  | redirect_to_file_resource_handler_ = handler.get(); | 
|  | handler->SetCreateTemporaryFileStreamFunctionForTesting( | 
|  | base::Bind(&ResourceLoaderRedirectToFileTest::PostCallback, | 
|  | base::Unretained(this), | 
|  | base::Passed(&file_stream))); | 
|  | return std::move(handler); | 
|  | } | 
|  |  | 
|  | private: | 
|  | void PostCallback(std::unique_ptr<net::FileStream> file_stream, | 
|  | const CreateTemporaryFileStreamCallback& callback) { | 
|  | base::ThreadTaskRunnerHandle::Get()->PostTask( | 
|  | FROM_HERE, | 
|  | base::Bind(callback, base::File::FILE_OK, base::Passed(&file_stream), | 
|  | base::RetainedRef(deletable_file_))); | 
|  | } | 
|  |  | 
|  | base::FilePath temp_path_; | 
|  | scoped_refptr<ShareableFileReference> deletable_file_; | 
|  | // These are owned by the ResourceLoader. | 
|  | net::testing::MockFileStream* file_stream_; | 
|  | RedirectToFileResourceHandler* redirect_to_file_resource_handler_; | 
|  | }; | 
|  |  | 
|  | // Tests that a RedirectToFileResourceHandler works and forwards everything | 
|  | // downstream. | 
|  | TEST_F(ResourceLoaderRedirectToFileTest, Basic) { | 
|  | // Run it to completion. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // Check that the handler forwarded all information to the downstream handler. | 
|  | EXPECT_EQ(temp_path(), | 
|  | raw_ptr_resource_handler_->response()->head.download_file_path); | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_EQ(net::URLRequestStatus::SUCCESS, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | EXPECT_EQ(test_data().size(), static_cast<size_t>( | 
|  | raw_ptr_resource_handler_->total_bytes_downloaded())); | 
|  |  | 
|  | // Check that the data was written to the file. | 
|  | std::string contents; | 
|  | ASSERT_TRUE(base::ReadFileToString(temp_path(), &contents)); | 
|  | EXPECT_EQ(test_data(), contents); | 
|  | } | 
|  |  | 
|  | // Tests that RedirectToFileResourceHandler handles errors in creating the | 
|  | // temporary file. | 
|  | TEST_F(ResourceLoaderRedirectToFileTest, CreateTemporaryError) { | 
|  | // Swap out the create temporary function. | 
|  | redirect_to_file_resource_handler()-> | 
|  | SetCreateTemporaryFileStreamFunctionForTesting( | 
|  | base::Bind(&CreateTemporaryError, base::File::FILE_ERROR_FAILED)); | 
|  |  | 
|  | // Run it to completion. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // To downstream, the request was canceled. | 
|  | EXPECT_EQ(net::URLRequestStatus::CANCELED, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded()); | 
|  | } | 
|  |  | 
|  | // Tests that RedirectToFileResourceHandler handles synchronous write errors. | 
|  | TEST_F(ResourceLoaderRedirectToFileTest, WriteError) { | 
|  | file_stream()->set_forced_error(net::ERR_FAILED); | 
|  |  | 
|  | // Run it to completion. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // To downstream, the request was canceled sometime after it started, but | 
|  | // before any data was written. | 
|  | EXPECT_EQ(temp_path(), | 
|  | raw_ptr_resource_handler_->response()->head.download_file_path); | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_EQ(net::URLRequestStatus::CANCELED, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded()); | 
|  | } | 
|  |  | 
|  | // Tests that RedirectToFileResourceHandler handles asynchronous write errors. | 
|  | TEST_F(ResourceLoaderRedirectToFileTest, WriteErrorAsync) { | 
|  | file_stream()->set_forced_error_async(net::ERR_FAILED); | 
|  |  | 
|  | // Run it to completion. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // To downstream, the request was canceled sometime after it started, but | 
|  | // before any data was written. | 
|  | EXPECT_EQ(temp_path(), | 
|  | raw_ptr_resource_handler_->response()->head.download_file_path); | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_EQ(net::URLRequestStatus::CANCELED, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded()); | 
|  | } | 
|  |  | 
|  | // Tests that RedirectToFileHandler defers completion if there are outstanding | 
|  | // writes and accounts for errors which occur in that time. | 
|  | TEST_F(ResourceLoaderRedirectToFileTest, DeferCompletion) { | 
|  | // Program the MockFileStream to error asynchronously, but throttle the | 
|  | // callback. | 
|  | file_stream()->set_forced_error_async(net::ERR_FAILED); | 
|  | file_stream()->ThrottleCallbacks(); | 
|  |  | 
|  | // Run it as far as it will go. | 
|  | loader_->StartRequest(); | 
|  | base::RunLoop().RunUntilIdle(); | 
|  |  | 
|  | // At this point, the request should have completed. | 
|  | EXPECT_EQ(net::URLRequestStatus::SUCCESS, | 
|  | raw_ptr_to_request_->status().status()); | 
|  |  | 
|  | // However, the resource loader stack is stuck somewhere after receiving the | 
|  | // response. | 
|  | EXPECT_EQ(temp_path(), | 
|  | raw_ptr_resource_handler_->response()->head.download_file_path); | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_FALSE(raw_ptr_resource_handler_->received_response_completed()); | 
|  | EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded()); | 
|  |  | 
|  | // Now, release the floodgates. | 
|  | file_stream()->ReleaseCallbacks(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // Although the URLRequest was successful, the leaf handler sees a failure | 
|  | // because the write never completed. | 
|  | EXPECT_EQ(net::URLRequestStatus::CANCELED, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | } | 
|  |  | 
|  | // Tests that a RedirectToFileResourceHandler behaves properly when the | 
|  | // downstream handler defers OnWillStart. | 
|  | TEST_F(ResourceLoaderRedirectToFileTest, DownstreamDeferStart) { | 
|  | // Defer OnWillStart. | 
|  | raw_ptr_resource_handler_->set_defer_request_on_will_start(true); | 
|  |  | 
|  | // Run as far as we'll go. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForDeferredStep(); | 
|  |  | 
|  | // The request should have stopped at OnWillStart. | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_FALSE(raw_ptr_resource_handler_->response()); | 
|  | EXPECT_FALSE(raw_ptr_resource_handler_->received_response_completed()); | 
|  | EXPECT_EQ(0, raw_ptr_resource_handler_->total_bytes_downloaded()); | 
|  |  | 
|  | // Now resume the request. Now we complete. | 
|  | raw_ptr_resource_handler_->Resume(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  |  | 
|  | // Check that the handler forwarded all information to the downstream handler. | 
|  | EXPECT_EQ(temp_path(), | 
|  | raw_ptr_resource_handler_->response()->head.download_file_path); | 
|  | EXPECT_EQ(test_url(), raw_ptr_resource_handler_->start_url()); | 
|  | EXPECT_EQ(net::URLRequestStatus::SUCCESS, | 
|  | raw_ptr_resource_handler_->status().status()); | 
|  | EXPECT_EQ(test_data().size(), static_cast<size_t>( | 
|  | raw_ptr_resource_handler_->total_bytes_downloaded())); | 
|  |  | 
|  | // Check that the data was written to the file. | 
|  | std::string contents; | 
|  | ASSERT_TRUE(base::ReadFileToString(temp_path(), &contents)); | 
|  | EXPECT_EQ(test_data(), contents); | 
|  | } | 
|  |  | 
|  | class EffectiveConnectionTypeResourceLoaderTest : public ResourceLoaderTest { | 
|  | public: | 
|  | void VerifyEffectiveConnectionType( | 
|  | ResourceType resource_type, | 
|  | bool belongs_to_main_frame, | 
|  | net::EffectiveConnectionType set_type, | 
|  | net::EffectiveConnectionType expected_type) { | 
|  | network_quality_estimator()->set_effective_connection_type(set_type); | 
|  |  | 
|  | // Start the request and wait for it to finish. | 
|  | std::unique_ptr<net::URLRequest> request( | 
|  | resource_context_.GetRequestContext()->CreateRequest( | 
|  | test_url(), net::DEFAULT_PRIORITY, nullptr /* delegate */)); | 
|  | SetUpResourceLoader(std::move(request), resource_type, | 
|  | belongs_to_main_frame); | 
|  |  | 
|  | // Send the request and wait until it completes. | 
|  | loader_->StartRequest(); | 
|  | raw_ptr_resource_handler_->WaitForResponseComplete(); | 
|  | ASSERT_EQ(net::URLRequestStatus::SUCCESS, | 
|  | raw_ptr_to_request_->status().status()); | 
|  |  | 
|  | EXPECT_EQ(expected_type, | 
|  | raw_ptr_resource_handler_->observed_effective_connection_type()); | 
|  | } | 
|  | }; | 
|  |  | 
|  | // Tests that the effective connection type is set on main frame requests. | 
|  | TEST_F(EffectiveConnectionTypeResourceLoaderTest, Slow2G) { | 
|  | VerifyEffectiveConnectionType(RESOURCE_TYPE_MAIN_FRAME, true, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G); | 
|  | } | 
|  |  | 
|  | // Tests that the effective connection type is set on main frame requests. | 
|  | TEST_F(EffectiveConnectionTypeResourceLoaderTest, 3G) { | 
|  | VerifyEffectiveConnectionType(RESOURCE_TYPE_MAIN_FRAME, true, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_3G, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_3G); | 
|  | } | 
|  |  | 
|  | // Tests that the effective connection type is not set on requests that belong | 
|  | // to main frame. | 
|  | TEST_F(EffectiveConnectionTypeResourceLoaderTest, BelongsToMainFrame) { | 
|  | VerifyEffectiveConnectionType(RESOURCE_TYPE_OBJECT, true, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_3G, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN); | 
|  | } | 
|  |  | 
|  | // Tests that the effective connection type is not set on non-main frame | 
|  | // requests. | 
|  | TEST_F(EffectiveConnectionTypeResourceLoaderTest, DoesNotBelongToMainFrame) { | 
|  | VerifyEffectiveConnectionType(RESOURCE_TYPE_OBJECT, false, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_3G, | 
|  | net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN); | 
|  | } | 
|  |  | 
|  | }  // namespace content |