Initial import of Cobalt 2.8885 2016-07-27
diff --git a/src/net/url_request/data_protocol_handler.cc b/src/net/url_request/data_protocol_handler.cc
new file mode 100644
index 0000000..3222f72
--- /dev/null
+++ b/src/net/url_request/data_protocol_handler.cc
@@ -0,0 +1,19 @@
+// Copyright (c) 2012 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 "net/url_request/data_protocol_handler.h"
+
+#include "net/url_request/url_request_data_job.h"
+
+namespace net {
+
+DataProtocolHandler::DataProtocolHandler() {
+}
+
+URLRequestJob* DataProtocolHandler::MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ return new URLRequestDataJob(request, network_delegate);
+}
+
+} // namespace net
diff --git a/src/net/url_request/data_protocol_handler.h b/src/net/url_request/data_protocol_handler.h
new file mode 100644
index 0000000..abb5abe
--- /dev/null
+++ b/src/net/url_request/data_protocol_handler.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_DATA_PROTOCOL_HANDLER_H_
+#define NET_URL_REQUEST_DATA_PROTOCOL_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class URLRequestJob;
+
+// Implements a ProtocolHandler for Data jobs.
+class NET_EXPORT DataProtocolHandler
+ : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ DataProtocolHandler();
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(DataProtocolHandler);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_DATA_PROTOCOL_HANDLER_H_
diff --git a/src/net/url_request/file_protocol_handler.cc b/src/net/url_request/file_protocol_handler.cc
new file mode 100644
index 0000000..bbeac1c
--- /dev/null
+++ b/src/net/url_request/file_protocol_handler.cc
@@ -0,0 +1,47 @@
+// Copyright (c) 2012 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 "net/url_request/file_protocol_handler.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_dir_job.h"
+#include "net/url_request/url_request_file_job.h"
+
+namespace net {
+
+FileProtocolHandler::FileProtocolHandler() { }
+
+URLRequestJob* FileProtocolHandler::MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ FilePath file_path;
+ const bool is_file = FileURLToFilePath(request->url(), &file_path);
+
+ // Check file access permissions.
+ if (!network_delegate ||
+ !network_delegate->CanAccessFile(*request, file_path)) {
+ return new URLRequestErrorJob(request, network_delegate, ERR_ACCESS_DENIED);
+ }
+#if !defined (COBALT) // Cobalt doesn't support URLRequestFileDirJob
+ // We need to decide whether to create URLRequestFileJob for file access or
+ // URLRequestFileDirJob for directory access. To avoid accessing the
+ // filesystem, we only look at the path string here.
+ // The code in the URLRequestFileJob::Start() method discovers that a path,
+ // which doesn't end with a slash, should really be treated as a directory,
+ // and it then redirects to the URLRequestFileDirJob.
+ if (is_file &&
+ file_util::EndsWithSeparator(file_path) &&
+ file_path.IsAbsolute()) {
+ return new URLRequestFileDirJob(request, network_delegate, file_path);
+ }
+#endif
+ // Use a regular file request job for all non-directories (including invalid
+ // file names).
+ return new URLRequestFileJob(request, network_delegate, file_path);
+}
+
+} // namespace net
diff --git a/src/net/url_request/file_protocol_handler.h b/src/net/url_request/file_protocol_handler.h
new file mode 100644
index 0000000..619227b
--- /dev/null
+++ b/src/net/url_request/file_protocol_handler.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_FILE_PROTOCOL_HANDLER_H_
+#define NET_URL_REQUEST_FILE_PROTOCOL_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class NetworkDelegate;
+class URLRequestJob;
+
+// Implements a ProtocolHandler for File jobs. If |network_delegate_| is NULL,
+// then all file requests will fail with ERR_ACCESS_DENIED.
+class NET_EXPORT FileProtocolHandler :
+ public URLRequestJobFactory::ProtocolHandler {
+ public:
+ FileProtocolHandler();
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FileProtocolHandler);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_FILE_PROTOCOL_HANDLER_H_
diff --git a/src/net/url_request/fraudulent_certificate_reporter.h b/src/net/url_request/fraudulent_certificate_reporter.h
new file mode 100644
index 0000000..007f660
--- /dev/null
+++ b/src/net/url_request/fraudulent_certificate_reporter.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_
+#define NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_
+
+#include <string>
+
+namespace net {
+
+class SSLInfo;
+
+// FraudulentCertificateReporter is an interface for asynchronously
+// reporting certificate chains that fail the certificate pinning
+// check.
+class FraudulentCertificateReporter {
+ public:
+ virtual ~FraudulentCertificateReporter() {}
+
+ // Sends a report to the report collection server containing the |ssl_info|
+ // associated with a connection to |hostname|. If |sni_available| is true,
+ // searches the SNI transport security metadata as well as the usual
+ // transport security metadata when determining policy for sending the report.
+ virtual void SendReport(const std::string& hostname,
+ const SSLInfo& ssl_info,
+ bool sni_available) = 0;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_FRAUDULENT_CERTIFICATE_REPORTER_H_
+
diff --git a/src/net/url_request/ftp_protocol_handler.cc b/src/net/url_request/ftp_protocol_handler.cc
new file mode 100644
index 0000000..7d9ba88
--- /dev/null
+++ b/src/net/url_request/ftp_protocol_handler.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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 "net/url_request/ftp_protocol_handler.h"
+
+#include "base/logging.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_ftp_job.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+FtpProtocolHandler::FtpProtocolHandler(
+ FtpTransactionFactory* ftp_transaction_factory,
+ FtpAuthCache* ftp_auth_cache)
+ : ftp_transaction_factory_(ftp_transaction_factory),
+ ftp_auth_cache_(ftp_auth_cache) {
+ DCHECK(ftp_transaction_factory_);
+ DCHECK(ftp_auth_cache_);
+}
+
+URLRequestJob* FtpProtocolHandler::MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ int port = request->url().IntPort();
+ if (request->url().has_port() &&
+ !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port)) {
+ return new URLRequestErrorJob(request, network_delegate, ERR_UNSAFE_PORT);
+ }
+
+ return new URLRequestFtpJob(request,
+ network_delegate,
+ ftp_transaction_factory_,
+ ftp_auth_cache_);
+}
+
+} // namespace net
diff --git a/src/net/url_request/ftp_protocol_handler.h b/src/net/url_request/ftp_protocol_handler.h
new file mode 100644
index 0000000..871f422
--- /dev/null
+++ b/src/net/url_request/ftp_protocol_handler.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
+#define NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class FtpAuthCache;
+class FtpTransactionFactory;
+class NetworkDelegate;
+class URLRequestJob;
+
+// Implements a ProtocolHandler for FTP.
+class NET_EXPORT FtpProtocolHandler :
+ public URLRequestJobFactory::ProtocolHandler {
+ public:
+ FtpProtocolHandler(FtpTransactionFactory* ftp_transaction_factory,
+ FtpAuthCache* ftp_auth_cache);
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+
+ private:
+ FtpTransactionFactory* ftp_transaction_factory_;
+ FtpAuthCache* ftp_auth_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(FtpProtocolHandler);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_FTP_PROTOCOL_HANDLER_H_
diff --git a/src/net/url_request/http_user_agent_settings.h b/src/net/url_request/http_user_agent_settings.h
new file mode 100644
index 0000000..f04f556
--- /dev/null
+++ b/src/net/url_request/http_user_agent_settings.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_HTTP_USER_AGENT_SETTINGS_H_
+#define NET_URL_REQUEST_HTTP_USER_AGENT_SETTINGS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+// The interface used by HTTP jobs to retrieve HTTP Accept-Language,
+// Accept-Charset and User-Agent header values.
+class NET_EXPORT HttpUserAgentSettings {
+ public:
+ HttpUserAgentSettings() {}
+ virtual ~HttpUserAgentSettings() {}
+
+ // Gets the value of 'Accept-Language' header field.
+ virtual const std::string& GetAcceptLanguage() const = 0;
+
+ // Gets the value of 'Accept-Charset' header field.
+ virtual const std::string& GetAcceptCharset() const = 0;
+
+ // Gets the UA string.
+ virtual const std::string& GetUserAgent() const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(HttpUserAgentSettings);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_HTTP_USER_AGENT_SETTINGS_H_
+
diff --git a/src/net/url_request/static_http_user_agent_settings.cc b/src/net/url_request/static_http_user_agent_settings.cc
new file mode 100644
index 0000000..d694326
--- /dev/null
+++ b/src/net/url_request/static_http_user_agent_settings.cc
@@ -0,0 +1,34 @@
+// Copyright (c) 2012 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 "net/url_request/static_http_user_agent_settings.h"
+
+namespace net {
+
+StaticHttpUserAgentSettings::StaticHttpUserAgentSettings(
+ const std::string& accept_language,
+ const std::string& accept_charset,
+ const std::string& user_agent)
+ : accept_language_(accept_language),
+ accept_charset_(accept_charset),
+ user_agent_(user_agent) {
+}
+
+StaticHttpUserAgentSettings::~StaticHttpUserAgentSettings() {
+}
+
+const std::string& StaticHttpUserAgentSettings::GetAcceptLanguage() const {
+ return accept_language_;
+}
+
+const std::string& StaticHttpUserAgentSettings::GetAcceptCharset() const {
+ return accept_charset_;
+}
+
+const std::string& StaticHttpUserAgentSettings::GetUserAgent() const {
+ return user_agent_;
+}
+
+} // namespace net
+
diff --git a/src/net/url_request/static_http_user_agent_settings.h b/src/net/url_request/static_http_user_agent_settings.h
new file mode 100644
index 0000000..0663f93
--- /dev/null
+++ b/src/net/url_request/static_http_user_agent_settings.h
@@ -0,0 +1,43 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_STATIC_HTTP_USER_AGENT_SETTINGS_H_
+#define NET_URL_REQUEST_STATIC_HTTP_USER_AGENT_SETTINGS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/url_request/http_user_agent_settings.h"
+
+namespace net {
+
+// An implementation of |HttpUserAgentSettings| that always provides the
+// same constant values for the HTTP Accept-Language, Accept-Charset, and
+// User-Agent headers.
+class NET_EXPORT StaticHttpUserAgentSettings : public HttpUserAgentSettings {
+ public:
+ StaticHttpUserAgentSettings(const std::string& accept_language,
+ const std::string& accept_charset,
+ const std::string& user_agent);
+ virtual ~StaticHttpUserAgentSettings();
+
+ // HttpUserAgentSettings implementation
+ virtual const std::string& GetAcceptLanguage() const OVERRIDE;
+ virtual const std::string& GetAcceptCharset() const OVERRIDE;
+ virtual const std::string& GetUserAgent() const OVERRIDE;
+
+ private:
+ const std::string accept_language_;
+ const std::string accept_charset_;
+ const std::string user_agent_;
+
+ DISALLOW_COPY_AND_ASSIGN(StaticHttpUserAgentSettings);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_STATIC_HTTP_USER_AGENT_SETTINGS_H_
+
diff --git a/src/net/url_request/test_url_fetcher_factory.cc b/src/net/url_request/test_url_fetcher_factory.cc
new file mode 100644
index 0000000..4b076d2
--- /dev/null
+++ b/src/net/url_request/test_url_fetcher_factory.cc
@@ -0,0 +1,377 @@
+// Copyright (c) 2012 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 "net/url_request/test_url_fetcher_factory.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "net/base/host_port_pair.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_fetcher_impl.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+ScopedURLFetcherFactory::ScopedURLFetcherFactory(
+ URLFetcherFactory* factory) {
+ DCHECK(!URLFetcherImpl::factory());
+ URLFetcherImpl::set_factory(factory);
+}
+
+ScopedURLFetcherFactory::~ScopedURLFetcherFactory() {
+ DCHECK(URLFetcherImpl::factory());
+ URLFetcherImpl::set_factory(NULL);
+}
+
+TestURLFetcher::TestURLFetcher(int id,
+ const GURL& url,
+ URLFetcherDelegate* d)
+ : owner_(NULL),
+ id_(id),
+ original_url_(url),
+ delegate_(d),
+ delegate_for_tests_(NULL),
+ did_receive_last_chunk_(false),
+ fake_load_flags_(0),
+ fake_response_code_(-1),
+ fake_response_destination_(STRING),
+ fake_was_fetched_via_proxy_(false),
+ fake_max_retries_(0) {
+}
+
+TestURLFetcher::~TestURLFetcher() {
+ if (delegate_for_tests_)
+ delegate_for_tests_->OnRequestEnd(id_);
+ if (owner_)
+ owner_->RemoveFetcherFromMap(id_);
+}
+
+void TestURLFetcher::SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) {
+ upload_data_ = upload_content;
+}
+
+void TestURLFetcher::SetChunkedUpload(const std::string& upload_content_type) {
+}
+
+void TestURLFetcher::AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) {
+ DCHECK(!did_receive_last_chunk_);
+ did_receive_last_chunk_ = is_last_chunk;
+ chunks_.push_back(data);
+ if (delegate_for_tests_)
+ delegate_for_tests_->OnChunkUpload(id_);
+}
+
+void TestURLFetcher::SetLoadFlags(int load_flags) {
+ fake_load_flags_= load_flags;
+}
+
+int TestURLFetcher::GetLoadFlags() const {
+ return fake_load_flags_;
+}
+
+void TestURLFetcher::SetReferrer(const std::string& referrer) {
+}
+
+void TestURLFetcher::SetExtraRequestHeaders(
+ const std::string& extra_request_headers) {
+ fake_extra_request_headers_.Clear();
+ fake_extra_request_headers_.AddHeadersFromString(extra_request_headers);
+}
+
+void TestURLFetcher::AddExtraRequestHeader(const std::string& header_line) {
+ fake_extra_request_headers_.AddHeaderFromString(header_line);
+}
+
+void TestURLFetcher::GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ *headers = fake_extra_request_headers_;
+}
+
+void TestURLFetcher::SetRequestContext(
+ URLRequestContextGetter* request_context_getter) {
+}
+
+void TestURLFetcher::SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) {
+}
+
+void TestURLFetcher::SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) {
+}
+
+void TestURLFetcher::SetStopOnRedirect(bool stop_on_redirect) {
+}
+
+void TestURLFetcher::SetAutomaticallyRetryOn5xx(bool retry) {
+}
+
+void TestURLFetcher::SetMaxRetriesOn5xx(int max_retries) {
+ fake_max_retries_ = max_retries;
+}
+
+int TestURLFetcher::GetMaxRetriesOn5xx() const {
+ return fake_max_retries_;
+}
+
+base::TimeDelta TestURLFetcher::GetBackoffDelay() const {
+ return fake_backoff_delay_;
+}
+
+void TestURLFetcher::SetAutomaticallyRetryOnNetworkChanges(int max_retries) {
+}
+
+void TestURLFetcher::SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+}
+
+void TestURLFetcher::SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+}
+
+#if defined(COBALT)
+void TestURLFetcher::DiscardResponse() {
+}
+#endif
+
+HttpResponseHeaders* TestURLFetcher::GetResponseHeaders() const {
+ return fake_response_headers_;
+}
+
+HostPortPair TestURLFetcher::GetSocketAddress() const {
+ NOTIMPLEMENTED();
+ return HostPortPair();
+}
+
+bool TestURLFetcher::WasFetchedViaProxy() const {
+ return fake_was_fetched_via_proxy_;
+}
+
+void TestURLFetcher::Start() {
+ // Overriden to do nothing. It is assumed the caller will notify the delegate.
+ if (delegate_for_tests_)
+ delegate_for_tests_->OnRequestStart(id_);
+}
+
+const GURL& TestURLFetcher::GetOriginalURL() const {
+ return original_url_;
+}
+
+const GURL& TestURLFetcher::GetURL() const {
+ return fake_url_;
+}
+
+const URLRequestStatus& TestURLFetcher::GetStatus() const {
+ return fake_status_;
+}
+
+int TestURLFetcher::GetResponseCode() const {
+ return fake_response_code_;
+}
+
+const ResponseCookies& TestURLFetcher::GetCookies() const {
+ return fake_cookies_;
+}
+
+bool TestURLFetcher::FileErrorOccurred(
+ base::PlatformFileError* out_error_code) const {
+ NOTIMPLEMENTED();
+ return false;
+}
+
+void TestURLFetcher::ReceivedContentWasMalformed() {
+}
+
+bool TestURLFetcher::GetResponseAsString(
+ std::string* out_response_string) const {
+ if (fake_response_destination_ != STRING)
+ return false;
+
+ *out_response_string = fake_response_string_;
+ return true;
+}
+
+bool TestURLFetcher::GetResponseAsFilePath(
+ bool take_ownership, FilePath* out_response_path) const {
+ if (fake_response_destination_ != TEMP_FILE)
+ return false;
+
+ *out_response_path = fake_response_file_path_;
+ return true;
+}
+
+void TestURLFetcher::set_status(const URLRequestStatus& status) {
+ fake_status_ = status;
+}
+
+void TestURLFetcher::set_was_fetched_via_proxy(bool flag) {
+ fake_was_fetched_via_proxy_ = flag;
+}
+
+void TestURLFetcher::set_response_headers(
+ scoped_refptr<HttpResponseHeaders> headers) {
+ fake_response_headers_ = headers;
+}
+
+void TestURLFetcher::set_backoff_delay(base::TimeDelta backoff_delay) {
+ fake_backoff_delay_ = backoff_delay;
+}
+
+void TestURLFetcher::SetDelegateForTests(DelegateForTests* delegate_for_tests) {
+ delegate_for_tests_ = delegate_for_tests;
+}
+
+void TestURLFetcher::SetResponseString(const std::string& response) {
+ fake_response_destination_ = STRING;
+ fake_response_string_ = response;
+}
+
+void TestURLFetcher::SetResponseFilePath(const FilePath& path) {
+ fake_response_destination_ = TEMP_FILE;
+ fake_response_file_path_ = path;
+}
+
+TestURLFetcherFactory::TestURLFetcherFactory()
+ : ScopedURLFetcherFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ delegate_for_tests_(NULL),
+ remove_fetcher_on_delete_(false) {
+}
+
+TestURLFetcherFactory::~TestURLFetcherFactory() {}
+
+URLFetcher* TestURLFetcherFactory::CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ TestURLFetcher* fetcher = new TestURLFetcher(id, url, d);
+ if (remove_fetcher_on_delete_)
+ fetcher->set_owner(this);
+ fetcher->SetDelegateForTests(delegate_for_tests_);
+ fetchers_[id] = fetcher;
+ return fetcher;
+}
+
+TestURLFetcher* TestURLFetcherFactory::GetFetcherByID(int id) const {
+ Fetchers::const_iterator i = fetchers_.find(id);
+ return i == fetchers_.end() ? NULL : i->second;
+}
+
+void TestURLFetcherFactory::RemoveFetcherFromMap(int id) {
+ Fetchers::iterator i = fetchers_.find(id);
+ DCHECK(i != fetchers_.end());
+ fetchers_.erase(i);
+}
+
+void TestURLFetcherFactory::SetDelegateForTests(
+ TestURLFetcherDelegateForTests* delegate_for_tests) {
+ delegate_for_tests_ = delegate_for_tests;
+}
+
+// This class is used by the FakeURLFetcherFactory below.
+class FakeURLFetcher : public TestURLFetcher {
+ public:
+ // Normal URL fetcher constructor but also takes in a pre-baked response.
+ FakeURLFetcher(const GURL& url,
+ URLFetcherDelegate* d,
+ const std::string& response_data, bool success)
+ : TestURLFetcher(0, url, d),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+ set_status(URLRequestStatus(
+ success ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED,
+ 0));
+ set_response_code(success ? 200 : 500);
+ SetResponseString(response_data);
+ }
+
+ // Start the request. This will call the given delegate asynchronously
+ // with the pre-baked response as parameter.
+ virtual void Start() OVERRIDE {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&FakeURLFetcher::RunDelegate, weak_factory_.GetWeakPtr()));
+ }
+
+ virtual const GURL& GetURL() const OVERRIDE {
+ return TestURLFetcher::GetOriginalURL();
+ }
+
+ private:
+ virtual ~FakeURLFetcher() {
+ }
+
+ // This is the method which actually calls the delegate that is passed in the
+ // constructor.
+ void RunDelegate() {
+ delegate()->OnURLFetchComplete(this);
+ }
+
+ base::WeakPtrFactory<FakeURLFetcher> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeURLFetcher);
+};
+
+FakeURLFetcherFactory::FakeURLFetcherFactory()
+ : ScopedURLFetcherFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ default_factory_(NULL) {
+}
+
+FakeURLFetcherFactory::FakeURLFetcherFactory(
+ URLFetcherFactory* default_factory)
+ : ScopedURLFetcherFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)),
+ default_factory_(default_factory) {
+}
+
+FakeURLFetcherFactory::~FakeURLFetcherFactory() {}
+
+URLFetcher* FakeURLFetcherFactory::CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ FakeResponseMap::const_iterator it = fake_responses_.find(url);
+ if (it == fake_responses_.end()) {
+ if (default_factory_ == NULL) {
+ // If we don't have a baked response for that URL we return NULL.
+ DLOG(ERROR) << "No baked response for URL: " << url.spec();
+ return NULL;
+ } else {
+ return default_factory_->CreateURLFetcher(id, url, request_type, d);
+ }
+ }
+ return new FakeURLFetcher(url, d, it->second.first, it->second.second);
+}
+
+void FakeURLFetcherFactory::SetFakeResponse(const std::string& url,
+ const std::string& response_data,
+ bool success) {
+ // Overwrite existing URL if it already exists.
+ fake_responses_[GURL(url)] = std::make_pair(response_data, success);
+}
+
+void FakeURLFetcherFactory::ClearFakeResponses() {
+ fake_responses_.clear();
+}
+
+URLFetcherImplFactory::URLFetcherImplFactory() {}
+
+URLFetcherImplFactory::~URLFetcherImplFactory() {}
+
+URLFetcher* URLFetcherImplFactory::CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ return new URLFetcherImpl(url, request_type, d);
+}
+
+} // namespace net
diff --git a/src/net/url_request/test_url_fetcher_factory.h b/src/net/url_request/test_url_fetcher_factory.h
new file mode 100644
index 0000000..62c0efb
--- /dev/null
+++ b/src/net/url_request/test_url_fetcher_factory.h
@@ -0,0 +1,342 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_TEST_URL_FETCHER_FACTORY_H_
+#define NET_URL_REQUEST_TEST_URL_FETCHER_FACTORY_H_
+
+#include <list>
+#include <map>
+#include <string>
+#include <utility>
+
+#include "base/compiler_specific.h"
+#include "base/threading/non_thread_safe.h"
+#include "googleurl/src/gurl.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_fetcher_factory.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+// Changes URLFetcher's Factory for the lifetime of the object.
+// Note that this scoper cannot be nested (to make it even harder to misuse).
+class ScopedURLFetcherFactory : public base::NonThreadSafe {
+ public:
+ explicit ScopedURLFetcherFactory(URLFetcherFactory* factory);
+ virtual ~ScopedURLFetcherFactory();
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ScopedURLFetcherFactory);
+};
+
+// TestURLFetcher and TestURLFetcherFactory are used for testing consumers of
+// URLFetcher. TestURLFetcherFactory is a URLFetcherFactory that creates
+// TestURLFetchers. TestURLFetcher::Start is overriden to do nothing. It is
+// expected that you'll grab the delegate from the TestURLFetcher and invoke
+// the callback method when appropriate. In this way it's easy to mock a
+// URLFetcher.
+// Typical usage:
+// // TestURLFetcher requires a MessageLoop.
+// MessageLoop message_loop;
+// // And an IO thread to release URLRequestContextGetter in URLFetcher::Core.
+// BrowserThreadImpl io_thread(BrowserThread::IO, &message_loop);
+// // Create factory (it automatically sets itself as URLFetcher's factory).
+// TestURLFetcherFactory factory;
+// // Do something that triggers creation of a URLFetcher.
+// ...
+// TestURLFetcher* fetcher = factory.GetFetcherByID(expected_id);
+// DCHECK(fetcher);
+// // Notify delegate with whatever data you want.
+// fetcher->delegate()->OnURLFetchComplete(...);
+// // Make sure consumer of URLFetcher does the right thing.
+// ...
+//
+// Note: if you don't know when your request objects will be created you
+// might want to use the FakeURLFetcher and FakeURLFetcherFactory classes
+// below.
+
+class TestURLFetcherFactory;
+class TestURLFetcher : public URLFetcher {
+ public:
+ // Interface for tests to intercept production code classes using URLFetcher.
+ // Allows even-driven mock server classes to analyze the correctness of
+ // requests / uploads events and forge responses back at the right moment.
+ class DelegateForTests {
+ public:
+ // Callback issued correspondingly to the call to the |Start()| method.
+ virtual void OnRequestStart(int fetcher_id) = 0;
+
+ // Callback issued correspondingly to the call to |AppendChunkToUpload|.
+ // Uploaded chunks can be retrieved with the |upload_chunks()| getter.
+ virtual void OnChunkUpload(int fetcher_id) = 0;
+
+ // Callback issued correspondingly to the destructor.
+ virtual void OnRequestEnd(int fetcher_id) = 0;
+ };
+
+ TestURLFetcher(int id,
+ const GURL& url,
+ URLFetcherDelegate* d);
+ virtual ~TestURLFetcher();
+
+ // URLFetcher implementation
+ virtual void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) OVERRIDE;
+ virtual void SetChunkedUpload(
+ const std::string& upload_content_type) OVERRIDE;
+ // Overriden to cache the chunks uploaded. Caller can read back the uploaded
+ // chunks with the upload_chunks() accessor.
+ virtual void AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) OVERRIDE;
+ virtual void SetLoadFlags(int load_flags) OVERRIDE;
+ virtual int GetLoadFlags() const OVERRIDE;
+ virtual void SetReferrer(const std::string& referrer) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const std::string& extra_request_headers) OVERRIDE;
+ virtual void AddExtraRequestHeader(const std::string& header_line) OVERRIDE;
+ virtual void GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void SetRequestContext(
+ URLRequestContextGetter* request_context_getter) OVERRIDE;
+ virtual void SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) OVERRIDE;
+ virtual void SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) OVERRIDE;
+ virtual void SetStopOnRedirect(bool stop_on_redirect) OVERRIDE;
+ virtual void SetAutomaticallyRetryOn5xx(bool retry) OVERRIDE;
+ virtual void SetMaxRetriesOn5xx(int max_retries) OVERRIDE;
+ virtual int GetMaxRetriesOn5xx() const OVERRIDE;
+ virtual base::TimeDelta GetBackoffDelay() const OVERRIDE;
+ virtual void SetAutomaticallyRetryOnNetworkChanges(int max_retries) OVERRIDE;
+ virtual void SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+#if defined(COBALT)
+ virtual void DiscardResponse() OVERRIDE;
+#endif
+ virtual HttpResponseHeaders* GetResponseHeaders() const OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual bool WasFetchedViaProxy() const OVERRIDE;
+ virtual void Start() OVERRIDE;
+
+ // URL we were created with. Because of how we're using URLFetcher GetURL()
+ // always returns an empty URL. Chances are you'll want to use
+ // GetOriginalURL() in your tests.
+ virtual const GURL& GetOriginalURL() const OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual const URLRequestStatus& GetStatus() const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual const ResponseCookies& GetCookies() const OVERRIDE;
+ virtual bool FileErrorOccurred(
+ base::PlatformFileError* out_error_code) const OVERRIDE;
+ virtual void ReceivedContentWasMalformed() OVERRIDE;
+ // Override response access functions to return fake data.
+ virtual bool GetResponseAsString(
+ std::string* out_response_string) const OVERRIDE;
+ virtual bool GetResponseAsFilePath(
+ bool take_ownership, FilePath* out_response_path) const OVERRIDE;
+
+ // Sets owner of this class. Set it to a non-NULL value if you want
+ // to automatically unregister this fetcher from the owning factory
+ // upon destruction.
+ void set_owner(TestURLFetcherFactory* owner) { owner_ = owner; }
+
+ // Unique ID in our factory.
+ int id() const { return id_; }
+
+ // Returns the data uploaded on this URLFetcher.
+ const std::string& upload_data() const { return upload_data_; }
+
+ // Returns the chunks of data uploaded on this URLFetcher.
+ const std::list<std::string>& upload_chunks() const { return chunks_; }
+
+ // Checks whether the last call to |AppendChunkToUpload(...)| was final.
+ bool did_receive_last_chunk() const { return did_receive_last_chunk_; }
+
+ // Returns the delegate installed on the URLFetcher.
+ URLFetcherDelegate* delegate() const { return delegate_; }
+
+ void set_url(const GURL& url) { fake_url_ = url; }
+ void set_status(const URLRequestStatus& status);
+ void set_response_code(int response_code) {
+ fake_response_code_ = response_code;
+ }
+ void set_cookies(const ResponseCookies& c) { fake_cookies_ = c; }
+ void set_was_fetched_via_proxy(bool flag);
+ void set_response_headers(scoped_refptr<HttpResponseHeaders> headers);
+ void set_backoff_delay(base::TimeDelta backoff_delay);
+ void SetDelegateForTests(DelegateForTests* delegate_for_tests);
+
+ // Set string data.
+ void SetResponseString(const std::string& response);
+
+ // Set File data.
+ void SetResponseFilePath(const FilePath& path);
+
+ private:
+ enum ResponseDestinationType {
+ STRING, // Default: In a std::string
+ TEMP_FILE // Write to a temp file
+ };
+
+ TestURLFetcherFactory* owner_;
+ const int id_;
+ const GURL original_url_;
+ URLFetcherDelegate* delegate_;
+ DelegateForTests* delegate_for_tests_;
+ std::string upload_data_;
+ std::list<std::string> chunks_;
+ bool did_receive_last_chunk_;
+
+ // User can use set_* methods to provide values returned by getters.
+ // Setting the real values is not possible, because the real class
+ // has no setters. The data is a private member of a class defined
+ // in a .cc file, so we can't get at it with friendship.
+ int fake_load_flags_;
+ GURL fake_url_;
+ URLRequestStatus fake_status_;
+ int fake_response_code_;
+ ResponseCookies fake_cookies_;
+ ResponseDestinationType fake_response_destination_;
+ std::string fake_response_string_;
+ FilePath fake_response_file_path_;
+ bool fake_was_fetched_via_proxy_;
+ scoped_refptr<HttpResponseHeaders> fake_response_headers_;
+ HttpRequestHeaders fake_extra_request_headers_;
+ int fake_max_retries_;
+ base::TimeDelta fake_backoff_delay_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestURLFetcher);
+};
+
+typedef TestURLFetcher::DelegateForTests TestURLFetcherDelegateForTests;
+
+// Simple URLFetcherFactory method that creates TestURLFetchers. All fetchers
+// are registered in a map by the id passed to the create method.
+// Optionally, a fetcher may be automatically unregistered from the map upon
+// its destruction.
+class TestURLFetcherFactory : public URLFetcherFactory,
+ public ScopedURLFetcherFactory {
+ public:
+ TestURLFetcherFactory();
+ virtual ~TestURLFetcherFactory();
+
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) OVERRIDE;
+ TestURLFetcher* GetFetcherByID(int id) const;
+ void RemoveFetcherFromMap(int id);
+ void SetDelegateForTests(TestURLFetcherDelegateForTests* delegate_for_tests);
+ void set_remove_fetcher_on_delete(bool remove_fetcher_on_delete) {
+ remove_fetcher_on_delete_ = remove_fetcher_on_delete;
+ }
+
+ private:
+ // Maps from id passed to create to the returned URLFetcher.
+ typedef std::map<int, TestURLFetcher*> Fetchers;
+ Fetchers fetchers_;
+ TestURLFetcherDelegateForTests* delegate_for_tests_;
+ // Whether to automatically unregister a fetcher from this factory upon its
+ // destruction, false by default.
+ bool remove_fetcher_on_delete_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestURLFetcherFactory);
+};
+
+// The FakeURLFetcher and FakeURLFetcherFactory classes are similar to the
+// ones above but don't require you to know when exactly the URLFetcher objects
+// will be created.
+//
+// These classes let you set pre-baked HTTP responses for particular URLs.
+// E.g., if the user requests http://a.com/ then respond with an HTTP/500.
+//
+// We assume that the thread that is calling Start() on the URLFetcher object
+// has a message loop running.
+//
+// This class is not thread-safe. You should not call SetFakeResponse or
+// ClearFakeResponse at the same time you call CreateURLFetcher. However, it is
+// OK to start URLFetcher objects while setting or clearning fake responses
+// since already created URLFetcher objects will not be affected by any changes
+// made to the fake responses (once a URLFetcher object is created you cannot
+// change its fake response).
+//
+// Example usage:
+// FakeURLFetcherFactory factory;
+//
+// // You know that class SomeService will request url http://a.com/ and you
+// // want to test the service class by returning an error.
+// factory.SetFakeResponse("http://a.com/", "", false);
+// // But if the service requests http://b.com/asdf you want to respond with
+// // a simple html page and an HTTP/200 code.
+// factory.SetFakeResponse("http://b.com/asdf",
+// "<html><body>hello world</body></html>",
+// true);
+//
+// SomeService service;
+// service.Run(); // Will eventually request these two URLs.
+
+class FakeURLFetcherFactory : public URLFetcherFactory,
+ public ScopedURLFetcherFactory {
+ public:
+ FakeURLFetcherFactory();
+ // FakeURLFetcherFactory that will delegate creating URLFetcher for unknown
+ // url to the given factory.
+ explicit FakeURLFetcherFactory(URLFetcherFactory* default_factory);
+ virtual ~FakeURLFetcherFactory();
+
+ // If no fake response is set for the given URL this method will delegate the
+ // call to |default_factory_| if it is not NULL, or return NULL if it is
+ // NULL.
+ // Otherwise, it will return a URLFetcher object which will respond with the
+ // pre-baked response that the client has set by calling SetFakeResponse().
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) OVERRIDE;
+
+ // Sets the fake response for a given URL. If success is true we will serve
+ // an HTTP/200 and an HTTP/500 otherwise. The |response_data| may be empty.
+ void SetFakeResponse(const std::string& url,
+ const std::string& response_data,
+ bool success);
+
+ // Clear all the fake responses that were previously set via
+ // SetFakeResponse().
+ void ClearFakeResponses();
+
+ private:
+ typedef std::map<GURL, std::pair<std::string, bool> > FakeResponseMap;
+ FakeResponseMap fake_responses_;
+ URLFetcherFactory* default_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(FakeURLFetcherFactory);
+};
+
+// This is an implementation of URLFetcherFactory that will create a
+// URLFetcherImpl. It can be use in conjunction with a FakeURLFetcherFactory in
+// integration tests to control the behavior of some requests but execute
+// all the other ones.
+class URLFetcherImplFactory : public URLFetcherFactory {
+ public:
+ URLFetcherImplFactory();
+ virtual ~URLFetcherImplFactory();
+
+ // This method will create a real URLFetcher.
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) OVERRIDE;
+
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_TEST_URL_FETCHER_FACTORY_H_
diff --git a/src/net/url_request/url_fetcher.cc b/src/net/url_request/url_fetcher.cc
new file mode 100644
index 0000000..3cc75ad
--- /dev/null
+++ b/src/net/url_request/url_fetcher.cc
@@ -0,0 +1,48 @@
+// Copyright (c) 2012 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 "net/url_request/url_fetcher.h"
+
+#include "net/url_request/url_fetcher_factory.h"
+#include "net/url_request/url_fetcher_impl.h"
+
+namespace net {
+
+URLFetcher::~URLFetcher() {}
+
+// static
+URLFetcher* net::URLFetcher::Create(
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ return new URLFetcherImpl(url, request_type, d);
+}
+
+// static
+URLFetcher* net::URLFetcher::Create(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d) {
+ URLFetcherFactory* factory = URLFetcherImpl::factory();
+ return factory ? factory->CreateURLFetcher(id, url, request_type, d) :
+ new URLFetcherImpl(url, request_type, d);
+}
+
+// static
+void net::URLFetcher::CancelAll() {
+ URLFetcherImpl::CancelAll();
+}
+
+// static
+void net::URLFetcher::SetEnableInterceptionForTests(bool enabled) {
+ URLFetcherImpl::SetEnableInterceptionForTests(enabled);
+}
+
+// static
+void net::URLFetcher::SetIgnoreCertificateRequests(bool ignored) {
+ URLFetcherImpl::SetIgnoreCertificateRequests(ignored);
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_fetcher.h b/src/net/url_request/url_fetcher.h
new file mode 100644
index 0000000..59ca148
--- /dev/null
+++ b/src/net/url_request/url_fetcher.h
@@ -0,0 +1,297 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_H_
+#define NET_URL_REQUEST_URL_FETCHER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/platform_file.h"
+#include "base/supports_user_data.h"
+#include "base/task_runner.h"
+#include "net/base/net_export.h"
+
+class FilePath;
+class GURL;
+
+namespace base {
+class MessageLoopProxy;
+class TimeDelta;
+}
+
+namespace net {
+class HostPortPair;
+class HttpRequestHeaders;
+class HttpResponseHeaders;
+class URLFetcherDelegate;
+class URLRequestContextGetter;
+class URLRequestStatus;
+typedef std::vector<std::string> ResponseCookies;
+
+// To use this class, create an instance with the desired URL and a pointer to
+// the object to be notified when the URL has been loaded:
+// URLFetcher* fetcher = URLFetcher::Create("http://www.google.com",
+// URLFetcher::GET, this);
+//
+// You must also set a request context getter:
+//
+// fetcher->SetRequestContext(&my_request_context_getter);
+//
+// Then, optionally set properties on this object, like the request context or
+// extra headers:
+// fetcher->set_extra_request_headers("X-Foo: bar");
+//
+// Finally, start the request:
+// fetcher->Start();
+//
+//
+// The object you supply as a delegate must inherit from URLFetcherDelegate.
+// When the response headers are received, OnURLFetchResponseStarted()
+// will be called with a pointer to the URLFetcher and GetResponseCode()/
+// GetResponseHeaders() can be called INSIDE it to retrieve the response code
+// and headers. Note that when retry is enabled, the response code and headers
+// retrieved at this stage may different than the final ones. When the fetch
+// is completed, OnURLFetchComplete() will be called with a pointer to the
+// URLFetcher. From that point until the original URLFetcher instance is
+// destroyed, you may use accessor methods to see the result of the fetch. You
+// should copy these objects if you need them to live longer than the
+// URLFetcher instance. If the URLFetcher instance is destroyed before the
+// callback happens, the fetch will be canceled and no callback will occur.
+//
+// You may create the URLFetcher instance on any thread; OnURLFetchComplete()
+// will be called back on the same thread you use to create the instance.
+//
+//
+// NOTE: By default URLFetcher requests are NOT intercepted, except when
+// interception is explicitly enabled in tests.
+class NET_EXPORT URLFetcher {
+ public:
+ // Imposible http response code. Used to signal that no http response code
+ // was received.
+ enum ResponseCode {
+ RESPONSE_CODE_INVALID = -1
+ };
+
+ enum RequestType {
+ GET,
+ POST,
+ HEAD,
+ DELETE_REQUEST, // DELETE is already taken on Windows.
+ // <winnt.h> defines a DELETE macro.
+ PUT,
+ };
+
+ // Used by SetURLRequestUserData. The callback should make a fresh
+ // base::SupportsUserData::Data object every time it's called.
+ typedef base::Callback<base::SupportsUserData::Data*()> CreateDataCallback;
+
+ virtual ~URLFetcher();
+
+ // |url| is the URL to send the request to.
+ // |request_type| is the type of request to make.
+ // |d| the object that will receive the callback on fetch completion.
+ static URLFetcher* Create(const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d);
+
+ // Like above, but if there's a URLFetcherFactory registered with the
+ // implementation it will be used. |id| may be used during testing to identify
+ // who is creating the URLFetcher.
+ static URLFetcher* Create(int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d);
+
+ // Cancels all existing URLFetchers. Will notify the URLFetcherDelegates.
+ // Note that any new URLFetchers created while this is running will not be
+ // cancelled. Typically, one would call this in the CleanUp() method of an IO
+ // thread, so that no new URLRequests would be able to start on the IO thread
+ // anyway. This doesn't prevent new URLFetchers from trying to post to the IO
+ // thread though, even though the task won't ever run.
+ static void CancelAll();
+
+ // Normally interception is disabled for URLFetcher, but you can use this
+ // to enable it for tests. Also see ScopedURLFetcherFactory for another way
+ // of testing code that uses an URLFetcher.
+ static void SetEnableInterceptionForTests(bool enabled);
+
+ // Normally, URLFetcher will abort loads that request SSL client certificate
+ // authentication, but this method may be used to cause URLFetchers to ignore
+ // requests for client certificates and continue anonymously. Because such
+ // behaviour affects the URLRequestContext's shared network state and socket
+ // pools, it should only be used for testing.
+ static void SetIgnoreCertificateRequests(bool ignored);
+
+ // Sets data only needed by POSTs. All callers making POST requests should
+ // call this before the request is started. |upload_content_type| is the MIME
+ // type of the content, while |upload_content| is the data to be sent (the
+ // Content-Length header value will be set to the length of this data).
+ virtual void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) = 0;
+
+ // Indicates that the POST data is sent via chunked transfer encoding.
+ // This may only be called before calling Start().
+ // Use AppendChunkToUpload() to give the data chunks after calling Start().
+ virtual void SetChunkedUpload(const std::string& upload_content_type) = 0;
+
+ // Adds the given bytes to a request's POST data transmitted using chunked
+ // transfer encoding.
+ // This method should be called ONLY after calling Start().
+ virtual void AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) = 0;
+
+ // Set one or more load flags as defined in net/base/load_flags.h. Must be
+ // called before the request is started.
+ virtual void SetLoadFlags(int load_flags) = 0;
+
+ // Returns the current load flags.
+ virtual int GetLoadFlags() const = 0;
+
+ // The referrer URL for the request. Must be called before the request is
+ // started.
+ virtual void SetReferrer(const std::string& referrer) = 0;
+
+ // Set extra headers on the request. Must be called before the request
+ // is started.
+ // This replaces the entire extra request headers.
+ virtual void SetExtraRequestHeaders(
+ const std::string& extra_request_headers) = 0;
+
+ // Add header (with format field-name ":" [ field-value ]) to the request
+ // headers. Must be called before the request is started.
+ // This appends the header to the current extra request headers.
+ virtual void AddExtraRequestHeader(const std::string& header_line) = 0;
+
+ virtual void GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const = 0;
+
+ // Set the URLRequestContext on the request. Must be called before the
+ // request is started.
+ virtual void SetRequestContext(
+ URLRequestContextGetter* request_context_getter) = 0;
+
+ // Set the URL that should be consulted for the third-party cookie
+ // blocking policy.
+ virtual void SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) = 0;
+
+ // Set the key and data callback that is used when setting the user
+ // data on any URLRequest objects this object creates.
+ virtual void SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) = 0;
+
+ // If |stop_on_redirect| is true, 3xx responses will cause the fetch to halt
+ // immediately rather than continue through the redirect. OnURLFetchComplete
+ // will be called, with the URLFetcher's URL set to the redirect destination,
+ // its status set to CANCELED, and its response code set to the relevant 3xx
+ // server response code.
+ virtual void SetStopOnRedirect(bool stop_on_redirect) = 0;
+
+ // If |retry| is false, 5xx responses will be propagated to the observer,
+ // if it is true URLFetcher will automatically re-execute the request,
+ // after backoff_delay() elapses. URLFetcher has it set to true by default.
+ virtual void SetAutomaticallyRetryOn5xx(bool retry) = 0;
+
+ virtual void SetMaxRetriesOn5xx(int max_retries) = 0;
+ virtual int GetMaxRetriesOn5xx() const = 0;
+
+ // Returns the back-off delay before the request will be retried,
+ // when a 5xx response was received.
+ virtual base::TimeDelta GetBackoffDelay() const = 0;
+
+ // Retries up to |max_retries| times when requests fail with
+ // ERR_NETWORK_CHANGED. If ERR_NETWORK_CHANGED is received after having
+ // retried |max_retries| times then it is propagated to the observer.
+ virtual void SetAutomaticallyRetryOnNetworkChanges(int max_retries) = 0;
+
+ // By default, the response is saved in a string. Call this method to save the
+ // response to a file instead. Must be called before Start().
+ // |file_task_runner| will be used for all file operations.
+ // To save to a temporary file, use SaveResponseToTemporaryFile().
+ // The created file is removed when the URLFetcher is deleted unless you
+ // take ownership by calling GetResponseAsFilePath().
+ virtual void SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) = 0;
+
+ // By default, the response is saved in a string. Call this method to save the
+ // response to a temporary file instead. Must be called before Start().
+ // |file_task_runner| will be used for all file operations.
+ // The created file is removed when the URLFetcher is deleted unless you
+ // take ownership by calling GetResponseAsFilePath().
+ virtual void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) = 0;
+
+#if defined(COBALT)
+ // By default, the response is saved in a string. Call this method to
+ // discard the response instead. Must be called before Start().
+ // This may be useful if your delegate will be implementing
+ // ShouldSendDownloadData() and buffering it internally.
+ virtual void DiscardResponse() = 0;
+#endif
+
+ // Retrieve the response headers from the request. Must only be called
+ // inside the OnURLFetchReceivedResponseHeaders callback or after the
+ // OnURLFetchComplete callback is called.
+ virtual HttpResponseHeaders* GetResponseHeaders() const = 0;
+
+ // Retrieve the remote socket address from the request. Must only
+ // be called after the OnURLFetchComplete callback has run and if
+ // the request has not failed.
+ virtual HostPortPair GetSocketAddress() const = 0;
+
+ // Returns true if the request was delivered through a proxy. Must only
+ // be called after the OnURLFetchComplete callback has run and the request
+ // has not failed.
+ virtual bool WasFetchedViaProxy() const = 0;
+
+ // Start the request. After this is called, you may not change any other
+ // settings.
+ virtual void Start() = 0;
+
+ // Return the URL that we were asked to fetch.
+ virtual const GURL& GetOriginalURL() const = 0;
+
+ // Return the URL that this fetcher is processing.
+ virtual const GURL& GetURL() const = 0;
+
+ // The status of the URL fetch.
+ virtual const URLRequestStatus& GetStatus() const = 0;
+
+ // The http response code received. Will return RESPONSE_CODE_INVALID
+ // if an error prevented any response from being received.
+ virtual int GetResponseCode() const = 0;
+
+ // Cookies recieved.
+ virtual const ResponseCookies& GetCookies() const = 0;
+
+ // Return true if any file system operation failed. If so, set |error_code|
+ // to the error code. File system errors are only possible if user called
+ // SaveResponseToTemporaryFile().
+ virtual bool FileErrorOccurred(
+ base::PlatformFileError* out_error_code) const = 0;
+
+ // Reports that the received content was malformed.
+ virtual void ReceivedContentWasMalformed() = 0;
+
+ // Get the response as a string. Return false if the fetcher was not
+ // set to store the response as a string.
+ virtual bool GetResponseAsString(std::string* out_response_string) const = 0;
+
+ // Get the path to the file containing the response body. Returns false
+ // if the response body was not saved to a file. If take_ownership is
+ // true, caller takes responsibility for the file, and it will not
+ // be removed once the URLFetcher is destroyed. User should not take
+ // ownership more than once, or call this method after taking ownership.
+ virtual bool GetResponseAsFilePath(bool take_ownership,
+ FilePath* out_response_path) const = 0;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_H_
diff --git a/src/net/url_request/url_fetcher_core.cc b/src/net/url_request/url_fetcher_core.cc
new file mode 100644
index 0000000..2097903
--- /dev/null
+++ b/src/net/url_request/url_fetcher_core.cc
@@ -0,0 +1,1123 @@
+// Copyright (c) 2012 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 "net/url_request/url_fetcher_core.h"
+
+#include "base/bind.h"
+#include "base/file_util_proxy.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/metrics/histogram.h"
+#include "base/stl_util.h"
+#include "base/thread_task_runner_handle.h"
+#include "base/tracked_objects.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace {
+
+const int kBufferSize = 16384;
+const int kDownloadCacheSize = 65536;
+const int kUploadProgressTimerInterval = 100;
+bool g_interception_enabled = false;
+bool g_ignore_certificate_requests = false;
+
+} // namespace
+
+namespace net {
+
+// URLFetcherCore::Registry ---------------------------------------------------
+
+URLFetcherCore::Registry::Registry() {}
+URLFetcherCore::Registry::~Registry() {}
+
+void URLFetcherCore::Registry::AddURLFetcherCore(URLFetcherCore* core) {
+ DCHECK(!ContainsKey(fetchers_, core));
+ fetchers_.insert(core);
+}
+
+void URLFetcherCore::Registry::RemoveURLFetcherCore(URLFetcherCore* core) {
+ DCHECK(ContainsKey(fetchers_, core));
+ fetchers_.erase(core);
+}
+
+void URLFetcherCore::Registry::CancelAll() {
+ while (!fetchers_.empty())
+ (*fetchers_.begin())->CancelURLRequest();
+}
+
+
+// URLFetcherCore::FileWriter -------------------------------------------------
+
+URLFetcherCore::FileWriter::FileWriter(
+ URLFetcherCore* core,
+ scoped_refptr<base::TaskRunner> file_task_runner)
+ : core_(core),
+ error_code_(base::PLATFORM_FILE_OK),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+ file_task_runner_(file_task_runner),
+ file_handle_(base::kInvalidPlatformFileValue) {
+}
+
+URLFetcherCore::FileWriter::~FileWriter() {
+ CloseAndDeleteFile();
+}
+
+void URLFetcherCore::FileWriter::CreateFileAtPath(
+ const FilePath& file_path) {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+ DCHECK(file_task_runner_.get());
+ base::FileUtilProxy::CreateOrOpen(
+ file_task_runner_,
+ file_path,
+ base::PLATFORM_FILE_CREATE_ALWAYS | base::PLATFORM_FILE_WRITE,
+ base::Bind(&URLFetcherCore::FileWriter::DidCreateFile,
+ weak_factory_.GetWeakPtr(),
+ file_path));
+}
+
+void URLFetcherCore::FileWriter::CreateTempFile() {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+ DCHECK(file_task_runner_.get());
+ base::FileUtilProxy::CreateTemporary(
+ file_task_runner_,
+ 0, // No additional file flags.
+ base::Bind(&URLFetcherCore::FileWriter::DidCreateTempFile,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLFetcherCore::FileWriter::WriteBuffer(int num_bytes) {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ // Start writing to the file by setting the initial state
+ // of |pending_bytes_| and |buffer_offset_| to indicate that the
+ // entire buffer has not yet been written.
+ pending_bytes_ = num_bytes;
+ buffer_offset_ = 0;
+ ContinueWrite(base::PLATFORM_FILE_OK, 0);
+}
+
+void URLFetcherCore::FileWriter::ContinueWrite(
+ base::PlatformFileError error_code,
+ int bytes_written) {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ if (file_handle_ == base::kInvalidPlatformFileValue) {
+ // While a write was being done on the file thread, a request
+ // to close or disown the file occured on the IO thread. At
+ // this point a request to close the file is pending on the
+ // file thread.
+ return;
+ }
+
+ // Every code path that resets |core_->request_| should reset
+ // |core->file_writer_| or cause the file writer to disown the file. In the
+ // former case, this callback can not be called, because the weak pointer to
+ // |this| will be NULL. In the latter case, the check of |file_handle_| at the
+ // start of this method ensures that we can not reach this point.
+ CHECK(core_->request_.get());
+
+ if (base::PLATFORM_FILE_OK != error_code) {
+ error_code_ = error_code;
+ CloseAndDeleteFile();
+ core_->delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, core_));
+ return;
+ }
+
+ total_bytes_written_ += bytes_written;
+ buffer_offset_ += bytes_written;
+ pending_bytes_ -= bytes_written;
+
+ if (pending_bytes_ > 0) {
+ base::FileUtilProxy::Write(
+ file_task_runner_, file_handle_,
+ total_bytes_written_, // Append to the end
+ (core_->buffer_->data() + buffer_offset_), pending_bytes_,
+ base::Bind(&URLFetcherCore::FileWriter::ContinueWrite,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ // Finished writing core_->buffer_ to the file. Read some more.
+ core_->ReadResponse();
+ }
+}
+
+void URLFetcherCore::FileWriter::DisownFile() {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ // Disowning is done by the delegate's OnURLFetchComplete method.
+ // The file should be closed by the time that method is called.
+ DCHECK(file_handle_ == base::kInvalidPlatformFileValue);
+
+ // Forget about any file by reseting the path.
+ file_path_.clear();
+}
+
+void URLFetcherCore::FileWriter::CloseFileAndCompleteRequest() {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ if (file_handle_ != base::kInvalidPlatformFileValue) {
+ base::FileUtilProxy::Close(
+ file_task_runner_, file_handle_,
+ base::Bind(&URLFetcherCore::FileWriter::DidCloseFile,
+ weak_factory_.GetWeakPtr()));
+ file_handle_ = base::kInvalidPlatformFileValue;
+ }
+}
+
+void URLFetcherCore::FileWriter::CloseAndDeleteFile() {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ if (file_handle_ == base::kInvalidPlatformFileValue) {
+ DeleteFile(base::PLATFORM_FILE_OK);
+ return;
+ }
+ // Close the file if it is open.
+ base::FileUtilProxy::Close(
+ file_task_runner_, file_handle_,
+ base::Bind(&URLFetcherCore::FileWriter::DeleteFile,
+ weak_factory_.GetWeakPtr()));
+ file_handle_ = base::kInvalidPlatformFileValue;
+}
+
+void URLFetcherCore::FileWriter::DeleteFile(
+ base::PlatformFileError error_code) {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+ if (file_path_.empty())
+ return;
+
+ base::FileUtilProxy::Delete(
+ file_task_runner_, file_path_,
+ false, // No need to recurse, as the path is to a file.
+ base::FileUtilProxy::StatusCallback());
+ DisownFile();
+}
+
+void URLFetcherCore::FileWriter::DidCreateFile(
+ const FilePath& file_path,
+ base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle,
+ bool created) {
+ DidCreateFileInternal(file_path, error_code, file_handle);
+}
+
+void URLFetcherCore::FileWriter::DidCreateTempFile(
+ base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle,
+ const FilePath& file_path) {
+ DidCreateFileInternal(file_path, error_code, file_handle);
+}
+
+void URLFetcherCore::FileWriter::DidCreateFileInternal(
+ const FilePath& file_path,
+ base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle) {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ if (base::PLATFORM_FILE_OK != error_code) {
+ error_code_ = error_code;
+ CloseAndDeleteFile();
+ core_->delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, core_));
+ return;
+ }
+
+ file_path_ = file_path;
+ file_handle_ = file_handle.ReleaseValue();
+ total_bytes_written_ = 0;
+
+ core_->network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::StartURLRequestWhenAppropriate, core_));
+}
+
+void URLFetcherCore::FileWriter::DidCloseFile(
+ base::PlatformFileError error_code) {
+ DCHECK(core_->network_task_runner_->BelongsToCurrentThread());
+
+ if (base::PLATFORM_FILE_OK != error_code) {
+ error_code_ = error_code;
+ CloseAndDeleteFile();
+ core_->delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateFetchIsComplete, core_));
+ return;
+ }
+
+ // If the file was successfully closed, then the URL request is complete.
+ core_->RetryOrCompleteUrlFetch();
+}
+
+
+// URLFetcherCore -------------------------------------------------------------
+
+// static
+base::LazyInstance<URLFetcherCore::Registry>
+ URLFetcherCore::g_registry = LAZY_INSTANCE_INITIALIZER;
+
+URLFetcherCore::URLFetcherCore(URLFetcher* fetcher,
+ const GURL& original_url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d)
+ : fetcher_(fetcher),
+ original_url_(original_url),
+ request_type_(request_type),
+ delegate_(d),
+ delegate_task_runner_(
+ base::ThreadTaskRunnerHandle::Get()),
+ request_(NULL),
+ load_flags_(LOAD_NORMAL),
+ response_code_(URLFetcher::RESPONSE_CODE_INVALID),
+ buffer_(new IOBuffer(kBufferSize)),
+ url_request_data_key_(NULL),
+ was_fetched_via_proxy_(false),
+ is_chunked_upload_(false),
+ was_cancelled_(false),
+ response_destination_(STRING),
+ stop_on_redirect_(false),
+ stopped_on_redirect_(false),
+ automatically_retry_on_5xx_(true),
+ num_retries_on_5xx_(0),
+ max_retries_on_5xx_(0),
+ num_retries_on_network_changes_(0),
+ max_retries_on_network_changes_(0),
+ current_upload_bytes_(-1),
+ current_response_bytes_(0),
+ total_response_bytes_(-1) {
+ CHECK(original_url_.is_valid());
+}
+
+void URLFetcherCore::Start() {
+ DCHECK(delegate_task_runner_);
+ DCHECK(request_context_getter_) << "We need an URLRequestContext!";
+ if (network_task_runner_) {
+ DCHECK_EQ(network_task_runner_,
+ request_context_getter_->GetNetworkTaskRunner());
+ } else {
+ network_task_runner_ = request_context_getter_->GetNetworkTaskRunner();
+ }
+ DCHECK(network_task_runner_.get()) << "We need an IO task runner";
+
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::StartOnIOThread, this));
+}
+
+void URLFetcherCore::Stop() {
+ if (delegate_task_runner_) // May be NULL in tests.
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+
+ delegate_ = NULL;
+ fetcher_ = NULL;
+ if (!network_task_runner_.get())
+ return;
+ if (network_task_runner_->RunsTasksOnCurrentThread()) {
+ CancelURLRequest();
+ } else {
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::CancelURLRequest, this));
+ }
+}
+
+void URLFetcherCore::SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) {
+ DCHECK(!is_chunked_upload_);
+ upload_content_type_ = upload_content_type;
+ upload_content_ = upload_content;
+}
+
+void URLFetcherCore::SetChunkedUpload(const std::string& content_type) {
+ DCHECK(is_chunked_upload_ ||
+ (upload_content_type_.empty() &&
+ upload_content_.empty()));
+ upload_content_type_ = content_type;
+ upload_content_.clear();
+ is_chunked_upload_ = true;
+}
+
+void URLFetcherCore::AppendChunkToUpload(const std::string& content,
+ bool is_last_chunk) {
+ DCHECK(delegate_task_runner_);
+ DCHECK(network_task_runner_.get());
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::CompleteAddingUploadDataChunk, this, content,
+ is_last_chunk));
+}
+
+void URLFetcherCore::SetLoadFlags(int load_flags) {
+ load_flags_ = load_flags;
+}
+
+int URLFetcherCore::GetLoadFlags() const {
+ return load_flags_;
+}
+
+void URLFetcherCore::SetReferrer(const std::string& referrer) {
+ referrer_ = referrer;
+}
+
+void URLFetcherCore::SetExtraRequestHeaders(
+ const std::string& extra_request_headers) {
+ extra_request_headers_.Clear();
+ extra_request_headers_.AddHeadersFromString(extra_request_headers);
+}
+
+void URLFetcherCore::AddExtraRequestHeader(const std::string& header_line) {
+ extra_request_headers_.AddHeaderFromString(header_line);
+}
+
+void URLFetcherCore::GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ headers->CopyFrom(extra_request_headers_);
+}
+
+void URLFetcherCore::SetRequestContext(
+ URLRequestContextGetter* request_context_getter) {
+ DCHECK(!request_context_getter_);
+ DCHECK(request_context_getter);
+ request_context_getter_ = request_context_getter;
+}
+
+void URLFetcherCore::SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) {
+ DCHECK(first_party_for_cookies_.is_empty());
+ first_party_for_cookies_ = first_party_for_cookies;
+}
+
+void URLFetcherCore::SetURLRequestUserData(
+ const void* key,
+ const URLFetcher::CreateDataCallback& create_data_callback) {
+ DCHECK(key);
+ DCHECK(!create_data_callback.is_null());
+ url_request_data_key_ = key;
+ url_request_create_data_callback_ = create_data_callback;
+}
+
+void URLFetcherCore::SetStopOnRedirect(bool stop_on_redirect) {
+ stop_on_redirect_ = stop_on_redirect;
+}
+
+void URLFetcherCore::SetAutomaticallyRetryOn5xx(bool retry) {
+ automatically_retry_on_5xx_ = retry;
+}
+
+void URLFetcherCore::SetMaxRetriesOn5xx(int max_retries) {
+ max_retries_on_5xx_ = max_retries;
+}
+
+int URLFetcherCore::GetMaxRetriesOn5xx() const {
+ return max_retries_on_5xx_;
+}
+
+base::TimeDelta URLFetcherCore::GetBackoffDelay() const {
+ return backoff_delay_;
+}
+
+void URLFetcherCore::SetAutomaticallyRetryOnNetworkChanges(int max_retries) {
+ max_retries_on_network_changes_ = max_retries;
+}
+
+void URLFetcherCore::SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ file_task_runner_ = file_task_runner;
+ response_destination_ = URLFetcherCore::PERMANENT_FILE;
+ response_destination_file_path_ = file_path;
+}
+
+void URLFetcherCore::SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ file_task_runner_ = file_task_runner;
+ response_destination_ = URLFetcherCore::TEMP_FILE;
+}
+
+#if defined(COBALT)
+void URLFetcherCore::DiscardResponse() {
+ response_destination_ = URLFetcherCore::DISCARD;
+}
+#endif
+
+HttpResponseHeaders* URLFetcherCore::GetResponseHeaders() const {
+ return response_headers_;
+}
+
+// TODO(panayiotis): socket_address_ is written in the IO thread,
+// if this is accessed in the UI thread, this could result in a race.
+// Same for response_headers_ above and was_fetched_via_proxy_ below.
+HostPortPair URLFetcherCore::GetSocketAddress() const {
+ return socket_address_;
+}
+
+bool URLFetcherCore::WasFetchedViaProxy() const {
+ return was_fetched_via_proxy_;
+}
+
+const GURL& URLFetcherCore::GetOriginalURL() const {
+ return original_url_;
+}
+
+const GURL& URLFetcherCore::GetURL() const {
+ return url_;
+}
+
+const URLRequestStatus& URLFetcherCore::GetStatus() const {
+ return status_;
+}
+
+int URLFetcherCore::GetResponseCode() const {
+ return response_code_;
+}
+
+const ResponseCookies& URLFetcherCore::GetCookies() const {
+ return cookies_;
+}
+
+bool URLFetcherCore::FileErrorOccurred(
+ base::PlatformFileError* out_error_code) const {
+
+ // Can't have a file error if no file is being created or written to.
+ if (!file_writer_.get())
+ return false;
+
+ base::PlatformFileError error_code = file_writer_->error_code();
+ if (error_code == base::PLATFORM_FILE_OK)
+ return false;
+
+ *out_error_code = error_code;
+ return true;
+}
+
+void URLFetcherCore::ReceivedContentWasMalformed() {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (network_task_runner_.get()) {
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::NotifyMalformedContent, this));
+ }
+}
+
+bool URLFetcherCore::GetResponseAsString(
+ std::string* out_response_string) const {
+ if (response_destination_ != URLFetcherCore::STRING)
+ return false;
+
+ *out_response_string = data_;
+ UMA_HISTOGRAM_MEMORY_KB("UrlFetcher.StringResponseSize",
+ (data_.length() / 1024));
+
+ return true;
+}
+
+bool URLFetcherCore::GetResponseAsFilePath(bool take_ownership,
+ FilePath* out_response_path) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ const bool destination_is_file =
+ response_destination_ == URLFetcherCore::TEMP_FILE ||
+ response_destination_ == URLFetcherCore::PERMANENT_FILE;
+ if (!destination_is_file || !file_writer_.get())
+ return false;
+
+ *out_response_path = file_writer_->file_path();
+
+ if (take_ownership) {
+ network_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::DisownFile, this));
+ }
+ return true;
+}
+
+void URLFetcherCore::OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (stop_on_redirect_) {
+ stopped_on_redirect_ = true;
+ url_ = new_url;
+ response_code_ = request_->GetResponseCode();
+ was_fetched_via_proxy_ = request_->was_fetched_via_proxy();
+ request->Cancel();
+ OnReadCompleted(request, 0);
+ }
+}
+
+void URLFetcherCore::OnResponseStarted(URLRequest* request) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (request_->status().is_success()) {
+ response_code_ = request_->GetResponseCode();
+ response_headers_ = request_->response_headers();
+ socket_address_ = request_->GetSocketAddress();
+ was_fetched_via_proxy_ = request_->was_fetched_via_proxy();
+ total_response_bytes_ = request_->GetExpectedContentSize();
+
+#if defined(COBALT)
+ // We update this earlier than OnReadCompleted(), so that the delegate
+ // can know about it if they call GetURL() in any callback.
+ if (!stopped_on_redirect_) {
+ url_ = request_->url();
+ }
+ InformDelegateResponseStarted();
+#endif // defined(COBALT)
+ }
+
+ ReadResponse();
+}
+
+void URLFetcherCore::OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) {
+ DCHECK_EQ(request, request_.get());
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (g_ignore_certificate_requests) {
+ request->ContinueWithCertificate(NULL);
+ } else {
+ request->Cancel();
+ }
+}
+
+void URLFetcherCore::OnReadCompleted(URLRequest* request,
+ int bytes_read) {
+ DCHECK(request == request_);
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+#if !defined(COBALT)
+ if (!stopped_on_redirect_) {
+ url_ = request->url();
+ }
+#endif
+ URLRequestThrottlerManager* throttler_manager =
+ request->context()->throttler_manager();
+ if (throttler_manager) {
+ url_throttler_entry_ = throttler_manager->RegisterRequestUrl(url_);
+ }
+
+ download_data_cache_.reset();
+ bool waiting_on_write = false;
+ do {
+ if (!request_->status().is_success() || bytes_read <= 0)
+ break;
+
+ current_response_bytes_ += bytes_read;
+ InformDelegateDownloadDataIfNecessary(bytes_read);
+
+ if (!WriteBuffer(bytes_read)) {
+ // If WriteBuffer() returns false, we have a pending write to
+ // wait on before reading further.
+ waiting_on_write = true;
+ break;
+ }
+ } while (request_->Read(buffer_, kBufferSize, &bytes_read));
+ InformDelegateDownloadData();
+
+ const URLRequestStatus status = request_->status();
+
+ if (status.is_success())
+ request_->GetResponseCookies(&cookies_);
+
+ // See comments re: HEAD requests in ReadResponse().
+ if ((!status.is_io_pending() && !waiting_on_write) ||
+ (request_type_ == URLFetcher::HEAD)) {
+ status_ = status;
+ ReleaseRequest();
+
+ // If a file is open, close it.
+ if (file_writer_.get()) {
+ // If the file is open, close it. After closing the file,
+ // RetryOrCompleteUrlFetch() will be called.
+ file_writer_->CloseFileAndCompleteRequest();
+ } else {
+ // Otherwise, complete or retry the URL request directly.
+ RetryOrCompleteUrlFetch();
+ }
+ }
+}
+
+void URLFetcherCore::CancelAll() {
+ g_registry.Get().CancelAll();
+}
+
+int URLFetcherCore::GetNumFetcherCores() {
+ return g_registry.Get().size();
+}
+
+void URLFetcherCore::SetEnableInterceptionForTests(bool enabled) {
+ g_interception_enabled = enabled;
+}
+
+void URLFetcherCore::SetIgnoreCertificateRequests(bool ignored) {
+ g_ignore_certificate_requests = ignored;
+}
+
+URLFetcherCore::~URLFetcherCore() {
+ // |request_| should be NULL. If not, it's unsafe to delete it here since we
+ // may not be on the IO thread.
+ DCHECK(!request_.get());
+}
+
+void URLFetcherCore::StartOnIOThread() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ switch (response_destination_) {
+ case STRING:
+ StartURLRequestWhenAppropriate();
+ break;
+
+ case PERMANENT_FILE:
+ case TEMP_FILE:
+ DCHECK(file_task_runner_.get())
+ << "Need to set the file task runner.";
+
+ file_writer_.reset(new FileWriter(this, file_task_runner_));
+
+ // If the file is successfully created,
+ // URLFetcherCore::StartURLRequestWhenAppropriate() will be called.
+ switch (response_destination_) {
+ case PERMANENT_FILE:
+ file_writer_->CreateFileAtPath(response_destination_file_path_);
+ break;
+ case TEMP_FILE:
+ file_writer_->CreateTempFile();
+ break;
+ default:
+ NOTREACHED();
+ }
+ break;
+
+#if defined(COBALT)
+ case DISCARD:
+ StartURLRequestWhenAppropriate();
+ break;
+#endif
+
+ default:
+ NOTREACHED();
+ }
+}
+
+void URLFetcherCore::StartURLRequest() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (was_cancelled_) {
+ // Since StartURLRequest() is posted as a *delayed* task, it may
+ // run after the URLFetcher was already stopped.
+ return;
+ }
+
+ DCHECK(request_context_getter_);
+ DCHECK(!request_.get());
+
+ g_registry.Get().AddURLFetcherCore(this);
+ current_response_bytes_ = 0;
+ request_.reset(request_context_getter_->GetURLRequestContext()->CreateRequest(
+ original_url_, this));
+ request_->set_stack_trace(stack_trace_);
+ int flags = request_->load_flags() | load_flags_;
+ if (!g_interception_enabled)
+ flags = flags | LOAD_DISABLE_INTERCEPT;
+
+ if (is_chunked_upload_)
+ request_->EnableChunkedUpload();
+ request_->set_load_flags(flags);
+ request_->set_referrer(referrer_);
+ request_->set_first_party_for_cookies(first_party_for_cookies_.is_empty() ?
+ original_url_ : first_party_for_cookies_);
+ if (url_request_data_key_ && !url_request_create_data_callback_.is_null()) {
+ request_->SetUserData(url_request_data_key_,
+ url_request_create_data_callback_.Run());
+ }
+
+ switch (request_type_) {
+ case URLFetcher::GET:
+ break;
+
+ case URLFetcher::POST:
+ case URLFetcher::PUT:
+#if !defined(COBALT)
+ // Allow not specifying a content type when uploading ArrayBuffer data.
+ DCHECK(!upload_content_type_.empty());
+#endif
+ request_->set_method(
+ request_type_ == URLFetcher::POST ? "POST" : "PUT");
+ if (!upload_content_type_.empty()) {
+ extra_request_headers_.SetHeader(HttpRequestHeaders::kContentType,
+ upload_content_type_);
+ }
+ if (!upload_content_.empty()) {
+ scoped_ptr<UploadElementReader> reader(new UploadBytesElementReader(
+ upload_content_.data(), upload_content_.size()));
+ request_->set_upload(make_scoped_ptr(
+ UploadDataStream::CreateWithReader(reader.Pass(), 0)));
+ }
+
+ current_upload_bytes_ = -1;
+ // TODO(kinaba): http://crbug.com/118103. Implement upload callback in the
+ // layer and avoid using timer here.
+ upload_progress_checker_timer_.reset(
+ new base::RepeatingTimer<URLFetcherCore>());
+ upload_progress_checker_timer_->Start(
+ FROM_HERE,
+ base::TimeDelta::FromMilliseconds(kUploadProgressTimerInterval),
+ this,
+ &URLFetcherCore::InformDelegateUploadProgress);
+ break;
+
+ case URLFetcher::HEAD:
+ request_->set_method("HEAD");
+ break;
+
+ case URLFetcher::DELETE_REQUEST:
+ request_->set_method("DELETE");
+ break;
+
+ default:
+ NOTREACHED();
+ }
+
+ if (!extra_request_headers_.IsEmpty())
+ request_->SetExtraRequestHeaders(extra_request_headers_);
+
+ // There might be data left over from a previous request attempt.
+ data_.clear();
+
+ // If we are writing the response to a file, the only caller
+ // of this function should have created it and not written yet.
+ DCHECK(!file_writer_.get() || file_writer_->total_bytes_written() == 0);
+
+ request_->Start();
+}
+
+void URLFetcherCore::StartURLRequestWhenAppropriate() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (was_cancelled_)
+ return;
+
+ DCHECK(request_context_getter_);
+
+ int64 delay = 0LL;
+ if (original_url_throttler_entry_ == NULL) {
+ URLRequestThrottlerManager* manager =
+ request_context_getter_->GetURLRequestContext()->throttler_manager();
+ if (manager) {
+ original_url_throttler_entry_ =
+ manager->RegisterRequestUrl(original_url_);
+ }
+ }
+ if (original_url_throttler_entry_ != NULL) {
+ delay = original_url_throttler_entry_->ReserveSendingTimeForNextRequest(
+ GetBackoffReleaseTime());
+ }
+
+ if (delay == 0) {
+ StartURLRequest();
+ } else {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::StartURLRequest, this),
+ base::TimeDelta::FromMilliseconds(delay));
+ }
+}
+
+void URLFetcherCore::CancelURLRequest() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (request_.get()) {
+ request_->Cancel();
+ ReleaseRequest();
+ }
+ // Release the reference to the request context. There could be multiple
+ // references to URLFetcher::Core at this point so it may take a while to
+ // delete the object, but we cannot delay the destruction of the request
+ // context.
+ request_context_getter_ = NULL;
+ first_party_for_cookies_ = GURL();
+ url_request_data_key_ = NULL;
+ url_request_create_data_callback_.Reset();
+ was_cancelled_ = true;
+ file_writer_.reset();
+}
+
+void URLFetcherCore::OnCompletedURLRequest(
+ base::TimeDelta backoff_delay) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+
+ // Save the status and backoff_delay so that delegates can read it.
+ if (delegate_) {
+ backoff_delay_ = backoff_delay;
+ InformDelegateFetchIsComplete();
+ }
+}
+
+void URLFetcherCore::InformDelegateFetchIsComplete() {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_)
+ delegate_->OnURLFetchComplete(fetcher_);
+}
+
+void URLFetcherCore::NotifyMalformedContent() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (url_throttler_entry_ != NULL) {
+ int status_code = response_code_;
+ if (status_code == URLFetcher::RESPONSE_CODE_INVALID) {
+ // The status code will generally be known by the time clients
+ // call the |ReceivedContentWasMalformed()| function (which ends up
+ // calling the current function) but if it's not, we need to assume
+ // the response was successful so that the total failure count
+ // used to calculate exponential back-off goes up.
+ status_code = 200;
+ }
+ url_throttler_entry_->ReceivedContentWasMalformed(status_code);
+ }
+}
+
+void URLFetcherCore::RetryOrCompleteUrlFetch() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ base::TimeDelta backoff_delay;
+
+ // Checks the response from server.
+ if (response_code_ >= 500 ||
+ status_.error() == ERR_TEMPORARILY_THROTTLED) {
+ // When encountering a server error, we will send the request again
+ // after backoff time.
+ ++num_retries_on_5xx_;
+
+ // Note that backoff_delay may be 0 because (a) the
+ // URLRequestThrottlerManager and related code does not
+ // necessarily back off on the first error, (b) it only backs off
+ // on some of the 5xx status codes, (c) not all URLRequestContexts
+ // have a throttler manager.
+ base::TimeTicks backoff_release_time = GetBackoffReleaseTime();
+ backoff_delay = backoff_release_time - base::TimeTicks::Now();
+ if (backoff_delay < base::TimeDelta())
+ backoff_delay = base::TimeDelta();
+
+ if (automatically_retry_on_5xx_ &&
+ num_retries_on_5xx_ <= max_retries_on_5xx_) {
+ StartOnIOThread();
+ return;
+ }
+ } else {
+ backoff_delay = base::TimeDelta();
+ }
+
+ // Retry if the request failed due to network changes.
+ if (status_.error() == ERR_NETWORK_CHANGED &&
+ num_retries_on_network_changes_ < max_retries_on_network_changes_) {
+ ++num_retries_on_network_changes_;
+
+ // Retry soon, after flushing all the current tasks which may include
+ // further network change observers.
+ network_task_runner_->PostTask(
+ FROM_HERE, base::Bind(&URLFetcherCore::StartOnIOThread, this));
+ return;
+ }
+
+ request_context_getter_ = NULL;
+ first_party_for_cookies_ = GURL();
+ url_request_data_key_ = NULL;
+ url_request_create_data_callback_.Reset();
+ bool posted = delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::OnCompletedURLRequest, this, backoff_delay));
+
+ // If the delegate message loop does not exist any more, then the delegate
+ // should be gone too.
+ DCHECK(posted || !delegate_);
+}
+
+void URLFetcherCore::ReleaseRequest() {
+#if defined(COBALT)
+ if (upload_progress_checker_timer_) {
+ // The request may have completed too quickly, before the upload
+ // progress checker had a chance to run. Force it to run here.
+ InformDelegateUploadProgress();
+ }
+#endif
+
+ upload_progress_checker_timer_.reset();
+ request_.reset();
+ g_registry.Get().RemoveURLFetcherCore(this);
+}
+
+base::TimeTicks URLFetcherCore::GetBackoffReleaseTime() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+
+ if (original_url_throttler_entry_) {
+ base::TimeTicks original_url_backoff =
+ original_url_throttler_entry_->GetExponentialBackoffReleaseTime();
+ base::TimeTicks destination_url_backoff;
+ if (url_throttler_entry_ != NULL &&
+ original_url_throttler_entry_ != url_throttler_entry_) {
+ destination_url_backoff =
+ url_throttler_entry_->GetExponentialBackoffReleaseTime();
+ }
+
+ return original_url_backoff > destination_url_backoff ?
+ original_url_backoff : destination_url_backoff;
+ } else {
+ return base::TimeTicks();
+ }
+}
+
+void URLFetcherCore::CompleteAddingUploadDataChunk(
+ const std::string& content, bool is_last_chunk) {
+ if (was_cancelled_) {
+ // Since CompleteAddingUploadDataChunk() is posted as a *delayed* task, it
+ // may run after the URLFetcher was already stopped.
+ return;
+ }
+ DCHECK(is_chunked_upload_);
+ DCHECK(request_.get());
+ DCHECK(!content.empty());
+ request_->AppendChunkToUpload(content.data(),
+ static_cast<int>(content.length()),
+ is_last_chunk);
+}
+
+// Return true if the write was done and reading may continue.
+// Return false if the write is pending, and the next read will
+// be done later.
+bool URLFetcherCore::WriteBuffer(int num_bytes) {
+ bool write_complete = false;
+ switch (response_destination_) {
+ case STRING:
+ data_.append(buffer_->data(), num_bytes);
+ write_complete = true;
+ break;
+
+ case PERMANENT_FILE:
+ case TEMP_FILE:
+ file_writer_->WriteBuffer(num_bytes);
+ // WriteBuffer() sends a request the file thread.
+ // The write is not done yet.
+ write_complete = false;
+ break;
+
+#if defined(COBALT)
+ case DISCARD:
+ write_complete = true;
+ break;
+#endif
+
+ default:
+ NOTREACHED();
+ }
+ return write_complete;
+}
+
+void URLFetcherCore::ReadResponse() {
+ // Some servers may treat HEAD requests as GET requests. To free up the
+ // network connection as soon as possible, signal that the request has
+ // completed immediately, without trying to read any data back (all we care
+ // about is the response code and headers, which we already have).
+ int bytes_read = 0;
+ if (request_->status().is_success() &&
+ (request_type_ != URLFetcher::HEAD))
+ request_->Read(buffer_, kBufferSize, &bytes_read);
+ OnReadCompleted(request_.get(), bytes_read);
+}
+
+void URLFetcherCore::DisownFile() {
+ file_writer_->DisownFile();
+}
+
+#if defined(COBALT)
+
+void URLFetcherCore::InformDelegateResponseStarted() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ DCHECK(request_);
+
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &URLFetcherCore::InformDelegateResponseStartedInDelegateThread,
+ this));
+}
+
+void URLFetcherCore::InformDelegateResponseStartedInDelegateThread() {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_) {
+ delegate_->OnURLFetchResponseStarted(fetcher_);
+ }
+}
+
+#endif // defined(COBALT)
+
+void URLFetcherCore::InformDelegateUploadProgress() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (request_.get()) {
+ int64 current = request_->GetUploadProgress().position();
+ if (current_upload_bytes_ != current) {
+ current_upload_bytes_ = current;
+ int64 total = -1;
+ if (!is_chunked_upload_)
+ total = static_cast<int64>(upload_content_.size());
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &URLFetcherCore::InformDelegateUploadProgressInDelegateThread,
+ this, current, total));
+ }
+ }
+}
+
+void URLFetcherCore::InformDelegateUploadProgressInDelegateThread(
+ int64 current, int64 total) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_)
+ delegate_->OnURLFetchUploadProgress(fetcher_, current, total);
+}
+
+void URLFetcherCore::InformDelegateDownloadDataIfNecessary(int bytes_read) {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (delegate_ && delegate_->ShouldSendDownloadData() && bytes_read != 0) {
+ if (!download_data_cache_) {
+ download_data_cache_.reset(new std::string);
+ download_data_cache_->reserve(kDownloadCacheSize);
+ }
+ download_data_cache_->resize(download_data_cache_->size() + bytes_read);
+ memcpy(&(*download_data_cache_)[download_data_cache_->size() - bytes_read],
+ buffer_->data(), bytes_read);
+ if (download_data_cache_->size() >= kDownloadCacheSize) {
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(
+ &URLFetcherCore::InformDelegateDownloadDataInDelegateThread, this,
+ base::Passed(&download_data_cache_)));
+ }
+ }
+}
+
+void URLFetcherCore::InformDelegateDownloadData() {
+ DCHECK(network_task_runner_->BelongsToCurrentThread());
+ if (delegate_ && delegate_->ShouldSendDownloadData() &&
+ download_data_cache_) {
+ delegate_task_runner_->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCore::InformDelegateDownloadDataInDelegateThread,
+ this, base::Passed(&download_data_cache_)));
+ }
+}
+
+void URLFetcherCore::InformDelegateDownloadDataInDelegateThread(
+ scoped_ptr<std::string> download_data) {
+ DCHECK(delegate_task_runner_->BelongsToCurrentThread());
+ if (delegate_) {
+ delegate_->OnURLFetchDownloadData(fetcher_, download_data.Pass());
+ }
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_fetcher_core.h b/src/net/url_request/url_fetcher_core.h
new file mode 100644
index 0000000..ff2c174
--- /dev/null
+++ b/src/net/url_request/url_fetcher_core.h
@@ -0,0 +1,434 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_CORE_H_
+#define NET_URL_REQUEST_URL_FETCHER_CORE_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
+#include "base/file_path.h"
+#include "base/lazy_instance.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/platform_file.h"
+#include "base/timer.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/host_port_pair.h"
+#include "net/http/http_request_headers.h"
+#include "net/url_request/url_fetcher.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_status.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+class HttpResponseHeaders;
+class IOBuffer;
+class URLFetcherDelegate;
+class URLRequestContextGetter;
+class URLRequestThrottlerEntryInterface;
+
+class URLFetcherCore
+ : public base::RefCountedThreadSafe<URLFetcherCore>,
+ public URLRequest::Delegate {
+ public:
+ URLFetcherCore(URLFetcher* fetcher,
+ const GURL& original_url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* d);
+
+ // Starts the load. It's important that this not happen in the constructor
+ // because it causes the IO thread to begin AddRef()ing and Release()ing
+ // us. If our caller hasn't had time to fully construct us and take a
+ // reference, the IO thread could interrupt things, run a task, Release()
+ // us, and destroy us, leaving the caller with an already-destroyed object
+ // when construction finishes.
+ void Start();
+
+ // Stops any in-progress load and ensures no callback will happen. It is
+ // safe to call this multiple times.
+ void Stop();
+
+ // URLFetcher-like functions.
+
+ // For POST requests, set |content_type| to the MIME type of the
+ // content and set |content| to the data to upload.
+ void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content);
+ void SetChunkedUpload(const std::string& upload_content_type);
+ // Adds a block of data to be uploaded in a POST body. This can only be
+ // called after Start().
+ void AppendChunkToUpload(const std::string& data, bool is_last_chunk);
+ // |flags| are flags to apply to the load operation--these should be
+ // one or more of the LOAD_* flags defined in net/base/load_flags.h.
+ void SetLoadFlags(int load_flags);
+ int GetLoadFlags() const;
+ void SetReferrer(const std::string& referrer);
+ void SetExtraRequestHeaders(const std::string& extra_request_headers);
+ void AddExtraRequestHeader(const std::string& header_line);
+ void GetExtraRequestHeaders(HttpRequestHeaders* headers) const;
+ void SetRequestContext(URLRequestContextGetter* request_context_getter);
+ // Set the URL that should be consulted for the third-party cookie
+ // blocking policy.
+ void SetFirstPartyForCookies(const GURL& first_party_for_cookies);
+ // Set the key and data callback that is used when setting the user
+ // data on any URLRequest objects this object creates.
+ void SetURLRequestUserData(
+ const void* key,
+ const URLFetcher::CreateDataCallback& create_data_callback);
+ void SetStopOnRedirect(bool stop_on_redirect);
+ void SetAutomaticallyRetryOn5xx(bool retry);
+ void SetMaxRetriesOn5xx(int max_retries);
+ int GetMaxRetriesOn5xx() const;
+ base::TimeDelta GetBackoffDelay() const;
+ void SetAutomaticallyRetryOnNetworkChanges(int max_retries);
+ void SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner);
+ void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner);
+#if defined(COBALT)
+ // Don't save the response.
+ void DiscardResponse();
+#endif
+ HttpResponseHeaders* GetResponseHeaders() const;
+ HostPortPair GetSocketAddress() const;
+ bool WasFetchedViaProxy() const;
+ const GURL& GetOriginalURL() const;
+ const GURL& GetURL() const;
+ const URLRequestStatus& GetStatus() const;
+ int GetResponseCode() const;
+ const ResponseCookies& GetCookies() const;
+ bool FileErrorOccurred(base::PlatformFileError* out_error_code) const;
+ // Reports that the received content was malformed (i.e. failed parsing
+ // or validation). This makes the throttling logic that does exponential
+ // back-off when servers are having problems treat the current request as
+ // a failure. Your call to this method will be ignored if your request is
+ // already considered a failure based on the HTTP response code or response
+ // headers.
+ void ReceivedContentWasMalformed();
+ bool GetResponseAsString(std::string* out_response_string) const;
+ bool GetResponseAsFilePath(bool take_ownership,
+ FilePath* out_response_path);
+
+ // Overridden from URLRequest::Delegate:
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(URLRequest* request,
+ int bytes_read) OVERRIDE;
+ virtual void OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) OVERRIDE;
+
+ URLFetcherDelegate* delegate() const { return delegate_; }
+ static void CancelAll();
+ static int GetNumFetcherCores();
+ static void SetEnableInterceptionForTests(bool enabled);
+ static void SetIgnoreCertificateRequests(bool ignored);
+
+ private:
+ friend class base::RefCountedThreadSafe<URLFetcherCore>;
+
+ // How should the response be stored?
+ enum ResponseDestinationType {
+ STRING, // Default: In a std::string
+ PERMANENT_FILE, // Write to a permanent file.
+ TEMP_FILE, // Write to a temporary file.
+#if defined(COBALT)
+ DISCARD, // Don't store the response.
+#endif
+ };
+
+ class Registry {
+ public:
+ Registry();
+ ~Registry();
+
+ void AddURLFetcherCore(URLFetcherCore* core);
+ void RemoveURLFetcherCore(URLFetcherCore* core);
+
+ void CancelAll();
+
+ int size() const {
+ return fetchers_.size();
+ }
+
+ private:
+ std::set<URLFetcherCore*> fetchers_;
+
+ DISALLOW_COPY_AND_ASSIGN(Registry);
+ };
+
+ // Class FileWriter encapsulates all state involved in writing
+ // response bytes to a file. It is only used if
+ // |URLFetcherCore::response_destination_| == TEMP_FILE ||
+ // |URLFetcherCore::response_destination_| == PERMANENT_FILE. Each
+ // instance of FileWriter is owned by a URLFetcherCore, which
+ // manages its lifetime and never transfers ownership. All file operations
+ // happen on |file_task_runner_|.
+ class FileWriter {
+ public:
+ FileWriter(URLFetcherCore* core,
+ scoped_refptr<base::TaskRunner> file_task_runner);
+ ~FileWriter();
+
+ void CreateFileAtPath(const FilePath& file_path);
+ void CreateTempFile();
+
+ // Record |num_bytes_| response bytes in |core_->buffer_| to the file.
+ void WriteBuffer(int num_bytes);
+
+ // Called when a write has been done. Continues writing if there are
+ // any more bytes to write. Otherwise, initiates a read in core_.
+ void ContinueWrite(base::PlatformFileError error_code, int bytes_written);
+
+ // Drop ownership of the file at |file_path_|.
+ // This class will not delete it or write to it again.
+ void DisownFile();
+
+ // Close the file if it is open.
+ void CloseFileAndCompleteRequest();
+
+ // Close the file if it is open and then delete it.
+ void CloseAndDeleteFile();
+
+ const FilePath& file_path() const { return file_path_; }
+ int64 total_bytes_written() { return total_bytes_written_; }
+ base::PlatformFileError error_code() const { return error_code_; }
+
+ private:
+ // Callback which gets the result of a permanent file creation.
+ void DidCreateFile(const FilePath& file_path,
+ base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle,
+ bool created);
+ // Callback which gets the result of a temporary file creation.
+ void DidCreateTempFile(base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle,
+ const FilePath& file_path);
+ // This method is used to implement DidCreateFile and DidCreateTempFile.
+ void DidCreateFileInternal(const FilePath& file_path,
+ base::PlatformFileError error_code,
+ base::PassPlatformFile file_handle);
+
+ // Callback which gets the result of closing the file.
+ void DidCloseFile(base::PlatformFileError error);
+
+ // Callback which gets the result of closing the file. Deletes the file if
+ // it has been created.
+ void DeleteFile(base::PlatformFileError error_code);
+
+ // The URLFetcherCore which instantiated this class.
+ URLFetcherCore* core_;
+
+ // The last error encountered on a file operation. base::PLATFORM_FILE_OK
+ // if no error occurred.
+ base::PlatformFileError error_code_;
+
+ // Callbacks are created for use with base::FileUtilProxy.
+ base::WeakPtrFactory<URLFetcherCore::FileWriter> weak_factory_;
+
+ // Task runner on which file operations should happen.
+ scoped_refptr<base::TaskRunner> file_task_runner_;
+
+ // Path to the file. This path is empty when there is no file.
+ FilePath file_path_;
+
+ // Handle to the file.
+ base::PlatformFile file_handle_;
+
+ // We always append to the file. Track the total number of bytes
+ // written, so that writes know the offset to give.
+ int64 total_bytes_written_;
+
+ // How many bytes did the last Write() try to write? Needed so
+ // that if not all the bytes get written on a Write(), we can
+ // call Write() again with the rest.
+ int pending_bytes_;
+
+ // When writing, how many bytes from the buffer have been successfully
+ // written so far?
+ int buffer_offset_;
+ };
+
+ virtual ~URLFetcherCore();
+
+ // Wrapper functions that allow us to ensure actions happen on the right
+ // thread.
+ void StartOnIOThread();
+ void StartURLRequest();
+ void StartURLRequestWhenAppropriate();
+ void CancelURLRequest();
+ void OnCompletedURLRequest(base::TimeDelta backoff_delay);
+ void InformDelegateFetchIsComplete();
+ void NotifyMalformedContent();
+ void RetryOrCompleteUrlFetch();
+
+ // Deletes the request, removes it from the registry, and removes the
+ // destruction observer.
+ void ReleaseRequest();
+
+ // Returns the max value of exponential back-off release time for
+ // |original_url_| and |url_|.
+ base::TimeTicks GetBackoffReleaseTime();
+
+ void CompleteAddingUploadDataChunk(const std::string& data,
+ bool is_last_chunk);
+
+ // Store the response bytes in |buffer_| in the container indicated by
+ // |response_destination_|. Return true if the write has been
+ // done, and another read can overwrite |buffer_|. If this function
+ // returns false, it will post a task that will read more bytes once the
+ // write is complete.
+ bool WriteBuffer(int num_bytes);
+
+ // Read response bytes from the request.
+ void ReadResponse();
+
+ // Drop ownership of any file managed by |file_path_|.
+ void DisownFile();
+
+ // Notify Delegate about the progress of upload/download.
+#if defined(COBALT)
+ void InformDelegateResponseStarted();
+ void InformDelegateResponseStartedInDelegateThread();
+#endif // defined(COBALT)
+ void InformDelegateUploadProgress();
+ void InformDelegateUploadProgressInDelegateThread(int64 current, int64 total);
+ void InformDelegateDownloadDataIfNecessary(int bytes_read);
+ void InformDelegateDownloadData();
+ void InformDelegateDownloadDataInDelegateThread(
+ scoped_ptr<std::string> download_data);
+
+ URLFetcher* fetcher_; // Corresponding fetcher object
+ GURL original_url_; // The URL we were asked to fetch
+ GURL url_; // The URL we eventually wound up at
+ URLFetcher::RequestType request_type_; // What type of request is this?
+ URLRequestStatus status_; // Status of the request
+ URLFetcherDelegate* delegate_; // Object to notify on completion
+ scoped_refptr<base::SingleThreadTaskRunner> delegate_task_runner_;
+ // Task runner for the creating thread.
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+ // Task runner for file access.
+ scoped_refptr<base::TaskRunner> file_task_runner_;
+ // Task runner for the thread
+ // on which file access happens.
+ scoped_ptr<URLRequest> request_; // The actual request this wraps
+ int load_flags_; // Flags for the load operation
+ int response_code_; // HTTP status code for the request
+ std::string data_; // Results of the request, when we are
+ // storing the response as a string.
+ scoped_refptr<IOBuffer> buffer_; // Read buffer
+ // Caches the data to be sent to the request thread to minimize task posting.
+ scoped_ptr<std::string> download_data_cache_;
+ scoped_refptr<URLRequestContextGetter> request_context_getter_;
+ // Cookie/cache info for the request
+ GURL first_party_for_cookies_; // The first party URL for the request
+ // The user data to add to each newly-created URLRequest.
+ const void* url_request_data_key_;
+ URLFetcher::CreateDataCallback url_request_create_data_callback_;
+ ResponseCookies cookies_; // Response cookies
+ HttpRequestHeaders extra_request_headers_;
+ scoped_refptr<HttpResponseHeaders> response_headers_;
+ bool was_fetched_via_proxy_;
+ HostPortPair socket_address_;
+
+ std::string upload_content_; // HTTP POST payload
+ std::string upload_content_type_; // MIME type of POST payload
+ std::string referrer_; // HTTP Referer header value
+ bool is_chunked_upload_; // True if using chunked transfer encoding
+
+ // Used to determine how long to wait before making a request or doing a
+ // retry.
+ //
+ // Both of them can only be accessed on the IO thread.
+ //
+ // We need not only the throttler entry for |original_URL|, but also
+ // the one for |url|. For example, consider the case that URL A
+ // redirects to URL B, for which the server returns a 500
+ // response. In this case, the exponential back-off release time of
+ // URL A won't increase. If we retry without considering the
+ // back-off constraint of URL B, we may send out too many requests
+ // for URL A in a short period of time.
+ //
+ // Both of these will be NULL if
+ // URLRequestContext::throttler_manager() is NULL.
+ scoped_refptr<URLRequestThrottlerEntryInterface>
+ original_url_throttler_entry_;
+ scoped_refptr<URLRequestThrottlerEntryInterface> url_throttler_entry_;
+
+ // True if the URLFetcher has been cancelled.
+ bool was_cancelled_;
+
+ // If writing results to a file, |file_writer_| will manage creation,
+ // writing, and destruction of that file.
+ scoped_ptr<FileWriter> file_writer_;
+
+ // Where should responses be saved?
+ ResponseDestinationType response_destination_;
+
+ // Path to the file where the response is written.
+ FilePath response_destination_file_path_;
+
+ // By default any server-initiated redirects are automatically followed. If
+ // this flag is set to true, however, a redirect will halt the fetch and call
+ // back to to the delegate immediately.
+ bool stop_on_redirect_;
+ // True when we're actually stopped due to a redirect halted by the above. We
+ // use this to ensure that |url_| is set to the redirect destination rather
+ // than the originally-fetched URL.
+ bool stopped_on_redirect_;
+
+ // If |automatically_retry_on_5xx_| is false, 5xx responses will be
+ // propagated to the observer, if it is true URLFetcher will automatically
+ // re-execute the request, after the back-off delay has expired.
+ // true by default.
+ bool automatically_retry_on_5xx_;
+ // |num_retries_on_5xx_| indicates how many times we've failed to successfully
+ // fetch this URL due to 5xx responses. Once this value exceeds the maximum
+ // number of retries specified by the owner URLFetcher instance,
+ // we'll give up.
+ int num_retries_on_5xx_;
+ // Maximum retries allowed when 5xx responses are received.
+ int max_retries_on_5xx_;
+ // Back-off time delay. 0 by default.
+ base::TimeDelta backoff_delay_;
+
+ // The number of retries that have been attempted due to ERR_NETWORK_CHANGED.
+ int num_retries_on_network_changes_;
+ // Maximum retries allowed when the request fails with ERR_NETWORK_CHANGED.
+ // 0 by default.
+ int max_retries_on_network_changes_;
+
+ // Timer to poll the progress of uploading for POST and PUT requests.
+ // When crbug.com/119629 is fixed, scoped_ptr is not necessary here.
+ scoped_ptr<base::RepeatingTimer<URLFetcherCore> >
+ upload_progress_checker_timer_;
+ // Number of bytes sent so far.
+ int64 current_upload_bytes_;
+ // Number of bytes received so far.
+ int64 current_response_bytes_;
+ // Total expected bytes to receive (-1 if it cannot be determined).
+ int64 total_response_bytes_;
+
+ // TODO(willchan): Get rid of this after debugging crbug.com/90971.
+ base::debug::StackTrace stack_trace_;
+
+ static base::LazyInstance<Registry> g_registry;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherCore);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_CORE_H_
diff --git a/src/net/url_request/url_fetcher_delegate.cc b/src/net/url_request/url_fetcher_delegate.cc
new file mode 100644
index 0000000..7759157
--- /dev/null
+++ b/src/net/url_request/url_fetcher_delegate.cc
@@ -0,0 +1,26 @@
+// Copyright (c) 2012 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 "net/url_request/url_fetcher_delegate.h"
+
+namespace net {
+
+#if defined(COBALT)
+void URLFetcherDelegate::OnURLFetchResponseStarted(
+ const URLFetcher* source) {}
+#endif // defined(COBALT)
+
+void URLFetcherDelegate::OnURLFetchDownloadData(
+ const URLFetcher* source, scoped_ptr<std::string> download_data) {}
+
+void URLFetcherDelegate::OnURLFetchUploadProgress(
+ const URLFetcher* source, int64 current, int64 total) {}
+
+bool URLFetcherDelegate::ShouldSendDownloadData() {
+ return false;
+}
+
+URLFetcherDelegate::~URLFetcherDelegate() {}
+
+} // namespace net
diff --git a/src/net/url_request/url_fetcher_delegate.h b/src/net/url_request/url_fetcher_delegate.h
new file mode 100644
index 0000000..0e643f8
--- /dev/null
+++ b/src/net/url_request/url_fetcher_delegate.h
@@ -0,0 +1,55 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_DELEGATE_H_
+#define NET_URL_REQUEST_URL_FETCHER_DELEGATE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class URLFetcher;
+
+// A delegate interface for users of URLFetcher.
+class NET_EXPORT URLFetcherDelegate {
+ public:
+#if defined(COBALT)
+ // This will be called when the response code and headers have been received.
+ virtual void OnURLFetchResponseStarted(const URLFetcher* source);
+#endif // defined(COBALT)
+
+ // This will be called when the URL has been fetched, successfully or not.
+ // Use accessor methods on |source| to get the results.
+ virtual void OnURLFetchComplete(const URLFetcher* source) = 0;
+
+ // This will be called when some part of the response is read.
+ // |download_data| contains the current bytes received since the last call.
+ // This will be called after ShouldSendDownloadData() and only if the latter
+ // returns true.
+ virtual void OnURLFetchDownloadData(const URLFetcher* source,
+ scoped_ptr<std::string> download_data);
+
+ // This indicates if OnURLFetchDownloadData should be called.
+ // This will be called before OnURLFetchDownloadData is called, and only if
+ // this returns true.
+ // Default implementation is false.
+ virtual bool ShouldSendDownloadData();
+
+ // This will be called when uploading of POST or PUT requests proceeded.
+ // |current| denotes the number of bytes sent so far, and |total| is the
+ // total size of uploading data (or -1 if chunked upload is enabled).
+ virtual void OnURLFetchUploadProgress(const URLFetcher* source,
+ int64 current, int64 total);
+
+ protected:
+ virtual ~URLFetcherDelegate();
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_DELEGATE_H_
diff --git a/src/net/url_request/url_fetcher_factory.h b/src/net/url_request/url_fetcher_factory.h
new file mode 100644
index 0000000..0c5a5eb
--- /dev/null
+++ b/src/net/url_request/url_fetcher_factory.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_FACTORY_H_
+#define NET_URL_REQUEST_URL_FETCHER_FACTORY_H_
+
+#include "net/url_request/url_fetcher.h"
+
+namespace net {
+class URLFetcherDelegate;
+
+// URLFetcher::Create uses the currently registered Factory to create the
+// URLFetcher. Factory is intended for testing.
+class URLFetcherFactory {
+ public:
+ virtual URLFetcher* CreateURLFetcher(
+ int id,
+ const GURL& url,
+ URLFetcher::RequestType request_type,
+ URLFetcherDelegate* delegate) = 0;
+
+ protected:
+ virtual ~URLFetcherFactory() {}
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_FACTORY_H_
diff --git a/src/net/url_request/url_fetcher_impl.cc b/src/net/url_request/url_fetcher_impl.cc
new file mode 100644
index 0000000..527a195
--- /dev/null
+++ b/src/net/url_request/url_fetcher_impl.cc
@@ -0,0 +1,216 @@
+// Copyright (c) 2012 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 "net/url_request/url_fetcher_impl.h"
+
+#include "base/bind.h"
+#include "base/message_loop_proxy.h"
+#include "net/url_request/url_fetcher_core.h"
+#include "net/url_request/url_fetcher_factory.h"
+
+namespace net {
+
+static URLFetcherFactory* g_factory = NULL;
+
+URLFetcherImpl::URLFetcherImpl(const GURL& url,
+ RequestType request_type,
+ URLFetcherDelegate* d)
+ : ALLOW_THIS_IN_INITIALIZER_LIST(
+ core_(new URLFetcherCore(this, url, request_type, d))) {
+}
+
+URLFetcherImpl::~URLFetcherImpl() {
+ core_->Stop();
+}
+
+void URLFetcherImpl::SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) {
+ core_->SetUploadData(upload_content_type, upload_content);
+}
+
+void URLFetcherImpl::SetChunkedUpload(const std::string& content_type) {
+ core_->SetChunkedUpload(content_type);
+}
+
+void URLFetcherImpl::AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) {
+ DCHECK(data.length());
+ core_->AppendChunkToUpload(data, is_last_chunk);
+}
+
+void URLFetcherImpl::SetReferrer(const std::string& referrer) {
+ core_->SetReferrer(referrer);
+}
+
+void URLFetcherImpl::SetLoadFlags(int load_flags) {
+ core_->SetLoadFlags(load_flags);
+}
+
+int URLFetcherImpl::GetLoadFlags() const {
+ return core_->GetLoadFlags();
+}
+
+void URLFetcherImpl::SetExtraRequestHeaders(
+ const std::string& extra_request_headers) {
+ core_->SetExtraRequestHeaders(extra_request_headers);
+}
+
+void URLFetcherImpl::AddExtraRequestHeader(const std::string& header_line) {
+ core_->AddExtraRequestHeader(header_line);
+}
+
+void URLFetcherImpl::GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const {
+ core_->GetExtraRequestHeaders(headers);
+}
+
+void URLFetcherImpl::SetRequestContext(
+ URLRequestContextGetter* request_context_getter) {
+ core_->SetRequestContext(request_context_getter);
+}
+
+void URLFetcherImpl::SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) {
+ core_->SetFirstPartyForCookies(first_party_for_cookies);
+}
+
+void URLFetcherImpl::SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) {
+ core_->SetURLRequestUserData(key, create_data_callback);
+}
+
+void URLFetcherImpl::SetStopOnRedirect(bool stop_on_redirect) {
+ core_->SetStopOnRedirect(stop_on_redirect);
+}
+
+void URLFetcherImpl::SetAutomaticallyRetryOn5xx(bool retry) {
+ core_->SetAutomaticallyRetryOn5xx(retry);
+}
+
+void URLFetcherImpl::SetMaxRetriesOn5xx(int max_retries) {
+ core_->SetMaxRetriesOn5xx(max_retries);
+}
+
+int URLFetcherImpl::GetMaxRetriesOn5xx() const {
+ return core_->GetMaxRetriesOn5xx();
+}
+
+
+base::TimeDelta URLFetcherImpl::GetBackoffDelay() const {
+ return core_->GetBackoffDelay();
+}
+
+void URLFetcherImpl::SetAutomaticallyRetryOnNetworkChanges(int max_retries) {
+ core_->SetAutomaticallyRetryOnNetworkChanges(max_retries);
+}
+
+void URLFetcherImpl::SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ core_->SaveResponseToFileAtPath(file_path, file_task_runner);
+}
+
+void URLFetcherImpl::SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) {
+ core_->SaveResponseToTemporaryFile(file_task_runner);
+}
+
+#if defined(COBALT)
+void URLFetcherImpl::DiscardResponse() {
+ core_->DiscardResponse();
+}
+#endif
+
+HttpResponseHeaders* URLFetcherImpl::GetResponseHeaders() const {
+ return core_->GetResponseHeaders();
+}
+
+HostPortPair URLFetcherImpl::GetSocketAddress() const {
+ return core_->GetSocketAddress();
+}
+
+bool URLFetcherImpl::WasFetchedViaProxy() const {
+ return core_->WasFetchedViaProxy();
+}
+
+void URLFetcherImpl::Start() {
+ core_->Start();
+}
+
+const GURL& URLFetcherImpl::GetOriginalURL() const {
+ return core_->GetOriginalURL();
+}
+
+const GURL& URLFetcherImpl::GetURL() const {
+ return core_->GetURL();
+}
+
+const URLRequestStatus& URLFetcherImpl::GetStatus() const {
+ return core_->GetStatus();
+}
+
+int URLFetcherImpl::GetResponseCode() const {
+ return core_->GetResponseCode();
+}
+
+const ResponseCookies& URLFetcherImpl::GetCookies() const {
+ return core_->GetCookies();
+}
+
+bool URLFetcherImpl::FileErrorOccurred(
+ base::PlatformFileError* out_error_code) const {
+ return core_->FileErrorOccurred(out_error_code);
+}
+
+void URLFetcherImpl::ReceivedContentWasMalformed() {
+ core_->ReceivedContentWasMalformed();
+}
+
+bool URLFetcherImpl::GetResponseAsString(
+ std::string* out_response_string) const {
+ return core_->GetResponseAsString(out_response_string);
+}
+
+bool URLFetcherImpl::GetResponseAsFilePath(
+ bool take_ownership,
+ FilePath* out_response_path) const {
+ return core_->GetResponseAsFilePath(take_ownership, out_response_path);
+}
+
+// static
+void URLFetcherImpl::CancelAll() {
+ URLFetcherCore::CancelAll();
+}
+
+// static
+void URLFetcherImpl::SetEnableInterceptionForTests(bool enabled) {
+ URLFetcherCore::SetEnableInterceptionForTests(enabled);
+}
+
+// static
+void URLFetcherImpl::SetIgnoreCertificateRequests(bool ignored) {
+ URLFetcherCore::SetIgnoreCertificateRequests(ignored);
+}
+
+// static
+int URLFetcherImpl::GetNumFetcherCores() {
+ return URLFetcherCore::GetNumFetcherCores();
+}
+
+URLFetcherDelegate* URLFetcherImpl::delegate() const {
+ return core_->delegate();
+}
+
+// static
+URLFetcherFactory* URLFetcherImpl::factory() {
+ return g_factory;
+}
+
+// static
+void URLFetcherImpl::set_factory(URLFetcherFactory* factory) {
+ g_factory = factory;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_fetcher_impl.h b/src/net/url_request/url_fetcher_impl.h
new file mode 100644
index 0000000..5961b80
--- /dev/null
+++ b/src/net/url_request/url_fetcher_impl.h
@@ -0,0 +1,126 @@
+// Copyright (c) 2012 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.
+
+// This file contains URLFetcher, a wrapper around URLRequest that handles
+// low-level details like thread safety, ref counting, and incremental buffer
+// reading. This is useful for callers who simply want to get the data from a
+// URL and don't care about all the nitty-gritty details.
+//
+// NOTE(willchan): Only one "IO" thread is supported for URLFetcher. This is a
+// temporary situation. We will work on allowing support for multiple "io"
+// threads per process.
+
+#ifndef NET_URL_REQUEST_URL_FETCHER_IMPL_H_
+#define NET_URL_REQUEST_URL_FETCHER_IMPL_H_
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_fetcher.h"
+
+namespace net {
+class URLFetcherCore;
+class URLFetcherDelegate;
+class URLFetcherFactory;
+
+class NET_EXPORT_PRIVATE URLFetcherImpl : public URLFetcher {
+ public:
+ // |url| is the URL to send the request to.
+ // |request_type| is the type of request to make.
+ // |d| the object that will receive the callback on fetch completion.
+ URLFetcherImpl(const GURL& url,
+ RequestType request_type,
+ URLFetcherDelegate* d);
+ virtual ~URLFetcherImpl();
+
+ // URLFetcher implementation:
+ virtual void SetUploadData(const std::string& upload_content_type,
+ const std::string& upload_content) OVERRIDE;
+ virtual void SetChunkedUpload(
+ const std::string& upload_content_type) OVERRIDE;
+ virtual void AppendChunkToUpload(const std::string& data,
+ bool is_last_chunk) OVERRIDE;
+ virtual void SetLoadFlags(int load_flags) OVERRIDE;
+ virtual int GetLoadFlags() const OVERRIDE;
+ virtual void SetReferrer(const std::string& referrer) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const std::string& extra_request_headers) OVERRIDE;
+ virtual void AddExtraRequestHeader(const std::string& header_line) OVERRIDE;
+ virtual void GetExtraRequestHeaders(
+ HttpRequestHeaders* headers) const OVERRIDE;
+ virtual void SetRequestContext(
+ URLRequestContextGetter* request_context_getter) OVERRIDE;
+ virtual void SetFirstPartyForCookies(
+ const GURL& first_party_for_cookies) OVERRIDE;
+ virtual void SetURLRequestUserData(
+ const void* key,
+ const CreateDataCallback& create_data_callback) OVERRIDE;
+ virtual void SetStopOnRedirect(bool stop_on_redirect) OVERRIDE;
+ virtual void SetAutomaticallyRetryOn5xx(bool retry) OVERRIDE;
+ virtual void SetMaxRetriesOn5xx(int max_retries) OVERRIDE;
+ virtual int GetMaxRetriesOn5xx() const OVERRIDE;
+ virtual base::TimeDelta GetBackoffDelay() const OVERRIDE;
+ virtual void SetAutomaticallyRetryOnNetworkChanges(int max_retries) OVERRIDE;
+ virtual void SaveResponseToFileAtPath(
+ const FilePath& file_path,
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+ virtual void SaveResponseToTemporaryFile(
+ scoped_refptr<base::TaskRunner> file_task_runner) OVERRIDE;
+#if defined(COBALT)
+ virtual void DiscardResponse() OVERRIDE;
+#endif
+ virtual HttpResponseHeaders* GetResponseHeaders() const OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual bool WasFetchedViaProxy() const OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual const GURL& GetOriginalURL() const OVERRIDE;
+ virtual const GURL& GetURL() const OVERRIDE;
+ virtual const URLRequestStatus& GetStatus() const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual const ResponseCookies& GetCookies() const OVERRIDE;
+ virtual bool FileErrorOccurred(
+ base::PlatformFileError* out_error_code) const OVERRIDE;
+ virtual void ReceivedContentWasMalformed() OVERRIDE;
+ virtual bool GetResponseAsString(
+ std::string* out_response_string) const OVERRIDE;
+ virtual bool GetResponseAsFilePath(
+ bool take_ownership,
+ FilePath* out_response_path) const OVERRIDE;
+
+ static void CancelAll();
+
+ static void SetEnableInterceptionForTests(bool enabled);
+ static void SetIgnoreCertificateRequests(bool ignored);
+
+ // TODO(akalin): Make these private again once URLFetcher::Create()
+ // is in net/.
+
+ static URLFetcherFactory* factory();
+
+ // Sets the factory used by the static method Create to create a URLFetcher.
+ // URLFetcher does not take ownership of |factory|. A value of NULL results
+ // in a URLFetcher being created directly.
+ //
+ // NOTE: for safety, this should only be used through ScopedURLFetcherFactory!
+ static void set_factory(URLFetcherFactory* factory);
+
+ protected:
+ // Returns the delegate.
+ URLFetcherDelegate* delegate() const;
+
+ private:
+ friend class URLFetcherTest;
+
+ // Only used by URLFetcherTest, returns the number of URLFetcher::Core objects
+ // actively running.
+ static int GetNumFetcherCores();
+
+ const scoped_refptr<URLFetcherCore> core_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLFetcherImpl);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_FETCHER_IMPL_H_
diff --git a/src/net/url_request/url_fetcher_impl_unittest.cc b/src/net/url_request/url_fetcher_impl_unittest.cc
new file mode 100644
index 0000000..53723e6
--- /dev/null
+++ b/src/net/url_request/url_fetcher_impl_unittest.cc
@@ -0,0 +1,1449 @@
+// Copyright (c) 2012 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 "net/url_request/url_fetcher_impl.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/message_loop_proxy.h"
+#include "base/stringprintf.h"
+#include "base/synchronization/waitable_event.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "crypto/nss_util.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/network_change_notifier.h"
+#include "net/http/http_response_headers.h"
+#include "net/test/test_server.h"
+#include "net/url_request/url_fetcher_delegate.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/url_request/url_request_throttler_manager.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(USE_NSS) || defined(OS_IOS)
+#include "net/ocsp/nss_ocsp.h"
+#endif
+
+namespace net {
+
+using base::Time;
+using base::TimeDelta;
+
+// TODO(eroman): Add a regression test for http://crbug.com/40505.
+
+namespace {
+
+// TODO(akalin): Move all the test data to somewhere under net/.
+const FilePath::CharType kDocRoot[] = FILE_PATH_LITERAL("chrome/test/data");
+const char kTestServerFilePrefix[] = "files/";
+
+class ThrottlingTestURLRequestContext : public TestURLRequestContext {
+ public:
+ ThrottlingTestURLRequestContext() : TestURLRequestContext(true) {
+ set_throttler_manager(&throttler_manager_);
+ Init();
+ DCHECK(throttler_manager() != NULL);
+ }
+
+ private:
+ URLRequestThrottlerManager throttler_manager_;
+};
+
+class ThrottlingTestURLRequestContextGetter
+ : public TestURLRequestContextGetter {
+ public:
+ ThrottlingTestURLRequestContextGetter(
+ base::MessageLoopProxy* io_message_loop_proxy,
+ TestURLRequestContext* request_context)
+ : TestURLRequestContextGetter(io_message_loop_proxy),
+ context_(request_context) {
+ }
+
+ // TestURLRequestContextGetter:
+ virtual TestURLRequestContext* GetURLRequestContext() OVERRIDE {
+ return context_;
+ }
+
+ protected:
+ virtual ~ThrottlingTestURLRequestContextGetter() {}
+
+ TestURLRequestContext* const context_;
+};
+
+} // namespace
+
+class URLFetcherTest : public testing::Test,
+ public URLFetcherDelegate {
+ public:
+ URLFetcherTest()
+ : fetcher_(NULL),
+ context_(NULL) {
+ }
+
+ static int GetNumFetcherCores() {
+ return URLFetcherImpl::GetNumFetcherCores();
+ }
+
+ // Creates a URLFetcher, using the program's main thread to do IO.
+ virtual void CreateFetcher(const GURL& url);
+
+ // URLFetcherDelegate:
+ // Subclasses that override this should either call this function or
+ // CleanupAfterFetchComplete() at the end of their processing, depending on
+ // whether they want to check for a non-empty HTTP 200 response or not.
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ // Deletes |fetcher| and terminates the message loop.
+ void CleanupAfterFetchComplete();
+
+ scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy() {
+ return io_message_loop_proxy_;
+ }
+
+ TestURLRequestContext* request_context() {
+ return context_.get();
+ }
+
+ protected:
+ // testing::Test:
+ virtual void SetUp() OVERRIDE {
+ testing::Test::SetUp();
+
+ context_.reset(new ThrottlingTestURLRequestContext());
+ io_message_loop_proxy_ = base::MessageLoopProxy::current();
+
+#if defined(USE_NSS) || defined(OS_IOS)
+ crypto::EnsureNSSInit();
+ EnsureNSSHttpIOInit();
+#endif
+ }
+
+ virtual void TearDown() OVERRIDE {
+#if defined(USE_NSS) || defined(OS_IOS)
+ ShutdownNSSHttpIO();
+#endif
+ }
+
+ // URLFetcher is designed to run on the main UI thread, but in our tests
+ // we assume that the current thread is the IO thread where the URLFetcher
+ // dispatches its requests to. When we wish to simulate being used from
+ // a UI thread, we dispatch a worker thread to do so.
+ scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_;
+
+ URLFetcherImpl* fetcher_;
+ scoped_ptr<TestURLRequestContext> context_;
+};
+
+// A test fixture that uses a MockHostResolver, so that name resolutions can
+// be manipulated by the tests to keep connections in the resolving state.
+class URLFetcherMockDnsTest : public URLFetcherTest {
+ public:
+ // testing::Test:
+ virtual void SetUp() OVERRIDE;
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ protected:
+ GURL test_url_;
+ scoped_ptr<TestServer> test_server_;
+ MockHostResolver resolver_;
+ scoped_ptr<URLFetcher> completed_fetcher_;
+};
+
+void URLFetcherTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->Start();
+}
+
+void URLFetcherTest::OnURLFetchComplete(const URLFetcher* source) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(200, source->GetResponseCode()); // HTTP OK
+
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherTest::CleanupAfterFetchComplete() {
+ delete fetcher_; // Have to delete this here and not in the destructor,
+ // because the destructor won't necessarily run on the
+ // same thread that CreateFetcher() did.
+
+ io_message_loop_proxy()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ // If the current message loop is not the IO loop, it will be shut down when
+ // the main loop returns and this thread subsequently goes out of scope.
+}
+
+void URLFetcherMockDnsTest::SetUp() {
+ URLFetcherTest::SetUp();
+
+ resolver_.set_ondemand_mode(true);
+ resolver_.rules()->AddRule("example.com", "127.0.0.1");
+
+ context_.reset(new TestURLRequestContext(true));
+ context_->set_host_resolver(&resolver_);
+ context_->Init();
+
+ test_server_.reset(new TestServer(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot)));
+ ASSERT_TRUE(test_server_->Start());
+
+ // test_server_.GetURL() returns a URL with 127.0.0.1 (kLocalhost), that is
+ // immediately resolved by the MockHostResolver. Use a hostname instead to
+ // trigger an async resolve.
+ test_url_ = GURL(
+ base::StringPrintf("http://example.com:%d/defaultresponse",
+ test_server_->host_port_pair().port()));
+ ASSERT_TRUE(test_url_.is_valid());
+}
+
+void URLFetcherMockDnsTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+}
+
+void URLFetcherMockDnsTest::OnURLFetchComplete(const URLFetcher* source) {
+ io_message_loop_proxy()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ ASSERT_EQ(fetcher_, source);
+ EXPECT_EQ(test_url_, source->GetOriginalURL());
+ completed_fetcher_.reset(fetcher_);
+}
+
+namespace {
+
+// Version of URLFetcherTest that does a POST instead
+class URLFetcherPostTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+};
+
+// Version of URLFetcherTest that does a POST instead with empty upload body
+class URLFetcherEmptyPostTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+};
+
+// Version of URLFetcherTest that tests download progress reports.
+class URLFetcherDownloadProgressTest : public URLFetcherTest {
+ public:
+ URLFetcherDownloadProgressTest()
+ : previous_progress_(0),
+ expected_total_(0) {
+ }
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchDownloadProgress(const URLFetcher* source,
+ int64 current,
+ int64 total) OVERRIDE;
+
+ protected:
+ // Download progress returned by the previous callback.
+ int64 previous_progress_;
+ // Size of the file being downloaded, known in advance (provided by each test
+ // case).
+ int64 expected_total_;
+};
+
+// Version of URLFetcherTest that tests progress reports at cancellation.
+class URLFetcherDownloadProgressCancelTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ virtual void OnURLFetchDownloadProgress(const URLFetcher* source,
+ int64 current,
+ int64 total) OVERRIDE;
+ protected:
+ bool cancelled_;
+};
+
+// Version of URLFetcherTest that tests upload progress reports.
+class URLFetcherUploadProgressTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchUploadProgress(const URLFetcher* source,
+ int64 current,
+ int64 total) OVERRIDE;
+ protected:
+ int64 previous_progress_;
+ std::string chunk_;
+ int64 number_of_chunks_added_;
+};
+
+// Version of URLFetcherTest that tests headers.
+class URLFetcherHeadersTest : public URLFetcherTest {
+ public:
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+};
+
+// Version of URLFetcherTest that tests SocketAddress.
+class URLFetcherSocketAddressTest : public URLFetcherTest {
+ public:
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ protected:
+ std::string expected_host_;
+ uint16 expected_port_;
+};
+
+// Version of URLFetcherTest that tests stopping on a redirect.
+class URLFetcherStopOnRedirectTest : public URLFetcherTest {
+ public:
+ URLFetcherStopOnRedirectTest();
+ virtual ~URLFetcherStopOnRedirectTest();
+
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ protected:
+ // The URL we should be redirected to.
+ static const char* kRedirectTarget;
+
+ bool callback_called_; // Set to true in OnURLFetchComplete().
+};
+
+// Version of URLFetcherTest that tests overload protection.
+class URLFetcherProtectTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ private:
+ Time start_time_;
+};
+
+// Version of URLFetcherTest that tests overload protection, when responses
+// passed through.
+class URLFetcherProtectTestPassedThrough : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ private:
+ Time start_time_;
+};
+
+// Version of URLFetcherTest that tests bad HTTPS requests.
+class URLFetcherBadHTTPSTest : public URLFetcherTest {
+ public:
+ URLFetcherBadHTTPSTest();
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ private:
+ FilePath cert_dir_;
+};
+
+// Version of URLFetcherTest that tests request cancellation on shutdown.
+class URLFetcherCancelTest : public URLFetcherTest {
+ public:
+ // URLFetcherTest:
+ virtual void CreateFetcher(const GURL& url) OVERRIDE;
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ void CancelRequest();
+};
+
+// Version of TestURLRequestContext that posts a Quit task to the IO
+// thread once it is deleted.
+class CancelTestURLRequestContext : public ThrottlingTestURLRequestContext {
+ public:
+ explicit CancelTestURLRequestContext() {
+ }
+
+ private:
+ virtual ~CancelTestURLRequestContext() {
+ // The d'tor should execute in the IO thread. Post the quit task to the
+ // current thread.
+ MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ }
+};
+
+class CancelTestURLRequestContextGetter
+ : public TestURLRequestContextGetter {
+ public:
+ CancelTestURLRequestContextGetter(
+ base::MessageLoopProxy* io_message_loop_proxy,
+ const GURL& throttle_for_url)
+ : TestURLRequestContextGetter(io_message_loop_proxy),
+ io_message_loop_proxy_(io_message_loop_proxy),
+ context_created_(false, false),
+ throttle_for_url_(throttle_for_url) {
+ }
+
+ // TestURLRequestContextGetter:
+ virtual TestURLRequestContext* GetURLRequestContext() OVERRIDE {
+ if (!context_.get()) {
+ context_.reset(new CancelTestURLRequestContext());
+ DCHECK(context_->throttler_manager());
+
+ // Registers an entry for test url. The backoff time is calculated by:
+ // new_backoff = 2.0 * old_backoff + 0
+ // The initial backoff is 2 seconds and maximum backoff is 4 seconds.
+ // Maximum retries allowed is set to 2.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(
+ context_->throttler_manager(),
+ "", 200, 3, 2000, 2.0, 0.0, 4000));
+ context_->throttler_manager()->OverrideEntryForTests(
+ throttle_for_url_, entry);
+
+ context_created_.Signal();
+ }
+ return context_.get();
+ }
+
+ virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const {
+ return io_message_loop_proxy_;
+ }
+
+ void WaitForContextCreation() {
+ context_created_.Wait();
+ }
+
+ protected:
+ virtual ~CancelTestURLRequestContextGetter() {}
+
+ private:
+ scoped_ptr<TestURLRequestContext> context_;
+ scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_;
+ base::WaitableEvent context_created_;
+ GURL throttle_for_url_;
+};
+
+// Version of URLFetcherTest that tests retying the same request twice.
+class URLFetcherMultipleAttemptTest : public URLFetcherTest {
+ public:
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+ private:
+ std::string data_;
+};
+
+class URLFetcherFileTest : public URLFetcherTest {
+ public:
+ URLFetcherFileTest() : take_ownership_of_file_(false),
+ expected_file_error_(base::PLATFORM_FILE_OK) {}
+
+ void CreateFetcherForFile(const GURL& url, const FilePath& file_path);
+ void CreateFetcherForTempFile(const GURL& url);
+
+ // URLFetcherDelegate:
+ virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE;
+
+ protected:
+ FilePath expected_file_;
+ FilePath file_path_;
+
+ // Set by the test. Used in OnURLFetchComplete() to decide if
+ // the URLFetcher should own the temp file, so that we can test
+ // disowning prevents the file from being deleted.
+ bool take_ownership_of_file_;
+
+ // Expected file error code for the test.
+ // PLATFORM_FILE_OK when expecting success.
+ base::PlatformFileError expected_file_error_;
+};
+
+void URLFetcherPostTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->SetUploadData("application/x-www-form-urlencoded",
+ "bobsyeruncle");
+ fetcher_->Start();
+}
+
+void URLFetcherPostTest::OnURLFetchComplete(const URLFetcher* source) {
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_EQ(std::string("bobsyeruncle"), data);
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+void URLFetcherEmptyPostTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new TestURLRequestContextGetter(
+ io_message_loop_proxy()));
+ fetcher_->SetUploadData("text/plain", "");
+ fetcher_->Start();
+}
+
+void URLFetcherEmptyPostTest::OnURLFetchComplete(const URLFetcher* source) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(200, source->GetResponseCode()); // HTTP OK
+
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_TRUE(data.empty());
+
+ CleanupAfterFetchComplete();
+ // Do not call the super class method URLFetcherTest::OnURLFetchComplete,
+ // since it expects a non-empty response.
+}
+
+void URLFetcherDownloadProgressTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->Start();
+}
+
+void URLFetcherDownloadProgressTest::OnURLFetchDownloadProgress(
+ const URLFetcher* source, int64 progress, int64 total) {
+ // Increasing between 0 and total.
+ EXPECT_LE(0, progress);
+ EXPECT_GE(total, progress);
+ EXPECT_LE(previous_progress_, progress);
+ EXPECT_EQ(expected_total_, total);
+ previous_progress_ = progress;
+}
+
+void URLFetcherDownloadProgressCancelTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ cancelled_ = false;
+ fetcher_->Start();
+}
+
+void URLFetcherDownloadProgressCancelTest::OnURLFetchDownloadProgress(
+ const URLFetcher* source, int64 current, int64 total) {
+ EXPECT_FALSE(cancelled_);
+ if (!cancelled_) {
+ cancelled_ = true;
+ CleanupAfterFetchComplete();
+ }
+}
+
+void URLFetcherDownloadProgressCancelTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ // Should have been cancelled.
+ ADD_FAILURE();
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherUploadProgressTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::POST, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ previous_progress_ = 0;
+ // Large enough data to require more than one read from UploadDataStream.
+ chunk_.assign(1<<16, 'a');
+ // Use chunked upload to wait for a timer event of progress notification.
+ fetcher_->SetChunkedUpload("application/x-www-form-urlencoded");
+ fetcher_->Start();
+ number_of_chunks_added_ = 1;
+ fetcher_->AppendChunkToUpload(chunk_, false);
+}
+
+void URLFetcherUploadProgressTest::OnURLFetchUploadProgress(
+ const URLFetcher* source, int64 current, int64 total) {
+ // Increasing between 0 and total.
+ EXPECT_LE(0, current);
+ EXPECT_GE(static_cast<int64>(chunk_.size()) * number_of_chunks_added_,
+ current);
+ EXPECT_LE(previous_progress_, current);
+ previous_progress_ = current;
+ EXPECT_EQ(-1, total);
+
+ if (number_of_chunks_added_ < 2) {
+ number_of_chunks_added_ += 1;
+ fetcher_->AppendChunkToUpload(chunk_, true);
+ }
+}
+
+void URLFetcherHeadersTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ std::string header;
+ EXPECT_TRUE(source->GetResponseHeaders()->GetNormalizedHeader("cache-control",
+ &header));
+ EXPECT_EQ("private", header);
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+void URLFetcherSocketAddressTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ EXPECT_EQ("127.0.0.1", source->GetSocketAddress().host());
+ EXPECT_EQ(expected_port_, source->GetSocketAddress().port());
+ URLFetcherTest::OnURLFetchComplete(source);
+}
+
+// static
+const char* URLFetcherStopOnRedirectTest::kRedirectTarget =
+ "http://redirect.target.com";
+
+URLFetcherStopOnRedirectTest::URLFetcherStopOnRedirectTest()
+ : callback_called_(false) {
+}
+
+URLFetcherStopOnRedirectTest::~URLFetcherStopOnRedirectTest() {
+}
+
+void URLFetcherStopOnRedirectTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->SetStopOnRedirect(true);
+ fetcher_->Start();
+}
+
+void URLFetcherStopOnRedirectTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ callback_called_ = true;
+ EXPECT_EQ(GURL(kRedirectTarget), source->GetURL());
+ EXPECT_EQ(URLRequestStatus::CANCELED, source->GetStatus().status());
+ EXPECT_EQ(ERR_ABORTED, source->GetStatus().error());
+ EXPECT_EQ(301, source->GetResponseCode());
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherProtectTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ start_time_ = Time::Now();
+ fetcher_->SetMaxRetriesOn5xx(11);
+ fetcher_->Start();
+}
+
+void URLFetcherProtectTest::OnURLFetchComplete(const URLFetcher* source) {
+ const TimeDelta one_second = TimeDelta::FromMilliseconds(1000);
+ if (source->GetResponseCode() >= 500) {
+ // Now running ServerUnavailable test.
+ // It takes more than 1 second to finish all 11 requests.
+ EXPECT_TRUE(Time::Now() - start_time_ >= one_second);
+ EXPECT_TRUE(source->GetStatus().is_success());
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+ CleanupAfterFetchComplete();
+ } else {
+ // Now running Overload test.
+ static int count = 0;
+ count++;
+ if (count < 20) {
+ fetcher_->SetRequestContext(
+ new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->Start();
+ } else {
+ // We have already sent 20 requests continuously. And we expect that
+ // it takes more than 1 second due to the overload protection settings.
+ EXPECT_TRUE(Time::Now() - start_time_ >= one_second);
+ URLFetcherTest::OnURLFetchComplete(source);
+ }
+ }
+}
+
+void URLFetcherProtectTestPassedThrough::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->SetAutomaticallyRetryOn5xx(false);
+ start_time_ = Time::Now();
+ fetcher_->SetMaxRetriesOn5xx(11);
+ fetcher_->Start();
+}
+
+void URLFetcherProtectTestPassedThrough::OnURLFetchComplete(
+ const URLFetcher* source) {
+ const TimeDelta one_minute = TimeDelta::FromMilliseconds(60000);
+ if (source->GetResponseCode() >= 500) {
+ // Now running ServerUnavailable test.
+ // It should get here on the first attempt, so almost immediately and
+ // *not* to attempt to execute all 11 requests (2.5 minutes).
+ EXPECT_TRUE(Time::Now() - start_time_ < one_minute);
+ EXPECT_TRUE(source->GetStatus().is_success());
+ // Check that suggested back off time is bigger than 0.
+ EXPECT_GT(fetcher_->GetBackoffDelay().InMicroseconds(), 0);
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+ } else {
+ // We should not get here!
+ ADD_FAILURE();
+ }
+
+ CleanupAfterFetchComplete();
+}
+
+
+URLFetcherBadHTTPSTest::URLFetcherBadHTTPSTest() {
+ PathService::Get(base::DIR_SOURCE_ROOT, &cert_dir_);
+ cert_dir_ = cert_dir_.AppendASCII("chrome");
+ cert_dir_ = cert_dir_.AppendASCII("test");
+ cert_dir_ = cert_dir_.AppendASCII("data");
+ cert_dir_ = cert_dir_.AppendASCII("ssl");
+ cert_dir_ = cert_dir_.AppendASCII("certificates");
+}
+
+// The "server certificate expired" error should result in automatic
+// cancellation of the request by
+// URLRequest::Delegate::OnSSLCertificateError.
+void URLFetcherBadHTTPSTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ // This part is different from URLFetcherTest::OnURLFetchComplete
+ // because this test expects the request to be cancelled.
+ EXPECT_EQ(URLRequestStatus::CANCELED, source->GetStatus().status());
+ EXPECT_EQ(ERR_ABORTED, source->GetStatus().error());
+ EXPECT_EQ(-1, source->GetResponseCode());
+ EXPECT_TRUE(source->GetCookies().empty());
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_TRUE(data.empty());
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherCancelTest::CreateFetcher(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ CancelTestURLRequestContextGetter* context_getter =
+ new CancelTestURLRequestContextGetter(io_message_loop_proxy(),
+ url);
+ fetcher_->SetRequestContext(context_getter);
+ fetcher_->SetMaxRetriesOn5xx(2);
+ fetcher_->Start();
+ // We need to wait for the creation of the URLRequestContext, since we
+ // rely on it being destroyed as a signal to end the test.
+ context_getter->WaitForContextCreation();
+ CancelRequest();
+}
+
+void URLFetcherCancelTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ // We should have cancelled the request before completion.
+ ADD_FAILURE();
+ CleanupAfterFetchComplete();
+}
+
+void URLFetcherCancelTest::CancelRequest() {
+ delete fetcher_;
+ // The URLFetcher's test context will post a Quit task once it is
+ // deleted. So if this test simply hangs, it means cancellation
+ // did not work.
+}
+
+void URLFetcherMultipleAttemptTest::OnURLFetchComplete(
+ const URLFetcher* source) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(200, source->GetResponseCode()); // HTTP OK
+ std::string data;
+ EXPECT_TRUE(source->GetResponseAsString(&data));
+ EXPECT_FALSE(data.empty());
+ if (!data.empty() && data_.empty()) {
+ data_ = data;
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+ fetcher_->Start();
+ } else {
+ EXPECT_EQ(data, data_);
+ CleanupAfterFetchComplete();
+ }
+}
+
+void URLFetcherFileTest::CreateFetcherForFile(const GURL& url,
+ const FilePath& file_path) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+
+ // Use the IO message loop to do the file operations in this test.
+ fetcher_->SaveResponseToFileAtPath(file_path, io_message_loop_proxy());
+ fetcher_->Start();
+}
+
+void URLFetcherFileTest::CreateFetcherForTempFile(const GURL& url) {
+ fetcher_ = new URLFetcherImpl(url, URLFetcher::GET, this);
+ fetcher_->SetRequestContext(new ThrottlingTestURLRequestContextGetter(
+ io_message_loop_proxy(), request_context()));
+
+ // Use the IO message loop to do the file operations in this test.
+ fetcher_->SaveResponseToTemporaryFile(io_message_loop_proxy());
+ fetcher_->Start();
+}
+
+void URLFetcherFileTest::OnURLFetchComplete(const URLFetcher* source) {
+ if (expected_file_error_ == base::PLATFORM_FILE_OK) {
+ EXPECT_TRUE(source->GetStatus().is_success());
+ EXPECT_EQ(source->GetResponseCode(), 200);
+
+ base::PlatformFileError error_code = base::PLATFORM_FILE_OK;
+ EXPECT_FALSE(fetcher_->FileErrorOccurred(&error_code));
+
+ EXPECT_TRUE(source->GetResponseAsFilePath(
+ take_ownership_of_file_, &file_path_));
+
+ EXPECT_TRUE(file_util::ContentsEqual(expected_file_, file_path_));
+ } else {
+ base::PlatformFileError error_code = base::PLATFORM_FILE_OK;
+ EXPECT_TRUE(fetcher_->FileErrorOccurred(&error_code));
+ EXPECT_EQ(expected_file_error_, error_code);
+ }
+ CleanupAfterFetchComplete();
+}
+
+TEST_F(URLFetcherTest, SameThreadsTest) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Create the fetcher on the main thread. Since IO will happen on the main
+ // thread, this will test URLFetcher's ability to do everything on one
+ // thread.
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+
+ MessageLoop::current()->Run();
+}
+
+#if defined(OS_MACOSX)
+// SIGSEGV on Mac: http://crbug.com/60426
+TEST_F(URLFetcherTest, DISABLED_DifferentThreadsTest) {
+#else
+TEST_F(URLFetcherTest, DifferentThreadsTest) {
+#endif
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Create a separate thread that will create the URLFetcher. The current
+ // (main) thread will do the IO, and when the fetch is complete it will
+ // terminate the main thread's message loop; then the other thread's
+ // message loop will be shut down automatically as the thread goes out of
+ // scope.
+ base::Thread t("URLFetcher test thread");
+ ASSERT_TRUE(t.Start());
+ t.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherTest::CreateFetcher,
+ base::Unretained(this),
+ test_server.GetURL("defaultresponse")));
+
+ MessageLoop::current()->Run();
+}
+
+void CancelAllOnIO() {
+ EXPECT_EQ(1, URLFetcherTest::GetNumFetcherCores());
+ URLFetcherImpl::CancelAll();
+ EXPECT_EQ(0, URLFetcherTest::GetNumFetcherCores());
+}
+
+// Tests to make sure CancelAll() will successfully cancel existing URLFetchers.
+TEST_F(URLFetcherTest, CancelAll) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+ EXPECT_EQ(0, GetNumFetcherCores());
+
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+ io_message_loop_proxy()->PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&CancelAllOnIO),
+ MessageLoop::QuitClosure());
+ MessageLoop::current()->Run();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ delete fetcher_;
+}
+
+TEST_F(URLFetcherMockDnsTest, DontRetryOnNetworkChangedByDefault) {
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+
+ // This posts a task to start the fetcher.
+ CreateFetcher(test_url_);
+ fetcher_->Start();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ MessageLoop::current()->RunUntilIdle();
+
+ // The fetcher is now running, but is pending the host resolve.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+
+ // A network change notification aborts the connect job.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+ ASSERT_TRUE(completed_fetcher_);
+
+ // And the owner of the fetcher gets the ERR_NETWORK_CHANGED error.
+ EXPECT_EQ(ERR_NETWORK_CHANGED, completed_fetcher_->GetStatus().error());
+}
+
+TEST_F(URLFetcherMockDnsTest, RetryOnNetworkChangedAndFail) {
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+
+ // This posts a task to start the fetcher.
+ CreateFetcher(test_url_);
+ fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ fetcher_->Start();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ MessageLoop::current()->RunUntilIdle();
+
+ // The fetcher is now running, but is pending the host resolve.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+
+ // Make it fail 3 times.
+ for (int i = 0; i < 3; ++i) {
+ // A network change notification aborts the connect job.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunUntilIdle();
+
+ // But the fetcher retries automatically.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+ }
+
+ // A 4th failure doesn't trigger another retry, and propagates the error
+ // to the owner of the fetcher.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+ ASSERT_TRUE(completed_fetcher_);
+
+ // And the owner of the fetcher gets the ERR_NETWORK_CHANGED error.
+ EXPECT_EQ(ERR_NETWORK_CHANGED, completed_fetcher_->GetStatus().error());
+}
+
+TEST_F(URLFetcherMockDnsTest, RetryOnNetworkChangedAndSucceed) {
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+
+ // This posts a task to start the fetcher.
+ CreateFetcher(test_url_);
+ fetcher_->SetAutomaticallyRetryOnNetworkChanges(3);
+ fetcher_->Start();
+ EXPECT_EQ(0, GetNumFetcherCores());
+ MessageLoop::current()->RunUntilIdle();
+
+ // The fetcher is now running, but is pending the host resolve.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+
+ // Make it fail 3 times.
+ for (int i = 0; i < 3; ++i) {
+ // A network change notification aborts the connect job.
+ NetworkChangeNotifier::NotifyObserversOfIPAddressChangeForTests();
+ MessageLoop::current()->RunUntilIdle();
+
+ // But the fetcher retries automatically.
+ EXPECT_EQ(1, GetNumFetcherCores());
+ EXPECT_TRUE(resolver_.has_pending_requests());
+ ASSERT_FALSE(completed_fetcher_);
+ }
+
+ // Now let it succeed by resolving the pending request.
+ resolver_.ResolveAllPending();
+ MessageLoop::current()->Run();
+
+ // URLFetcherMockDnsTest::OnURLFetchComplete() will quit the loop.
+ EXPECT_EQ(0, GetNumFetcherCores());
+ EXPECT_FALSE(resolver_.has_pending_requests());
+ ASSERT_TRUE(completed_fetcher_);
+
+ // This time the request succeeded.
+ EXPECT_EQ(OK, completed_fetcher_->GetStatus().error());
+ EXPECT_EQ(200, completed_fetcher_->GetResponseCode());
+}
+
+#if defined(OS_MACOSX)
+// SIGSEGV on Mac: http://crbug.com/60426
+TEST_F(URLFetcherPostTest, DISABLED_Basic) {
+#else
+TEST_F(URLFetcherPostTest, Basic) {
+#endif
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherEmptyPostTest, Basic) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ MessageLoop::current()->Run();
+}
+
+#if defined(OS_MACOSX)
+// SIGSEGV on Mac: http://crbug.com/60426
+TEST_F(URLFetcherUploadProgressTest, DISABLED_Basic) {
+#else
+TEST_F(URLFetcherUploadProgressTest, Basic) {
+#endif
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("echo"));
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherDownloadProgressTest, Basic) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ // Hardcoded file size - it cannot be easily fetched when a remote http server
+ // is used for testing.
+ static const int64 kFileSize = 19021;
+
+ expected_total_ = kFileSize;
+
+ CreateFetcher(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherDownloadProgressCancelTest, CancelWhileProgressReport) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ CreateFetcher(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherHeadersTest, Headers) {
+ TestServer test_server(
+ TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("files/with-headers.html"));
+ MessageLoop::current()->Run();
+ // The actual tests are in the URLFetcherHeadersTest fixture.
+}
+
+TEST_F(URLFetcherSocketAddressTest, SocketAddress) {
+ TestServer test_server(
+ TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(test_server.Start());
+ expected_port_ = test_server.host_port_pair().port();
+
+ // Reusing "with-headers.html" but doesn't really matter.
+ CreateFetcher(test_server.GetURL("files/with-headers.html"));
+ MessageLoop::current()->Run();
+ // The actual tests are in the URLFetcherSocketAddressTest fixture.
+}
+
+TEST_F(URLFetcherStopOnRedirectTest, StopOnRedirect) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(
+ test_server.GetURL(std::string("server-redirect?") + kRedirectTarget));
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(callback_called_);
+}
+
+TEST_F(URLFetcherProtectTest, Overload) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("defaultresponse"));
+
+ // Registers an entry for test url. It only allows 3 requests to be sent
+ // in 200 milliseconds.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(
+ request_context()->throttler_manager(),
+ "", 200, 3, 1, 2.0, 0.0, 256));
+ request_context()->throttler_manager()->OverrideEntryForTests(url, entry);
+
+ CreateFetcher(url);
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherProtectTest, ServerUnavailable) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Registers an entry for test url. The backoff time is calculated by:
+ // new_backoff = 2.0 * old_backoff + 0
+ // and maximum backoff time is 256 milliseconds.
+ // Maximum retries allowed is set to 11.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(
+ request_context()->throttler_manager(),
+ "", 200, 3, 1, 2.0, 0.0, 256));
+ request_context()->throttler_manager()->OverrideEntryForTests(url, entry);
+
+ CreateFetcher(url);
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherProtectTestPassedThrough, ServerUnavailablePropagateResponse) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Registers an entry for test url. The backoff time is calculated by:
+ // new_backoff = 2.0 * old_backoff + 0
+ // and maximum backoff time is 150000 milliseconds.
+ // Maximum retries allowed is set to 11.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(
+ request_context()->throttler_manager(),
+ "", 200, 3, 100, 2.0, 0.0, 150000));
+ // Total time if *not* for not doing automatic backoff would be 150s.
+ // In reality it should be "as soon as server responds".
+ request_context()->throttler_manager()->OverrideEntryForTests(url, entry);
+
+ CreateFetcher(url);
+
+ MessageLoop::current()->Run();
+}
+
+#if defined(OS_MACOSX)
+// SIGSEGV on Mac: http://crbug.com/60426
+TEST_F(URLFetcherBadHTTPSTest, DISABLED_BadHTTPSTest) {
+#else
+TEST_F(URLFetcherBadHTTPSTest, BadHTTPSTest) {
+#endif
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_EXPIRED);
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+ MessageLoop::current()->Run();
+}
+
+#if defined(OS_MACOSX)
+// SIGSEGV on Mac: http://crbug.com/60426
+TEST_F(URLFetcherCancelTest, DISABLED_ReleasesContext) {
+#else
+TEST_F(URLFetcherCancelTest, ReleasesContext) {
+#endif
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Create a separate thread that will create the URLFetcher. The current
+ // (main) thread will do the IO, and when the fetch is complete it will
+ // terminate the main thread's message loop; then the other thread's
+ // message loop will be shut down automatically as the thread goes out of
+ // scope.
+ base::Thread t("URLFetcher test thread");
+ ASSERT_TRUE(t.Start());
+ t.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherCancelTest::CreateFetcher,
+ base::Unretained(this), url));
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherCancelTest, CancelWhileDelayedStartTaskPending) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ GURL url(test_server.GetURL("files/server-unavailable.html"));
+
+ // Register an entry for test url.
+ // Using a sliding window of 4 seconds, and max of 1 request, under a fast
+ // run we expect to have a 4 second delay when posting the Start task.
+ scoped_refptr<URLRequestThrottlerEntry> entry(
+ new URLRequestThrottlerEntry(
+ request_context()->throttler_manager(),
+ "", 4000, 1, 2000, 2.0, 0.0, 4000));
+ request_context()->throttler_manager()->OverrideEntryForTests(url, entry);
+ // Fake that a request has just started.
+ entry->ReserveSendingTimeForNextRequest(base::TimeTicks());
+
+ // The next request we try to send will be delayed by ~4 seconds.
+ // The slower the test runs, the less the delay will be (since it takes the
+ // time difference from now).
+
+ base::Thread t("URLFetcher test thread");
+ ASSERT_TRUE(t.Start());
+ t.message_loop()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLFetcherTest::CreateFetcher, base::Unretained(this), url));
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherMultipleAttemptTest, SameData) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Create the fetcher on the main thread. Since IO will happen on the main
+ // thread, this will test URLFetcher's ability to do everything on one
+ // thread.
+ CreateFetcher(test_server.GetURL("defaultresponse"));
+
+ MessageLoop::current()->Run();
+}
+
+TEST_F(URLFetcherFileTest, SmallGet) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ temp_dir.path().AppendASCII(kFileToFetch));
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ ASSERT_FALSE(file_util::PathExists(file_path_))
+ << file_path_.value() << " not removed.";
+}
+
+TEST_F(URLFetcherFileTest, LargeGet) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ temp_dir.path().AppendASCII(kFileToFetch));
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+}
+
+TEST_F(URLFetcherFileTest, CanTakeOwnershipOfFile) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ temp_dir.path().AppendASCII(kFileToFetch));
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ MessageLoop::current()->RunUntilIdle();
+ ASSERT_FALSE(file_util::PathExists(file_path_))
+ << file_path_.value() << " not removed.";
+}
+
+
+TEST_F(URLFetcherFileTest, OverwriteExistingFile) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Create a file before trying to fetch.
+ static const char kFileToFetch[] = "simple.html";
+ static const char kData[] = "abcdefghijklmnopqrstuvwxyz";
+ file_path_ = temp_dir.path().AppendASCII(kFileToFetch);
+ const int data_size = arraysize(kData);
+ ASSERT_EQ(file_util::WriteFile(file_path_, kData, data_size), data_size);
+ ASSERT_TRUE(file_util::PathExists(file_path_));
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ ASSERT_FALSE(file_util::ContentsEqual(file_path_, expected_file_));
+
+ // Get a small file.
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ file_path_);
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+}
+
+TEST_F(URLFetcherFileTest, TryToOverwriteDirectory) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+
+ // Create a directory before trying to fetch.
+ static const char kFileToFetch[] = "simple.html";
+ file_path_ = temp_dir.path().AppendASCII(kFileToFetch);
+ ASSERT_TRUE(file_util::CreateDirectory(file_path_));
+ ASSERT_TRUE(file_util::PathExists(file_path_));
+
+ // Get a small file.
+ expected_file_error_ = base::PLATFORM_FILE_ERROR_ACCESS_DENIED;
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch),
+ file_path_);
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(URLFetcherFileTest, SmallGetToTempFile) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForTempFile(
+ test_server.GetURL(std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ ASSERT_FALSE(file_util::PathExists(file_path_))
+ << file_path_.value() << " not removed.";
+}
+
+TEST_F(URLFetcherFileTest, LargeGetToTempFile) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a file large enough to require more than one read into
+ // URLFetcher::Core's IOBuffer.
+ static const char kFileToFetch[] = "animate1.gif";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForTempFile(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+}
+
+TEST_F(URLFetcherFileTest, CanTakeOwnershipOfTempFile) {
+ TestServer test_server(TestServer::TYPE_HTTP,
+ TestServer::kLocalhost,
+ FilePath(kDocRoot));
+ ASSERT_TRUE(test_server.Start());
+
+ // Get a small file.
+ static const char kFileToFetch[] = "simple.html";
+ expected_file_ = test_server.GetDocumentRoot().AppendASCII(kFileToFetch);
+ CreateFetcherForTempFile(test_server.GetURL(
+ std::string(kTestServerFilePrefix) + kFileToFetch));
+
+ MessageLoop::current()->Run(); // OnURLFetchComplete() will Quit().
+
+ MessageLoop::current()->RunUntilIdle();
+ ASSERT_FALSE(file_util::PathExists(file_path_))
+ << file_path_.value() << " not removed.";
+}
+
+} // namespace
+
+} // namespace net
diff --git a/src/net/url_request/url_request.cc b/src/net/url_request/url_request.cc
new file mode 100644
index 0000000..735a34d
--- /dev/null
+++ b/src/net/url_request/url_request.cc
@@ -0,0 +1,984 @@
+// Copyright (c) 2012 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 "net/url_request/url_request.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/compiler_specific.h"
+#include "base/debug/stack_trace.h"
+#include "base/lazy_instance.h"
+#include "base/memory/singleton.h"
+#include "base/message_loop.h"
+#include "base/metrics/stats_counters.h"
+#include "base/stl_util.h"
+#include "base/synchronization/lock.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/network_change_notifier.h"
+#include "net/base/network_delegate.h"
+#include "net/base/ssl_cert_request_info.h"
+#include "net/base/upload_data_stream.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_manager.h"
+#include "net/url_request/url_request_netlog_params.h"
+#include "net/url_request/url_request_redirect_job.h"
+
+using base::Time;
+using std::string;
+
+namespace net {
+
+namespace {
+
+// Max number of http redirects to follow. Same number as gecko.
+const int kMaxRedirects = 20;
+
+// Discard headers which have meaning in POST (Content-Length, Content-Type,
+// Origin).
+void StripPostSpecificHeaders(HttpRequestHeaders* headers) {
+ // These are headers that may be attached to a POST.
+ headers->RemoveHeader(HttpRequestHeaders::kContentLength);
+ headers->RemoveHeader(HttpRequestHeaders::kContentType);
+ headers->RemoveHeader(HttpRequestHeaders::kOrigin);
+}
+
+// TODO(battre): Delete this, see http://crbug.com/89321:
+// This counter keeps track of the identifiers used for URL requests so far.
+// 0 is reserved to represent an invalid ID.
+uint64 g_next_url_request_identifier = 1;
+
+// This lock protects g_next_url_request_identifier.
+base::LazyInstance<base::Lock>::Leaky
+ g_next_url_request_identifier_lock = LAZY_INSTANCE_INITIALIZER;
+
+// Returns an prior unused identifier for URL requests.
+uint64 GenerateURLRequestIdentifier() {
+ base::AutoLock lock(g_next_url_request_identifier_lock.Get());
+ return g_next_url_request_identifier++;
+}
+
+// True once the first URLRequest was started.
+bool g_url_requests_started = false;
+
+// True if cookies are accepted by default.
+bool g_default_can_use_cookies = true;
+
+} // namespace
+
+URLRequest::ProtocolFactory*
+URLRequest::Deprecated::RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory) {
+ return URLRequest::RegisterProtocolFactory(scheme, factory);
+}
+
+void URLRequest::Deprecated::RegisterRequestInterceptor(
+ Interceptor* interceptor) {
+ URLRequest::RegisterRequestInterceptor(interceptor);
+}
+
+void URLRequest::Deprecated::UnregisterRequestInterceptor(
+ Interceptor* interceptor) {
+ URLRequest::UnregisterRequestInterceptor(interceptor);
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest::Interceptor
+
+URLRequestJob* URLRequest::Interceptor::MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) {
+ return NULL;
+}
+
+URLRequestJob* URLRequest::Interceptor::MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) {
+ return NULL;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest::Delegate
+
+void URLRequest::Delegate::OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+}
+
+void URLRequest::Delegate::OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) {
+ request->CancelAuth();
+}
+
+void URLRequest::Delegate::OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) {
+ request->Cancel();
+}
+
+void URLRequest::Delegate::OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool is_hsts_ok) {
+ request->Cancel();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+// URLRequest
+
+// TODO(shalev): Get rid of this constructor in favour of the one below it.
+URLRequest::URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context)
+ : context_(context),
+ network_delegate_(context->network_delegate()),
+ net_log_(BoundNetLog::Make(context->net_log(),
+ NetLog::SOURCE_URL_REQUEST)),
+ url_chain_(1, url),
+ method_("GET"),
+ referrer_policy_(CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE),
+ load_flags_(LOAD_NORMAL),
+ delegate_(delegate),
+ is_pending_(false),
+ is_redirecting_(false),
+ redirect_limit_(kMaxRedirects),
+ priority_(LOWEST),
+ identifier_(GenerateURLRequestIdentifier()),
+ blocked_on_delegate_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(before_request_callback_(
+ base::Bind(&URLRequest::BeforeRequestComplete,
+ base::Unretained(this)))),
+ has_notified_completion_(false),
+ received_response_content_length_(0),
+ creation_time_(base::TimeTicks::Now()) {
+ SIMPLE_STATS_COUNTER("URLRequestCount");
+
+ // Sanity check out environment.
+ DCHECK(MessageLoop::current()) << "The current MessageLoop must exist";
+
+ DCHECK(MessageLoop::current()->IsType(MessageLoop::TYPE_IO)) << ""
+ "The current MessageLoop must be TYPE_IO";
+
+ CHECK(context);
+ context->url_requests()->insert(this);
+
+ net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE);
+}
+
+URLRequest::URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context,
+ NetworkDelegate* network_delegate)
+ : context_(context),
+ network_delegate_(network_delegate),
+ net_log_(BoundNetLog::Make(context->net_log(),
+ NetLog::SOURCE_URL_REQUEST)),
+ url_chain_(1, url),
+ method_("GET"),
+ referrer_policy_(CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE),
+ load_flags_(LOAD_NORMAL),
+ delegate_(delegate),
+ is_pending_(false),
+ is_redirecting_(false),
+ redirect_limit_(kMaxRedirects),
+ priority_(LOWEST),
+ identifier_(GenerateURLRequestIdentifier()),
+ blocked_on_delegate_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(before_request_callback_(
+ base::Bind(&URLRequest::BeforeRequestComplete,
+ base::Unretained(this)))),
+ has_notified_completion_(false),
+ received_response_content_length_(0),
+ creation_time_(base::TimeTicks::Now()) {
+ SIMPLE_STATS_COUNTER("URLRequestCount");
+
+ // Sanity check out environment.
+ DCHECK(MessageLoop::current()) << "The current MessageLoop must exist";
+
+ DCHECK(MessageLoop::current()->IsType(MessageLoop::TYPE_IO)) << ""
+ "The current MessageLoop must be TYPE_IO";
+
+ CHECK(context);
+ context->url_requests()->insert(this);
+
+ net_log_.BeginEvent(NetLog::TYPE_REQUEST_ALIVE);
+}
+
+URLRequest::~URLRequest() {
+ Cancel();
+
+ if (network_delegate_) {
+ network_delegate_->NotifyURLRequestDestroyed(this);
+ if (job_)
+ job_->NotifyURLRequestDestroyed();
+ }
+
+ if (job_)
+ OrphanJob();
+
+ int deleted = context_->url_requests()->erase(this);
+ CHECK_EQ(1, deleted);
+
+ int net_error = OK;
+ // Log error only on failure, not cancellation, as even successful requests
+ // are "cancelled" on destruction.
+ if (status_.status() == URLRequestStatus::FAILED)
+ net_error = status_.error();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_REQUEST_ALIVE, net_error);
+}
+
+// static
+URLRequest::ProtocolFactory* URLRequest::RegisterProtocolFactory(
+ const string& scheme, ProtocolFactory* factory) {
+ return URLRequestJobManager::GetInstance()->RegisterProtocolFactory(scheme,
+ factory);
+}
+
+// static
+void URLRequest::RegisterRequestInterceptor(Interceptor* interceptor) {
+ URLRequestJobManager::GetInstance()->RegisterRequestInterceptor(interceptor);
+}
+
+// static
+void URLRequest::UnregisterRequestInterceptor(Interceptor* interceptor) {
+ URLRequestJobManager::GetInstance()->UnregisterRequestInterceptor(
+ interceptor);
+}
+
+void URLRequest::EnableChunkedUpload() {
+ DCHECK(!upload_data_stream_ || upload_data_stream_->is_chunked());
+ if (!upload_data_stream_) {
+ upload_data_stream_.reset(
+ new UploadDataStream(UploadDataStream::CHUNKED, 0));
+ }
+}
+
+void URLRequest::AppendChunkToUpload(const char* bytes,
+ int bytes_len,
+ bool is_last_chunk) {
+ DCHECK(upload_data_stream_);
+ DCHECK(upload_data_stream_->is_chunked());
+ DCHECK_GT(bytes_len, 0);
+ upload_data_stream_->AppendChunk(bytes, bytes_len, is_last_chunk);
+}
+
+void URLRequest::set_upload(scoped_ptr<UploadDataStream> upload) {
+ DCHECK(!upload->is_chunked());
+ upload_data_stream_ = upload.Pass();
+}
+
+const UploadDataStream* URLRequest::get_upload() const {
+ return upload_data_stream_.get();
+}
+
+bool URLRequest::has_upload() const {
+ return upload_data_stream_.get() != NULL;
+}
+
+void URLRequest::SetExtraRequestHeaderById(int id, const string& value,
+ bool overwrite) {
+ DCHECK(!is_pending_ || is_redirecting_);
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::SetExtraRequestHeaderByName(const string& name,
+ const string& value,
+ bool overwrite) {
+ DCHECK(!is_pending_ || is_redirecting_);
+ if (overwrite) {
+ extra_request_headers_.SetHeader(name, value);
+ } else {
+ extra_request_headers_.SetHeaderIfMissing(name, value);
+ }
+}
+
+void URLRequest::RemoveRequestHeaderByName(const string& name) {
+ DCHECK(!is_pending_ || is_redirecting_);
+ extra_request_headers_.RemoveHeader(name);
+}
+
+void URLRequest::SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) {
+ DCHECK(!is_pending_);
+ extra_request_headers_ = headers;
+
+ // NOTE: This method will likely become non-trivial once the other setters
+ // for request headers are implemented.
+}
+
+LoadStateWithParam URLRequest::GetLoadState() const {
+ // Only return LOAD_STATE_WAITING_FOR_DELEGATE if there's a load state param.
+ if (blocked_on_delegate_ && !load_state_param_.empty()) {
+ return LoadStateWithParam(LOAD_STATE_WAITING_FOR_DELEGATE,
+ load_state_param_);
+ }
+ return LoadStateWithParam(job_ ? job_->GetLoadState() : LOAD_STATE_IDLE,
+ string16());
+}
+
+UploadProgress URLRequest::GetUploadProgress() const {
+ if (!job_) {
+ // We haven't started or the request was cancelled
+ return UploadProgress();
+ }
+ if (final_upload_progress_.position()) {
+ // The first job completed and none of the subsequent series of
+ // GETs when following redirects will upload anything, so we return the
+ // cached results from the initial job, the POST.
+ return final_upload_progress_;
+ }
+ return job_->GetUploadProgress();
+}
+
+void URLRequest::GetResponseHeaderById(int id, string* value) {
+ DCHECK(job_);
+ NOTREACHED() << "implement me!";
+}
+
+void URLRequest::GetResponseHeaderByName(const string& name, string* value) {
+ DCHECK(value);
+ if (response_info_.headers) {
+ response_info_.headers->GetNormalizedHeader(name, value);
+ } else {
+ value->clear();
+ }
+}
+
+void URLRequest::GetAllResponseHeaders(string* headers) {
+ DCHECK(headers);
+ if (response_info_.headers) {
+ response_info_.headers->GetNormalizedHeaders(headers);
+ } else {
+ headers->clear();
+ }
+}
+
+HostPortPair URLRequest::GetSocketAddress() const {
+ DCHECK(job_);
+ return job_->GetSocketAddress();
+}
+
+HttpResponseHeaders* URLRequest::response_headers() const {
+ return response_info_.headers.get();
+}
+
+bool URLRequest::GetResponseCookies(ResponseCookies* cookies) {
+ DCHECK(job_);
+ return job_->GetResponseCookies(cookies);
+}
+
+void URLRequest::GetMimeType(string* mime_type) {
+ DCHECK(job_);
+ job_->GetMimeType(mime_type);
+}
+
+void URLRequest::GetCharset(string* charset) {
+ DCHECK(job_);
+ job_->GetCharset(charset);
+}
+
+int URLRequest::GetResponseCode() {
+ DCHECK(job_);
+ return job_->GetResponseCode();
+}
+
+// static
+void URLRequest::SetDefaultCookiePolicyToBlock() {
+ CHECK(!g_url_requests_started);
+ g_default_can_use_cookies = false;
+}
+
+// static
+bool URLRequest::IsHandledProtocol(const std::string& scheme) {
+ return URLRequestJobManager::GetInstance()->SupportsScheme(scheme);
+}
+
+// static
+bool URLRequest::IsHandledURL(const GURL& url) {
+ if (!url.is_valid()) {
+ // We handle error cases.
+ return true;
+ }
+
+ return IsHandledProtocol(url.scheme());
+}
+
+void URLRequest::set_first_party_for_cookies(
+ const GURL& first_party_for_cookies) {
+ first_party_for_cookies_ = first_party_for_cookies;
+}
+
+void URLRequest::set_method(const std::string& method) {
+ DCHECK(!is_pending_);
+ method_ = method;
+}
+
+void URLRequest::set_referrer(const std::string& referrer) {
+ DCHECK(!is_pending_);
+ referrer_ = referrer;
+}
+
+GURL URLRequest::GetSanitizedReferrer() const {
+ GURL ret(referrer());
+
+ // Ensure that we do not send username and password fields in the referrer.
+ if (ret.has_username() || ret.has_password()) {
+ GURL::Replacements referrer_mods;
+ referrer_mods.ClearUsername();
+ referrer_mods.ClearPassword();
+ ret = ret.ReplaceComponents(referrer_mods);
+ }
+
+ return ret;
+}
+
+void URLRequest::set_referrer_policy(ReferrerPolicy referrer_policy) {
+ DCHECK(!is_pending_);
+ referrer_policy_ = referrer_policy;
+}
+
+void URLRequest::set_delegate(Delegate* delegate) {
+ delegate_ = delegate;
+}
+
+void URLRequest::Start() {
+ DCHECK_EQ(network_delegate_, context_->network_delegate());
+
+ g_url_requests_started = true;
+ response_info_.request_time = Time::Now();
+
+ // Only notify the delegate for the initial request.
+ if (network_delegate_) {
+ int error = network_delegate_->NotifyBeforeURLRequest(
+ this, before_request_callback_, &delegate_redirect_url_);
+ if (error == net::ERR_IO_PENDING) {
+ // Paused on the delegate, will invoke |before_request_callback_| later.
+ SetBlockedOnDelegate();
+ } else {
+ BeforeRequestComplete(error);
+ }
+ return;
+ }
+
+ StartJob(URLRequestJobManager::GetInstance()->CreateJob(
+ this, network_delegate_));
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+void URLRequest::BeforeRequestComplete(int error) {
+ DCHECK(!job_);
+ DCHECK_NE(ERR_IO_PENDING, error);
+ DCHECK_EQ(network_delegate_, context_->network_delegate());
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, status_.status());
+
+ if (blocked_on_delegate_)
+ SetUnblockedOnDelegate();
+
+ if (error != OK) {
+ std::string source("delegate");
+ net_log_.AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source", &source));
+ StartJob(new URLRequestErrorJob(this, network_delegate_, error));
+ } else if (!delegate_redirect_url_.is_empty()) {
+ GURL new_url;
+ new_url.Swap(&delegate_redirect_url_);
+
+ URLRequestRedirectJob* job = new URLRequestRedirectJob(
+ this, network_delegate_, new_url,
+ // Use status code 307 to preserve the method, so POST requests work.
+ URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT);
+ StartJob(job);
+ } else {
+ StartJob(URLRequestJobManager::GetInstance()->CreateJob(
+ this, network_delegate_));
+ }
+}
+
+void URLRequest::StartJob(URLRequestJob* job) {
+ DCHECK(!is_pending_);
+ DCHECK(!job_);
+
+ net_log_.BeginEvent(
+ NetLog::TYPE_URL_REQUEST_START_JOB,
+ base::Bind(&NetLogURLRequestStartCallback,
+ &url(), &method_, load_flags_, priority_,
+ upload_data_stream_ ? upload_data_stream_->identifier() : -1));
+
+ job_ = job;
+ job_->SetExtraRequestHeaders(extra_request_headers_);
+
+ if (upload_data_stream_.get())
+ job_->SetUpload(upload_data_stream_.get());
+
+ is_pending_ = true;
+ is_redirecting_ = false;
+
+ response_info_.was_cached = false;
+
+ // Don't allow errors to be sent from within Start().
+ // TODO(brettw) this may cause NotifyDone to be sent synchronously,
+ // we probably don't want this: they should be sent asynchronously so
+ // the caller does not get reentered.
+ job_->Start();
+}
+
+void URLRequest::Restart() {
+ // Should only be called if the original job didn't make any progress.
+ DCHECK(job_ && !job_->has_response_started());
+ RestartWithJob(URLRequestJobManager::GetInstance()->CreateJob(
+ this, network_delegate_));
+}
+
+void URLRequest::RestartWithJob(URLRequestJob *job) {
+ DCHECK(job->request() == this);
+ PrepareToRestart();
+ StartJob(job);
+}
+
+void URLRequest::Cancel() {
+ DoCancel(ERR_ABORTED, SSLInfo());
+}
+
+void URLRequest::CancelWithError(int error) {
+ DoCancel(error, SSLInfo());
+}
+
+void URLRequest::CancelWithSSLError(int error, const SSLInfo& ssl_info) {
+ // This should only be called on a started request.
+ if (!is_pending_ || !job_ || job_->has_response_started()) {
+ NOTREACHED();
+ return;
+ }
+ DoCancel(error, ssl_info);
+}
+
+void URLRequest::DoCancel(int error, const SSLInfo& ssl_info) {
+ DCHECK(error < 0);
+
+ // If the URL request already has an error status, then canceling is a no-op.
+ // Plus, we don't want to change the error status once it has been set.
+ if (status_.is_success()) {
+ status_.set_status(URLRequestStatus::CANCELED);
+ status_.set_error(error);
+ response_info_.ssl_info = ssl_info;
+
+ // If the request hasn't already been completed, log a cancellation event.
+ if (!has_notified_completion_) {
+ // Don't log an error code on ERR_ABORTED, since that's redundant.
+ net_log_.AddEventWithNetErrorCode(NetLog::TYPE_CANCELLED,
+ error == ERR_ABORTED ? OK : error);
+ }
+ }
+
+ if (is_pending_ && job_)
+ job_->Kill();
+
+ // We need to notify about the end of this job here synchronously. The
+ // Job sends an asynchronous notification but by the time this is processed,
+ // our |context_| is NULL.
+ NotifyRequestCompleted();
+
+ // The Job will call our NotifyDone method asynchronously. This is done so
+ // that the Delegate implementation can call Cancel without having to worry
+ // about being called recursively.
+}
+
+bool URLRequest::Read(IOBuffer* dest, int dest_size, int* bytes_read) {
+ DCHECK(job_);
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ // This handles a cancel that happens while paused.
+ // TODO(ahendrickson): DCHECK() that it is not done after
+ // http://crbug.com/115705 is fixed.
+ if (job_->is_done())
+ return false;
+
+ if (dest_size == 0) {
+ // Caller is not too bright. I guess we've done what they asked.
+ return true;
+ }
+
+ // Once the request fails or is cancelled, read will just return 0 bytes
+ // to indicate end of stream.
+ if (!status_.is_success()) {
+ return true;
+ }
+
+ bool rv = job_->Read(dest, dest_size, bytes_read);
+ // If rv is false, the status cannot be success.
+ DCHECK(rv || status_.status() != URLRequestStatus::SUCCESS);
+ if (rv && *bytes_read <= 0 && status_.is_success())
+ NotifyRequestCompleted();
+ return rv;
+}
+
+void URLRequest::StopCaching() {
+ DCHECK(job_);
+ job_->StopCaching();
+}
+
+void URLRequest::NotifyReceivedRedirect(const GURL& location,
+ bool* defer_redirect) {
+ is_redirecting_ = true;
+
+ URLRequestJob* job =
+ URLRequestJobManager::GetInstance()->MaybeInterceptRedirect(
+ this, network_delegate_, location);
+ if (job) {
+ RestartWithJob(job);
+ } else if (delegate_) {
+ delegate_->OnReceivedRedirect(this, location, defer_redirect);
+ // |this| may be have been destroyed here.
+ }
+}
+
+void URLRequest::NotifyResponseStarted() {
+ int net_error = OK;
+ if (!status_.is_success())
+ net_error = status_.error();
+ net_log_.EndEventWithNetErrorCode(NetLog::TYPE_URL_REQUEST_START_JOB,
+ net_error);
+
+ URLRequestJob* job =
+ URLRequestJobManager::GetInstance()->MaybeInterceptResponse(
+ this, network_delegate_);
+ if (job) {
+ RestartWithJob(job);
+ } else {
+ if (delegate_) {
+ // In some cases (e.g. an event was canceled), we might have sent the
+ // completion event and receive a NotifyResponseStarted() later.
+ if (!has_notified_completion_ && status_.is_success()) {
+ if (network_delegate_)
+ network_delegate_->NotifyResponseStarted(this);
+ }
+
+ // Notify in case the entire URL Request has been finished.
+ if (!has_notified_completion_ && !status_.is_success())
+ NotifyRequestCompleted();
+
+ delegate_->OnResponseStarted(this);
+ // Nothing may appear below this line as OnResponseStarted may delete
+ // |this|.
+ }
+ }
+}
+
+void URLRequest::FollowDeferredRedirect() {
+ CHECK(job_);
+ CHECK(status_.is_success());
+
+ job_->FollowDeferredRedirect();
+}
+
+void URLRequest::SetAuth(const AuthCredentials& credentials) {
+ DCHECK(job_);
+ DCHECK(job_->NeedsAuth());
+
+ job_->SetAuth(credentials);
+}
+
+void URLRequest::CancelAuth() {
+ DCHECK(job_);
+ DCHECK(job_->NeedsAuth());
+
+ job_->CancelAuth();
+}
+
+void URLRequest::ContinueWithCertificate(X509Certificate* client_cert) {
+ DCHECK(job_);
+
+ job_->ContinueWithCertificate(client_cert);
+}
+
+void URLRequest::ContinueDespiteLastError() {
+ DCHECK(job_);
+
+ job_->ContinueDespiteLastError();
+}
+
+void URLRequest::PrepareToRestart() {
+ DCHECK(job_);
+
+ // Close the current URL_REQUEST_START_JOB, since we will be starting a new
+ // one.
+ net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_START_JOB);
+
+ OrphanJob();
+
+ response_info_ = HttpResponseInfo();
+ response_info_.request_time = Time::Now();
+ status_ = URLRequestStatus();
+ is_pending_ = false;
+}
+
+void URLRequest::OrphanJob() {
+ // When calling this function, please check that URLRequestHttpJob is
+ // not in between calling NetworkDelegate::NotifyHeadersReceived receiving
+ // the call back. This is currently guaranteed by the following strategies:
+ // - OrphanJob is called on JobRestart, in this case the URLRequestJob cannot
+ // be receiving any headers at that time.
+ // - OrphanJob is called in ~URLRequest, in this case
+ // NetworkDelegate::NotifyURLRequestDestroyed notifies the NetworkDelegate
+ // that the callback becomes invalid.
+ job_->Kill();
+ job_->DetachRequest(); // ensures that the job will not call us again
+ job_ = NULL;
+}
+
+int URLRequest::Redirect(const GURL& location, int http_status_code) {
+ if (net_log_.IsLoggingAllEvents()) {
+ net_log_.AddEvent(
+ NetLog::TYPE_URL_REQUEST_REDIRECTED,
+ NetLog::StringCallback("location", &location.possibly_invalid_spec()));
+ }
+
+ if (network_delegate_)
+ network_delegate_->NotifyBeforeRedirect(this, location);
+
+ if (redirect_limit_ <= 0) {
+ DVLOG(1) << "disallowing redirect: exceeds limit";
+ return ERR_TOO_MANY_REDIRECTS;
+ }
+
+ if (!location.is_valid())
+ return ERR_INVALID_URL;
+
+ if (!job_->IsSafeRedirect(location)) {
+ DVLOG(1) << "disallowing redirect: unsafe protocol";
+ return ERR_UNSAFE_REDIRECT;
+ }
+
+ if (!final_upload_progress_.position())
+ final_upload_progress_ = job_->GetUploadProgress();
+ PrepareToRestart();
+
+ // For 303 redirects, all request methods except HEAD are converted to GET,
+ // as per the latest httpbis draft. The draft also allows POST requests to
+ // be converted to GETs when following 301/302 redirects, for historical
+ // reasons. Most major browsers do this and so shall we. Both RFC 2616 and
+ // the httpbis draft say to prompt the user to confirm the generation of new
+ // requests, other than GET and HEAD requests, but IE omits these prompts and
+ // so shall we.
+ // See: https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-17#section-7.3
+ bool was_post = method_ == "POST";
+ if ((http_status_code == 303 && method_ != "HEAD") ||
+ ((http_status_code == 301 || http_status_code == 302) && was_post)) {
+ method_ = "GET";
+ upload_data_stream_.reset();
+ if (was_post) {
+ // If being switched from POST to GET, must remove headers that were
+ // specific to the POST and don't have meaning in GET. For example
+ // the inclusion of a multipart Content-Type header in GET can cause
+ // problems with some servers:
+ // http://code.google.com/p/chromium/issues/detail?id=843
+ StripPostSpecificHeaders(&extra_request_headers_);
+ }
+ }
+
+ // Suppress the referrer if we're redirecting out of https.
+ if (referrer_policy_ ==
+ CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE &&
+ GURL(referrer_).SchemeIsSecure() && !location.SchemeIsSecure()) {
+ referrer_.clear();
+ }
+
+ url_chain_.push_back(location);
+ --redirect_limit_;
+
+ Start();
+ return OK;
+}
+
+const URLRequestContext* URLRequest::context() const {
+ return context_;
+}
+
+int64 URLRequest::GetExpectedContentSize() const {
+ int64 expected_content_size = -1;
+ if (job_)
+ expected_content_size = job_->expected_content_size();
+
+ return expected_content_size;
+}
+
+bool URLRequest::GetHSTSRedirect(GURL* redirect_url) const {
+ const GURL& url = this->url();
+ if (!url.SchemeIs("http"))
+ return false;
+ TransportSecurityState::DomainState domain_state;
+ if (context()->transport_security_state() &&
+ context()->transport_security_state()->GetDomainState(
+ url.host(),
+ SSLConfigService::IsSNIAvailable(context()->ssl_config_service()),
+ &domain_state) &&
+ domain_state.ShouldRedirectHTTPToHTTPS()) {
+ url_canon::Replacements<char> replacements;
+ const char kNewScheme[] = "https";
+ replacements.SetScheme(kNewScheme,
+ url_parse::Component(0, strlen(kNewScheme)));
+ *redirect_url = url.ReplaceComponents(replacements);
+ return true;
+ }
+ return false;
+}
+
+void URLRequest::NotifyAuthRequired(AuthChallengeInfo* auth_info) {
+ NetworkDelegate::AuthRequiredResponse rv =
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ auth_info_ = auth_info;
+ if (network_delegate_) {
+ rv = network_delegate_->NotifyAuthRequired(
+ this,
+ *auth_info,
+ base::Bind(&URLRequest::NotifyAuthRequiredComplete,
+ base::Unretained(this)),
+ &auth_credentials_);
+ }
+
+ if (rv == NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING) {
+ SetBlockedOnDelegate();
+ } else {
+ NotifyAuthRequiredComplete(rv);
+ }
+}
+
+void URLRequest::NotifyAuthRequiredComplete(
+ NetworkDelegate::AuthRequiredResponse result) {
+ SetUnblockedOnDelegate();
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, status_.status());
+
+ // NotifyAuthRequired may be called multiple times, such as
+ // when an authentication attempt fails. Clear out the data
+ // so it can be reset on another round.
+ AuthCredentials credentials = auth_credentials_;
+ auth_credentials_ = AuthCredentials();
+ scoped_refptr<AuthChallengeInfo> auth_info;
+ auth_info.swap(auth_info_);
+
+ switch (result) {
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION:
+ // Defer to the URLRequest::Delegate, since the NetworkDelegate
+ // didn't take an action.
+ if (delegate_)
+ delegate_->OnAuthRequired(this, auth_info.get());
+ break;
+
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH:
+ SetAuth(credentials);
+ break;
+
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH:
+ CancelAuth();
+ break;
+
+ case NetworkDelegate::AUTH_REQUIRED_RESPONSE_IO_PENDING:
+ NOTREACHED();
+ break;
+ }
+}
+
+void URLRequest::NotifyCertificateRequested(
+ SSLCertRequestInfo* cert_request_info) {
+ if (delegate_)
+ delegate_->OnCertificateRequested(this, cert_request_info);
+}
+
+void URLRequest::NotifySSLCertificateError(const SSLInfo& ssl_info,
+ bool fatal) {
+ if (delegate_)
+ delegate_->OnSSLCertificateError(this, ssl_info, fatal);
+}
+
+bool URLRequest::CanGetCookies(const CookieList& cookie_list) const {
+ DCHECK(!(load_flags_ & LOAD_DO_NOT_SEND_COOKIES));
+ if (network_delegate_) {
+ return network_delegate_->CanGetCookies(*this, cookie_list);
+ }
+ return g_default_can_use_cookies;
+}
+
+bool URLRequest::CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const {
+ DCHECK(!(load_flags_ & LOAD_DO_NOT_SAVE_COOKIES));
+ if (network_delegate_) {
+ return network_delegate_->CanSetCookie(*this, cookie_line, options);
+ }
+ return g_default_can_use_cookies;
+}
+
+
+void URLRequest::NotifyReadCompleted(int bytes_read) {
+ // Notify in case the entire URL Request has been finished.
+ if (bytes_read <= 0)
+ NotifyRequestCompleted();
+
+ // Notify NetworkChangeNotifier that we just received network data.
+ // This is to identify cases where the NetworkChangeNotifier thinks we
+ // are off-line but we are still receiving network data (crbug.com/124069).
+ if (bytes_read > 0 && !was_cached())
+ NetworkChangeNotifier::NotifyDataReceived(url());
+
+ if (delegate_)
+ delegate_->OnReadCompleted(this, bytes_read);
+
+ // Nothing below this line as OnReadCompleted may delete |this|.
+}
+
+void URLRequest::NotifyRequestCompleted() {
+ // TODO(battre): Get rid of this check, according to willchan it should
+ // not be needed.
+ if (has_notified_completion_)
+ return;
+
+ is_pending_ = false;
+ is_redirecting_ = false;
+ has_notified_completion_ = true;
+ if (network_delegate_)
+ network_delegate_->NotifyCompleted(this, job_ != NULL);
+}
+
+void URLRequest::SetBlockedOnDelegate() {
+ blocked_on_delegate_ = true;
+ if (!load_state_param_.empty()) {
+ net_log_.BeginEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_DELEGATE,
+ NetLog::StringCallback("delegate", &load_state_param_));
+ } else {
+ net_log_.BeginEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_DELEGATE);
+ }
+}
+
+void URLRequest::SetUnblockedOnDelegate() {
+ if (!blocked_on_delegate_)
+ return;
+ blocked_on_delegate_ = false;
+ load_state_param_.clear();
+ net_log_.EndEvent(NetLog::TYPE_URL_REQUEST_BLOCKED_ON_DELEGATE);
+}
+
+void URLRequest::set_stack_trace(const base::debug::StackTrace& stack_trace) {
+ base::debug::StackTrace* stack_trace_copy =
+ new base::debug::StackTrace(NULL, 0);
+ *stack_trace_copy = stack_trace;
+ stack_trace_.reset(stack_trace_copy);
+}
+
+const base::debug::StackTrace* URLRequest::stack_trace() const {
+ return stack_trace_.get();
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request.h b/src/net/url_request/url_request.h
new file mode 100644
index 0000000..4081e75
--- /dev/null
+++ b/src/net/url_request/url_request.h
@@ -0,0 +1,849 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_H_
+#define NET_URL_REQUEST_URL_REQUEST_H_
+
+#include <string>
+#include <vector>
+
+#include "base/debug/leak_tracker.h"
+#include "base/logging.h"
+#include "base/memory/ref_counted.h"
+#include "base/string16.h"
+#include "base/supports_user_data.h"
+#include "base/time.h"
+#include "base/threading/non_thread_safe.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/network_delegate.h"
+#include "net/base/request_priority.h"
+#include "net/base/upload_progress.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_status.h"
+
+class FilePath;
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+class ChildProcessSecurityPolicyTest;
+class ComponentUpdateInterceptor;
+class TestAutomationProvider;
+class URLRequestAutomationJob;
+
+namespace base {
+namespace debug {
+class StackTrace;
+}
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace appcache {
+class AppCacheInterceptor;
+class AppCacheRequestHandlerTest;
+class AppCacheURLRequestJobTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace content {
+class ResourceDispatcherHostTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace extensions {
+class AutoUpdateInterceptor;
+class UserScriptListenerTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace fileapi {
+class FileSystemDirURLRequestJobTest;
+class FileSystemURLRequestJobTest;
+class FileWriterDelegateTest;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace policy {
+class CannedResponseInterceptor;
+}
+
+// Temporary layering violation to allow existing users of a deprecated
+// interface.
+namespace webkit_blob {
+class BlobURLRequestJobTest;
+}
+
+namespace net {
+
+class CookieOptions;
+class HostPortPair;
+class IOBuffer;
+class SSLCertRequestInfo;
+class SSLInfo;
+class UploadDataStream;
+class URLRequestContext;
+class URLRequestJob;
+class X509Certificate;
+
+// This stores the values of the Set-Cookie headers received during the request.
+// Each item in the vector corresponds to a Set-Cookie: line received,
+// excluding the "Set-Cookie:" part.
+typedef std::vector<std::string> ResponseCookies;
+
+//-----------------------------------------------------------------------------
+// A class representing the asynchronous load of a data stream from an URL.
+//
+// The lifetime of an instance of this class is completely controlled by the
+// consumer, and the instance is not required to live on the heap or be
+// allocated in any special way. It is also valid to delete an URLRequest
+// object during the handling of a callback to its delegate. Of course, once
+// the URLRequest is deleted, no further callbacks to its delegate will occur.
+//
+// NOTE: All usage of all instances of this class should be on the same thread.
+//
+class NET_EXPORT URLRequest : NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public base::SupportsUserData {
+ public:
+ // Callback function implemented by protocol handlers to create new jobs.
+ // The factory may return NULL to indicate an error, which will cause other
+ // factories to be queried. If no factory handles the request, then the
+ // default job will be used.
+ typedef URLRequestJob* (ProtocolFactory)(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ // HTTP request/response header IDs (via some preprocessor fun) for use with
+ // SetRequestHeaderById and GetResponseHeaderById.
+ enum {
+#define HTTP_ATOM(x) HTTP_ ## x,
+#include "net/http/http_atom_list.h"
+#undef HTTP_ATOM
+ };
+
+ // Referrer policies (see set_referrer_policy): During server redirects, the
+ // referrer header might be cleared, if the protocol changes from HTTPS to
+ // HTTP. This is the default behavior of URLRequest, corresponding to
+ // CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE. Alternatively, the
+ // referrer policy can be set to never change the referrer header. This
+ // behavior corresponds to NEVER_CLEAR_REFERRER. Embedders will want to use
+ // NEVER_CLEAR_REFERRER when implementing the meta-referrer support
+ // (http://wiki.whatwg.org/wiki/Meta_referrer) and sending requests with a
+ // non-default referrer policy. Only the default referrer policy requires
+ // the referrer to be cleared on transitions from HTTPS to HTTP.
+ enum ReferrerPolicy {
+ CLEAR_REFERRER_ON_TRANSITION_FROM_SECURE_TO_INSECURE,
+ NEVER_CLEAR_REFERRER,
+ };
+
+ // This class handles network interception. Use with
+ // (Un)RegisterRequestInterceptor.
+ class NET_EXPORT Interceptor {
+ public:
+ virtual ~Interceptor() {}
+
+ // Called for every request made. Should return a new job to handle the
+ // request if it should be intercepted, or NULL to allow the request to
+ // be handled in the normal manner.
+ virtual URLRequestJob* MaybeIntercept(
+ URLRequest* request, NetworkDelegate* network_delegate) = 0;
+
+ // Called after having received a redirect response, but prior to the
+ // the request delegate being informed of the redirect. Can return a new
+ // job to replace the existing job if it should be intercepted, or NULL
+ // to allow the normal handling to continue. If a new job is provided,
+ // the delegate never sees the original redirect response, instead the
+ // response produced by the intercept job will be returned.
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location);
+
+ // Called after having received a final response, but prior to the
+ // the request delegate being informed of the response. This is also
+ // called when there is no server response at all to allow interception
+ // on dns or network errors. Can return a new job to replace the existing
+ // job if it should be intercepted, or NULL to allow the normal handling to
+ // continue. If a new job is provided, the delegate never sees the original
+ // response, instead the response produced by the intercept job will be
+ // returned.
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate);
+ };
+
+ // Deprecated interfaces in net::URLRequest. They have been moved to
+ // URLRequest's private section to prevent new uses. Existing uses are
+ // explicitly friended here and should be removed over time.
+ class NET_EXPORT Deprecated {
+ private:
+ // TODO(willchan): Kill off these friend declarations.
+ friend class extensions::AutoUpdateInterceptor;
+ friend class ::ChildProcessSecurityPolicyTest;
+ friend class ::ComponentUpdateInterceptor;
+ friend class ::TestAutomationProvider;
+ friend class ::URLRequestAutomationJob;
+ friend class TestInterceptor;
+ friend class URLRequestFilter;
+ friend class appcache::AppCacheInterceptor;
+ friend class appcache::AppCacheRequestHandlerTest;
+ friend class appcache::AppCacheURLRequestJobTest;
+ friend class content::ResourceDispatcherHostTest;
+ friend class extensions::UserScriptListenerTest;
+ friend class fileapi::FileSystemDirURLRequestJobTest;
+ friend class fileapi::FileSystemURLRequestJobTest;
+ friend class fileapi::FileWriterDelegateTest;
+ friend class policy::CannedResponseInterceptor;
+ friend class webkit_blob::BlobURLRequestJobTest;
+
+ // Use URLRequestJobFactory::ProtocolHandler instead.
+ static ProtocolFactory* RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory);
+
+ // Use URLRequestJobFactory::Interceptor instead.
+ static void RegisterRequestInterceptor(Interceptor* interceptor);
+ static void UnregisterRequestInterceptor(Interceptor* interceptor);
+
+ DISALLOW_IMPLICIT_CONSTRUCTORS(Deprecated);
+ };
+
+ // The delegate's methods are called from the message loop of the thread
+ // on which the request's Start() method is called. See above for the
+ // ordering of callbacks.
+ //
+ // The callbacks will be called in the following order:
+ // Start()
+ // - OnCertificateRequested* (zero or more calls, if the SSL server and/or
+ // SSL proxy requests a client certificate for authentication)
+ // - OnSSLCertificateError* (zero or one call, if the SSL server's
+ // certificate has an error)
+ // - OnReceivedRedirect* (zero or more calls, for the number of redirects)
+ // - OnAuthRequired* (zero or more calls, for the number of
+ // authentication failures)
+ // - OnResponseStarted
+ // Read() initiated by delegate
+ // - OnReadCompleted* (zero or more calls until all data is read)
+ //
+ // Read() must be called at least once. Read() returns true when it completed
+ // immediately, and false if an IO is pending or if there is an error. When
+ // Read() returns false, the caller can check the Request's status() to see
+ // if an error occurred, or if the IO is just pending. When Read() returns
+ // true with zero bytes read, it indicates the end of the response.
+ //
+ class NET_EXPORT Delegate {
+ public:
+ // Called upon a server-initiated redirect. The delegate may call the
+ // request's Cancel method to prevent the redirect from being followed.
+ // Since there may be multiple chained redirects, there may also be more
+ // than one redirect call.
+ //
+ // When this function is called, the request will still contain the
+ // original URL, the destination of the redirect is provided in 'new_url'.
+ // If the delegate does not cancel the request and |*defer_redirect| is
+ // false, then the redirect will be followed, and the request's URL will be
+ // changed to the new URL. Otherwise if the delegate does not cancel the
+ // request and |*defer_redirect| is true, then the redirect will be
+ // followed once FollowDeferredRedirect is called on the URLRequest.
+ //
+ // The caller must set |*defer_redirect| to false, so that delegates do not
+ // need to set it if they are happy with the default behavior of not
+ // deferring redirect.
+ virtual void OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect);
+
+ // Called when we receive an authentication failure. The delegate should
+ // call request->SetAuth() with the user's credentials once it obtains them,
+ // or request->CancelAuth() to cancel the login and display the error page.
+ // When it does so, the request will be reissued, restarting the sequence
+ // of On* callbacks.
+ virtual void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info);
+
+ // Called when we receive an SSL CertificateRequest message for client
+ // authentication. The delegate should call
+ // request->ContinueWithCertificate() with the client certificate the user
+ // selected, or request->ContinueWithCertificate(NULL) to continue the SSL
+ // handshake without a client certificate.
+ virtual void OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info);
+
+ // Called when using SSL and the server responds with a certificate with
+ // an error, for example, whose common name does not match the common name
+ // we were expecting for that host. The delegate should either do the
+ // safe thing and Cancel() the request or decide to proceed by calling
+ // ContinueDespiteLastError(). cert_error is a ERR_* error code
+ // indicating what's wrong with the certificate.
+ // If |fatal| is true then the host in question demands a higher level
+ // of security (due e.g. to HTTP Strict Transport Security, user
+ // preference, or built-in policy). In this case, errors must not be
+ // bypassable by the user.
+ virtual void OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal);
+
+ // After calling Start(), the delegate will receive an OnResponseStarted
+ // callback when the request has completed. If an error occurred, the
+ // request->status() will be set. On success, all redirects have been
+ // followed and the final response is beginning to arrive. At this point,
+ // meta data about the response is available, including for example HTTP
+ // response headers if this is a request for a HTTP resource.
+ virtual void OnResponseStarted(URLRequest* request) = 0;
+
+ // Called when the a Read of the response body is completed after an
+ // IO_PENDING status from a Read() call.
+ // The data read is filled into the buffer which the caller passed
+ // to Read() previously.
+ //
+ // If an error occurred, request->status() will contain the error,
+ // and bytes read will be -1.
+ virtual void OnReadCompleted(URLRequest* request, int bytes_read) = 0;
+
+ protected:
+ virtual ~Delegate() {}
+ };
+
+ // TODO(shalev): Get rid of this constructor in favour of the one below it.
+ // Initialize an URL request.
+ URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context);
+
+ URLRequest(const GURL& url,
+ Delegate* delegate,
+ const URLRequestContext* context,
+ NetworkDelegate* network_delegate);
+
+ // If destroyed after Start() has been called but while IO is pending,
+ // then the request will be effectively canceled and the delegate
+ // will not have any more of its methods called.
+ virtual ~URLRequest();
+
+ // Changes the default cookie policy from allowing all cookies to blocking all
+ // cookies. Embedders that want to implement a more flexible policy should
+ // change the default to blocking all cookies, and provide a NetworkDelegate
+ // with the URLRequestContext that maintains the CookieStore.
+ // The cookie policy default has to be set before the first URLRequest is
+ // started. Once it was set to block all cookies, it cannot be changed back.
+ static void SetDefaultCookiePolicyToBlock();
+
+ // Returns true if the scheme can be handled by URLRequest. False otherwise.
+ static bool IsHandledProtocol(const std::string& scheme);
+
+ // Returns true if the url can be handled by URLRequest. False otherwise.
+ // The function returns true for invalid urls because URLRequest knows how
+ // to handle those.
+ // NOTE: This will also return true for URLs that are handled by
+ // ProtocolFactories that only work for requests that are scoped to a
+ // Profile.
+ static bool IsHandledURL(const GURL& url);
+
+ // The original url is the url used to initialize the request, and it may
+ // differ from the url if the request was redirected.
+ const GURL& original_url() const { return url_chain_.front(); }
+ // The chain of urls traversed by this request. If the request had no
+ // redirects, this vector will contain one element.
+ const std::vector<GURL>& url_chain() const { return url_chain_; }
+ const GURL& url() const { return url_chain_.back(); }
+
+ // The URL that should be consulted for the third-party cookie blocking
+ // policy.
+ //
+ // WARNING: This URL must only be used for the third-party cookie blocking
+ // policy. It MUST NEVER be used for any kind of SECURITY check.
+ //
+ // For example, if a top-level navigation is redirected, the
+ // first-party for cookies will be the URL of the first URL in the
+ // redirect chain throughout the whole redirect. If it was used for
+ // a security check, an attacker might try to get around this check
+ // by starting from some page that redirects to the
+ // host-to-be-attacked.
+ const GURL& first_party_for_cookies() const {
+ return first_party_for_cookies_;
+ }
+ // This method may be called before Start() or FollowDeferredRedirect() is
+ // called.
+ void set_first_party_for_cookies(const GURL& first_party_for_cookies);
+
+ // The request method, as an uppercase string. "GET" is the default value.
+ // The request method may only be changed before Start() is called and
+ // should only be assigned an uppercase value.
+ const std::string& method() const { return method_; }
+ void set_method(const std::string& method);
+
+ // The referrer URL for the request. This header may actually be suppressed
+ // from the underlying network request for security reasons (e.g., a HTTPS
+ // URL will not be sent as the referrer for a HTTP request). The referrer
+ // may only be changed before Start() is called.
+ const std::string& referrer() const { return referrer_; }
+ void set_referrer(const std::string& referrer);
+ // Returns the referrer header with potential username and password removed.
+ GURL GetSanitizedReferrer() const;
+
+ // The referrer policy to apply when updating the referrer during redirects.
+ // The referrer policy may only be changed before Start() is called.
+ void set_referrer_policy(ReferrerPolicy referrer_policy);
+
+ // Sets the delegate of the request. This value may be changed at any time,
+ // and it is permissible for it to be null.
+ void set_delegate(Delegate* delegate);
+
+ // Indicates that the request body should be sent using chunked transfer
+ // encoding. This method may only be called before Start() is called.
+ void EnableChunkedUpload();
+
+ // Appends the given bytes to the request's upload data to be sent
+ // immediately via chunked transfer encoding. When all data has been sent,
+ // call MarkEndOfChunks() to indicate the end of upload data.
+ //
+ // This method may be called only after calling EnableChunkedUpload().
+ void AppendChunkToUpload(const char* bytes,
+ int bytes_len,
+ bool is_last_chunk);
+
+ // Sets the upload data.
+ void set_upload(scoped_ptr<UploadDataStream> upload);
+
+ // Gets the upload data.
+ const UploadDataStream* get_upload() const;
+
+ // Returns true if the request has a non-empty message body to upload.
+ bool has_upload() const;
+
+ // Set an extra request header by ID or name, or remove one by name. These
+ // methods may only be called before Start() is called, or before a new
+ // redirect in the request chain.
+ void SetExtraRequestHeaderById(int header_id, const std::string& value,
+ bool overwrite);
+ void SetExtraRequestHeaderByName(const std::string& name,
+ const std::string& value, bool overwrite);
+ void RemoveRequestHeaderByName(const std::string& name);
+
+ // Sets all extra request headers. Any extra request headers set by other
+ // methods are overwritten by this method. This method may only be called
+ // before Start() is called. It is an error to call it later.
+ void SetExtraRequestHeaders(const HttpRequestHeaders& headers);
+
+ const HttpRequestHeaders& extra_request_headers() const {
+ return extra_request_headers_;
+ }
+
+ // Returns the current load state for the request. |param| is an optional
+ // parameter describing details related to the load state. Not all load states
+ // have a parameter.
+ LoadStateWithParam GetLoadState() const;
+ void SetLoadStateParam(const string16& param) {
+ load_state_param_ = param;
+ }
+
+ // Returns the current upload progress in bytes. When the upload data is
+ // chunked, size is set to zero, but position will not be.
+ UploadProgress GetUploadProgress() const;
+
+ // Get response header(s) by ID or name. These methods may only be called
+ // once the delegate's OnResponseStarted method has been called. Headers
+ // that appear more than once in the response are coalesced, with values
+ // separated by commas (per RFC 2616). This will not work with cookies since
+ // comma can be used in cookie values.
+ // TODO(darin): add API to enumerate response headers.
+ void GetResponseHeaderById(int header_id, std::string* value);
+ void GetResponseHeaderByName(const std::string& name, std::string* value);
+
+ // Get all response headers, \n-delimited and \n\0-terminated. This includes
+ // the response status line. Restrictions on GetResponseHeaders apply.
+ void GetAllResponseHeaders(std::string* headers);
+
+ // The time when |this| was constructed.
+ base::TimeTicks creation_time() const { return creation_time_; }
+
+ // The time at which the returned response was requested. For cached
+ // responses, this is the last time the cache entry was validated.
+ const base::Time& request_time() const {
+ return response_info_.request_time;
+ }
+
+ // The time at which the returned response was generated. For cached
+ // responses, this is the last time the cache entry was validated.
+ const base::Time& response_time() const {
+ return response_info_.response_time;
+ }
+
+ // Indicate if this response was fetched from disk cache.
+ bool was_cached() const { return response_info_.was_cached; }
+
+ // Returns true if the URLRequest was delivered through a proxy.
+ bool was_fetched_via_proxy() const {
+ return response_info_.was_fetched_via_proxy;
+ }
+
+ // Returns the host and port that the content was fetched from. See
+ // http_response_info.h for caveats relating to cached content.
+ HostPortPair GetSocketAddress() const;
+
+ // Get all response headers, as a HttpResponseHeaders object. See comments
+ // in HttpResponseHeaders class as to the format of the data.
+ HttpResponseHeaders* response_headers() const;
+
+ // Get the SSL connection info.
+ const SSLInfo& ssl_info() const {
+ return response_info_.ssl_info;
+ }
+
+ // Returns the cookie values included in the response, if the request is one
+ // that can have cookies. Returns true if the request is a cookie-bearing
+ // type, false otherwise. This method may only be called once the
+ // delegate's OnResponseStarted method has been called.
+ bool GetResponseCookies(ResponseCookies* cookies);
+
+ // Get the mime type. This method may only be called once the delegate's
+ // OnResponseStarted method has been called.
+ void GetMimeType(std::string* mime_type);
+
+ // Get the charset (character encoding). This method may only be called once
+ // the delegate's OnResponseStarted method has been called.
+ void GetCharset(std::string* charset);
+
+ // Returns the HTTP response code (e.g., 200, 404, and so on). This method
+ // may only be called once the delegate's OnResponseStarted method has been
+ // called. For non-HTTP requests, this method returns -1.
+ int GetResponseCode();
+
+ // Get the HTTP response info in its entirety.
+ const HttpResponseInfo& response_info() const { return response_info_; }
+
+ // Access the LOAD_* flags modifying this request (see load_flags.h).
+ int load_flags() const { return load_flags_; }
+ void set_load_flags(int flags) { load_flags_ = flags; }
+
+ // Returns true if the request is "pending" (i.e., if Start() has been called,
+ // and the response has not yet been called).
+ bool is_pending() const { return is_pending_; }
+
+ // Returns true if the request is in the process of redirecting to a new
+ // URL but has not yet initiated the new request.
+ bool is_redirecting() const { return is_redirecting_; }
+
+ // Returns the error status of the request.
+ const URLRequestStatus& status() const { return status_; }
+
+ // Returns a globally unique identifier for this request.
+ uint64 identifier() const { return identifier_; }
+
+ // This method is called to start the request. The delegate will receive
+ // a OnResponseStarted callback when the request is started.
+ void Start();
+
+ // This method may be called at any time after Start() has been called to
+ // cancel the request. This method may be called many times, and it has
+ // no effect once the response has completed. It is guaranteed that no
+ // methods of the delegate will be called after the request has been
+ // cancelled, except that this may call the delegate's OnReadCompleted()
+ // during the call to Cancel itself.
+ void Cancel();
+
+ // Cancels the request and sets the error to |error| (see net_error_list.h
+ // for values).
+ void CancelWithError(int error);
+
+ // Cancels the request and sets the error to |error| (see net_error_list.h
+ // for values) and attaches |ssl_info| as the SSLInfo for that request. This
+ // is useful to attach a certificate and certificate error to a canceled
+ // request.
+ void CancelWithSSLError(int error, const SSLInfo& ssl_info);
+
+ // Read initiates an asynchronous read from the response, and must only
+ // be called after the OnResponseStarted callback is received with a
+ // successful status.
+ // If data is available, Read will return true, and the data and length will
+ // be returned immediately. If data is not available, Read returns false,
+ // and an asynchronous Read is initiated. The Read is finished when
+ // the caller receives the OnReadComplete callback. Unless the request was
+ // cancelled, OnReadComplete will always be called, even if the read failed.
+ //
+ // The buf parameter is a buffer to receive the data. If the operation
+ // completes asynchronously, the implementation will reference the buffer
+ // until OnReadComplete is called. The buffer must be at least max_bytes in
+ // length.
+ //
+ // The max_bytes parameter is the maximum number of bytes to read.
+ //
+ // The bytes_read parameter is an output parameter containing the
+ // the number of bytes read. A value of 0 indicates that there is no
+ // more data available to read from the stream.
+ //
+ // If a read error occurs, Read returns false and the request->status
+ // will be set to an error.
+ bool Read(IOBuffer* buf, int max_bytes, int* bytes_read);
+
+ // If this request is being cached by the HTTP cache, stop subsequent caching.
+ // Note that this method has no effect on other (simultaneous or not) requests
+ // for the same resource. The typical example is a request that results in
+ // the data being stored to disk (downloaded instead of rendered) so we don't
+ // want to store it twice.
+ void StopCaching();
+
+ // This method may be called to follow a redirect that was deferred in
+ // response to an OnReceivedRedirect call.
+ void FollowDeferredRedirect();
+
+ // One of the following two methods should be called in response to an
+ // OnAuthRequired() callback (and only then).
+ // SetAuth will reissue the request with the given credentials.
+ // CancelAuth will give up and display the error page.
+ void SetAuth(const AuthCredentials& credentials);
+ void CancelAuth();
+
+ // This method can be called after the user selects a client certificate to
+ // instruct this URLRequest to continue with the request with the
+ // certificate. Pass NULL if the user doesn't have a client certificate.
+ void ContinueWithCertificate(X509Certificate* client_cert);
+
+ // This method can be called after some error notifications to instruct this
+ // URLRequest to ignore the current error and continue with the request. To
+ // cancel the request instead, call Cancel().
+ void ContinueDespiteLastError();
+
+ // Used to specify the context (cookie store, cache) for this request.
+ const URLRequestContext* context() const;
+
+ const BoundNetLog& net_log() const { return net_log_; }
+
+ // Returns the expected content size if available
+ int64 GetExpectedContentSize() const;
+
+ // Returns the priority level for this request.
+ RequestPriority priority() const { return priority_; }
+ void set_priority(RequestPriority priority) {
+ DCHECK_GE(priority, MINIMUM_PRIORITY);
+ DCHECK_LT(priority, NUM_PRIORITIES);
+ priority_ = priority;
+ }
+
+ // Returns true iff this request would be internally redirected to HTTPS
+ // due to HSTS. If so, |redirect_url| is rewritten to the new HTTPS URL.
+ bool GetHSTSRedirect(GURL* redirect_url) const;
+
+ // TODO(willchan): Undo this. Only temporarily public.
+ bool has_delegate() const { return delegate_ != NULL; }
+
+ // NOTE(willchan): This is just temporary for debugging
+ // http://crbug.com/90971.
+ // Allows to setting debug info into the URLRequest.
+ void set_stack_trace(const base::debug::StackTrace& stack_trace);
+ const base::debug::StackTrace* stack_trace() const;
+
+ void set_received_response_content_length(int64 received_content_length) {
+ received_response_content_length_ = received_content_length;
+ }
+ int64 received_response_content_length() {
+ return received_response_content_length_;
+ }
+
+ protected:
+ // Allow the URLRequestJob class to control the is_pending() flag.
+ void set_is_pending(bool value) { is_pending_ = value; }
+
+ // Allow the URLRequestJob class to set our status too
+ void set_status(const URLRequestStatus& value) { status_ = value; }
+
+ // Allow the URLRequestJob to redirect this request. Returns OK if
+ // successful, otherwise an error code is returned.
+ int Redirect(const GURL& location, int http_status_code);
+
+ // Called by URLRequestJob to allow interception when a redirect occurs.
+ void NotifyReceivedRedirect(const GURL& location, bool* defer_redirect);
+
+ // Allow an interceptor's URLRequestJob to restart this request.
+ // Should only be called if the original job has not started a response.
+ void Restart();
+
+ private:
+ friend class URLRequestJob;
+
+ // Registers a new protocol handler for the given scheme. If the scheme is
+ // already handled, this will overwrite the given factory. To delete the
+ // protocol factory, use NULL for the factory BUT this WILL NOT put back
+ // any previously registered protocol factory. It will have returned
+ // the previously registered factory (or NULL if none is registered) when
+ // the scheme was first registered so that the caller can manually put it
+ // back if desired.
+ //
+ // The scheme must be all-lowercase ASCII. See the ProtocolFactory
+ // declaration for its requirements.
+ //
+ // The registered protocol factory may return NULL, which will cause the
+ // regular "built-in" protocol factory to be used.
+ //
+ static ProtocolFactory* RegisterProtocolFactory(const std::string& scheme,
+ ProtocolFactory* factory);
+
+ // Registers or unregisters a network interception class.
+ static void RegisterRequestInterceptor(Interceptor* interceptor);
+ static void UnregisterRequestInterceptor(Interceptor* interceptor);
+
+ // Resumes or blocks a request paused by the NetworkDelegate::OnBeforeRequest
+ // handler. If |blocked| is true, the request is blocked and an error page is
+ // returned indicating so. This should only be called after Start is called
+ // and OnBeforeRequest returns true (signalling that the request should be
+ // paused).
+ void BeforeRequestComplete(int error);
+
+ void StartJob(URLRequestJob* job);
+
+ // Restarting involves replacing the current job with a new one such as what
+ // happens when following a HTTP redirect.
+ void RestartWithJob(URLRequestJob* job);
+ void PrepareToRestart();
+
+ // Detaches the job from this request in preparation for this object going
+ // away or the job being replaced. The job will not call us back when it has
+ // been orphaned.
+ void OrphanJob();
+
+ // Cancels the request and set the error and ssl info for this request to the
+ // passed values.
+ void DoCancel(int error, const SSLInfo& ssl_info);
+
+ // Notifies the network delegate that the request has been completed.
+ // This does not imply a successful completion. Also a canceled request is
+ // considered completed.
+ void NotifyRequestCompleted();
+
+ // Called by URLRequestJob to allow interception when the final response
+ // occurs.
+ void NotifyResponseStarted();
+
+ // These functions delegate to |delegate_| and may only be used if
+ // |delegate_| is not NULL. See URLRequest::Delegate for the meaning
+ // of these functions.
+ void NotifyAuthRequired(AuthChallengeInfo* auth_info);
+ void NotifyAuthRequiredComplete(NetworkDelegate::AuthRequiredResponse result);
+ void NotifyCertificateRequested(SSLCertRequestInfo* cert_request_info);
+ void NotifySSLCertificateError(const SSLInfo& ssl_info, bool fatal);
+ void NotifyReadCompleted(int bytes_read);
+
+ // These functions delegate to |network_delegate_| if it is not NULL.
+ // If |network_delegate_| is NULL, cookies can be used unless
+ // SetDefaultCookiePolicyToBlock() has been called.
+ bool CanGetCookies(const CookieList& cookie_list) const;
+ bool CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const;
+
+ // Called when the delegate blocks or unblocks this request when intercepting
+ // certain requests.
+ void SetBlockedOnDelegate();
+ void SetUnblockedOnDelegate();
+
+ // Contextual information used for this request. Cannot be NULL. This contains
+ // most of the dependencies which are shared between requests (disk cache,
+ // cookie store, socket pool, etc.)
+ const URLRequestContext* context_;
+
+ NetworkDelegate* network_delegate_;
+
+ // Tracks the time spent in various load states throughout this request.
+ BoundNetLog net_log_;
+
+ scoped_refptr<URLRequestJob> job_;
+ scoped_ptr<UploadDataStream> upload_data_stream_;
+ std::vector<GURL> url_chain_;
+ GURL first_party_for_cookies_;
+ GURL delegate_redirect_url_;
+ std::string method_; // "GET", "POST", etc. Should be all uppercase.
+ std::string referrer_;
+ ReferrerPolicy referrer_policy_;
+ HttpRequestHeaders extra_request_headers_;
+ int load_flags_; // Flags indicating the request type for the load;
+ // expected values are LOAD_* enums above.
+
+ // Never access methods of the |delegate_| directly. Always use the
+ // Notify... methods for this.
+ Delegate* delegate_;
+
+ // Current error status of the job. When no error has been encountered, this
+ // will be SUCCESS. If multiple errors have been encountered, this will be
+ // the first non-SUCCESS status seen.
+ URLRequestStatus status_;
+
+ // The HTTP response info, lazily initialized.
+ HttpResponseInfo response_info_;
+
+ // Tells us whether the job is outstanding. This is true from the time
+ // Start() is called to the time we dispatch RequestComplete and indicates
+ // whether the job is active.
+ bool is_pending_;
+
+ // Indicates if the request is in the process of redirecting to a new
+ // location. It is true from the time the headers complete until a
+ // new request begins.
+ bool is_redirecting_;
+
+ // Number of times we're willing to redirect. Used to guard against
+ // infinite redirects.
+ int redirect_limit_;
+
+ // Cached value for use after we've orphaned the job handling the
+ // first transaction in a request involving redirects.
+ UploadProgress final_upload_progress_;
+
+ // The priority level for this request. Objects like ClientSocketPool use
+ // this to determine which URLRequest to allocate sockets to first.
+ RequestPriority priority_;
+
+ // TODO(battre): The only consumer of the identifier_ is currently the
+ // web request API. We need to match identifiers of requests between the
+ // web request API and the web navigation API. As the URLRequest does not
+ // exist when the web navigation API is triggered, the tracking probably
+ // needs to be done outside of the URLRequest anyway. Therefore, this
+ // identifier should be deleted here. http://crbug.com/89321
+ // A globally unique identifier for this request.
+ const uint64 identifier_;
+
+ // True if this request is blocked waiting for the network delegate to resume
+ // it.
+ bool blocked_on_delegate_;
+
+ // An optional parameter that provides additional information about the load
+ // state. Only used with the LOAD_STATE_WAITING_FOR_DELEGATE state.
+ string16 load_state_param_;
+
+ base::debug::LeakTracker<URLRequest> leak_tracker_;
+
+ // Callback passed to the network delegate to notify us when a blocked request
+ // is ready to be resumed or canceled.
+ CompletionCallback before_request_callback_;
+
+ // Safe-guard to ensure that we do not send multiple "I am completed"
+ // messages to network delegate.
+ // TODO(battre): Remove this. http://crbug.com/89049
+ bool has_notified_completion_;
+
+ // Authentication data used by the NetworkDelegate for this request,
+ // if one is present. |auth_credentials_| may be filled in when calling
+ // |NotifyAuthRequired| on the NetworkDelegate. |auth_info_| holds
+ // the authentication challenge being handled by |NotifyAuthRequired|.
+ AuthCredentials auth_credentials_;
+ scoped_refptr<AuthChallengeInfo> auth_info_;
+
+ int64 received_response_content_length_;
+
+ base::TimeTicks creation_time_;
+
+ scoped_ptr<const base::debug::StackTrace> stack_trace_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequest);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_H_
diff --git a/src/net/url_request/url_request_about_job.cc b/src/net/url_request/url_request_about_job.cc
new file mode 100644
index 0000000..242a735
--- /dev/null
+++ b/src/net/url_request/url_request_about_job.cc
@@ -0,0 +1,50 @@
+// Copyright (c) 2012 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.
+
+// Simple implementation of about: protocol handler that treats everything as
+// about:blank. No other about: features should be available to web content,
+// so they're not implemented here.
+
+#include "net/url_request/url_request_about_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+
+namespace net {
+
+URLRequestAboutJob::URLRequestAboutJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : URLRequestJob(request, network_delegate),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+// static
+URLRequestJob* URLRequestAboutJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ return new URLRequestAboutJob(request, network_delegate);
+}
+
+void URLRequestAboutJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestAboutJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+bool URLRequestAboutJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = "text/html";
+ return true;
+}
+
+URLRequestAboutJob::~URLRequestAboutJob() {
+}
+
+void URLRequestAboutJob::StartAsync() {
+ NotifyHeadersComplete();
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_about_job.h b/src/net/url_request/url_request_about_job.h
new file mode 100644
index 0000000..ea2b83f
--- /dev/null
+++ b/src/net/url_request/url_request_about_job.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class NET_EXPORT URLRequestAboutJob : public URLRequestJob {
+ public:
+ URLRequestAboutJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ static URLRequest::ProtocolFactory Factory;
+
+ // URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+
+ private:
+ virtual ~URLRequestAboutJob();
+
+ void StartAsync();
+
+ base::WeakPtrFactory<URLRequestAboutJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_ABOUT_JOB_H_
diff --git a/src/net/url_request/url_request_context.cc b/src/net/url_request/url_request_context.cc
new file mode 100644
index 0000000..7dd2bb3
--- /dev/null
+++ b/src/net/url_request/url_request_context.cc
@@ -0,0 +1,128 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_context.h"
+
+#include "base/compiler_specific.h"
+#include "base/debug/alias.h"
+#include "base/debug/stack_trace.h"
+#include "base/string_util.h"
+#include "net/base/host_resolver.h"
+#include "net/cookies/cookie_store.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/url_request/http_user_agent_settings.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+URLRequestContext::URLRequestContext()
+ : net_log_(NULL),
+ host_resolver_(NULL),
+ cert_verifier_(NULL),
+ server_bound_cert_service_(NULL),
+ fraudulent_certificate_reporter_(NULL),
+ http_auth_handler_factory_(NULL),
+ proxy_service_(NULL),
+ network_delegate_(NULL),
+ http_server_properties_(NULL),
+ http_user_agent_settings_(NULL),
+ transport_security_state_(NULL),
+#if !defined(DISABLE_FTP_SUPPORT)
+ ftp_auth_cache_(new FtpAuthCache),
+#endif
+ http_transaction_factory_(NULL),
+ ftp_transaction_factory_(NULL),
+ job_factory_(NULL),
+ throttler_manager_(NULL),
+ url_requests_(new std::set<const URLRequest*>) {
+}
+
+URLRequestContext::~URLRequestContext() {
+#if !defined(__LB_SHELL__FOR_RELEASE__)
+ AssertNoURLRequests();
+#endif
+}
+
+void URLRequestContext::CopyFrom(const URLRequestContext* other) {
+ // Copy URLRequestContext parameters.
+ set_net_log(other->net_log_);
+ set_host_resolver(other->host_resolver_);
+ set_cert_verifier(other->cert_verifier_);
+ set_server_bound_cert_service(other->server_bound_cert_service_);
+ set_fraudulent_certificate_reporter(other->fraudulent_certificate_reporter_);
+ set_http_auth_handler_factory(other->http_auth_handler_factory_);
+ set_proxy_service(other->proxy_service_);
+ set_ssl_config_service(other->ssl_config_service_);
+ set_network_delegate(other->network_delegate_);
+ set_http_server_properties(other->http_server_properties_);
+ set_cookie_store(other->cookie_store_);
+ set_transport_security_state(other->transport_security_state_);
+ // FTPAuthCache is unique per context.
+ set_http_transaction_factory(other->http_transaction_factory_);
+ set_ftp_transaction_factory(other->ftp_transaction_factory_);
+ set_job_factory(other->job_factory_);
+ set_throttler_manager(other->throttler_manager_);
+ set_http_user_agent_settings(other->http_user_agent_settings_);
+}
+
+const HttpNetworkSession::Params* URLRequestContext::GetNetworkSessionParams(
+ ) const {
+ HttpTransactionFactory* transaction_factory = http_transaction_factory();
+ if (!transaction_factory)
+ return NULL;
+ HttpNetworkSession* network_session = transaction_factory->GetSession();
+ if (!network_session)
+ return NULL;
+ return &network_session->params();
+}
+
+URLRequest* URLRequestContext::CreateRequest(
+ const GURL& url, URLRequest::Delegate* delegate) const {
+ return new URLRequest(url, delegate, this, network_delegate_);
+}
+
+void URLRequestContext::set_cookie_store(CookieStore* cookie_store) {
+ cookie_store_ = cookie_store;
+}
+
+std::string URLRequestContext::GetAcceptCharset() const {
+ return http_user_agent_settings_ ?
+ http_user_agent_settings_->GetAcceptCharset() : EmptyString();
+}
+
+std::string URLRequestContext::GetAcceptLanguage() const {
+ return http_user_agent_settings_ ?
+ http_user_agent_settings_->GetAcceptLanguage() : EmptyString();
+}
+
+std::string URLRequestContext::GetUserAgent() const {
+ return http_user_agent_settings_ ? http_user_agent_settings_->GetUserAgent()
+ : EmptyString();
+}
+
+void URLRequestContext::AssertNoURLRequests() const {
+ int num_requests = url_requests_->size();
+ if (num_requests != 0) {
+ // We're leaking URLRequests :( Dump the URL of the first one and record how
+ // many we leaked so we have an idea of how bad it is.
+ char url_buf[128];
+ const URLRequest* request = *url_requests_->begin();
+ base::strlcpy(url_buf, request->url().spec().c_str(), arraysize(url_buf));
+ bool has_delegate = request->has_delegate();
+ int load_flags = request->load_flags();
+ base::debug::StackTrace stack_trace(NULL, 0);
+ if (request->stack_trace())
+ stack_trace = *request->stack_trace();
+ base::debug::Alias(url_buf);
+ base::debug::Alias(&num_requests);
+ base::debug::Alias(&has_delegate);
+ base::debug::Alias(&load_flags);
+ base::debug::Alias(&stack_trace);
+ CHECK(false) << "Leaked " << num_requests << " URLRequest(s). First URL: "
+ << request->url().spec().c_str() << ".";
+ }
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_context.h b/src/net/url_request/url_request_context.h
new file mode 100644
index 0000000..f843789
--- /dev/null
+++ b/src/net/url_request/url_request_context.h
@@ -0,0 +1,264 @@
+// Copyright (c) 2012 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.
+
+// This class represents contextual information (cookies, cache, etc.)
+// that's useful when processing resource requests.
+// The class is reference-counted so that it can be cleaned up after any
+// requests that are using it have been completed.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
+#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
+
+#include <set>
+#include <string>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/ssl_config_service.h"
+#include "net/base/transport_security_state.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties.h"
+#include "net/ftp/ftp_auth_cache.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+class CertVerifier;
+class CookieStore;
+class FraudulentCertificateReporter;
+class FtpTransactionFactory;
+class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpTransactionFactory;
+class HttpUserAgentSettings;
+class NetworkDelegate;
+class ServerBoundCertService;
+class ProxyService;
+class URLRequest;
+class URLRequestJobFactory;
+class URLRequestThrottlerManager;
+
+// Subclass to provide application-specific context for URLRequest
+// instances. Note that URLRequestContext typically does not provide storage for
+// these member variables, since they may be shared. For the ones that aren't
+// shared, URLRequestContextStorage can be helpful in defining their storage.
+class NET_EXPORT URLRequestContext
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ URLRequestContext();
+ virtual ~URLRequestContext();
+
+ // Copies the state from |other| into this context.
+ void CopyFrom(const URLRequestContext* other);
+
+ // May return NULL if this context doesn't have an associated network session.
+ const HttpNetworkSession::Params* GetNetworkSessionParams() const;
+
+ URLRequest* CreateRequest(
+ const GURL& url, URLRequest::Delegate* delegate) const;
+
+ NetLog* net_log() const {
+ return net_log_;
+ }
+
+ void set_net_log(NetLog* net_log) {
+ net_log_ = net_log;
+ }
+
+ HostResolver* host_resolver() const {
+ return host_resolver_;
+ }
+
+ void set_host_resolver(HostResolver* host_resolver) {
+ host_resolver_ = host_resolver;
+ }
+
+ CertVerifier* cert_verifier() const {
+ return cert_verifier_;
+ }
+
+ void set_cert_verifier(CertVerifier* cert_verifier) {
+ cert_verifier_ = cert_verifier;
+ }
+
+ ServerBoundCertService* server_bound_cert_service() const {
+ return server_bound_cert_service_;
+ }
+
+ void set_server_bound_cert_service(
+ ServerBoundCertService* server_bound_cert_service) {
+ server_bound_cert_service_ = server_bound_cert_service;
+ }
+
+ FraudulentCertificateReporter* fraudulent_certificate_reporter() const {
+ return fraudulent_certificate_reporter_;
+ }
+ void set_fraudulent_certificate_reporter(
+ FraudulentCertificateReporter* fraudulent_certificate_reporter) {
+ fraudulent_certificate_reporter_ = fraudulent_certificate_reporter;
+ }
+
+ // Get the proxy service for this context.
+ ProxyService* proxy_service() const { return proxy_service_; }
+ void set_proxy_service(ProxyService* proxy_service) {
+ proxy_service_ = proxy_service;
+ }
+
+ // Get the ssl config service for this context.
+ SSLConfigService* ssl_config_service() const { return ssl_config_service_; }
+ void set_ssl_config_service(SSLConfigService* service) {
+ ssl_config_service_ = service;
+ }
+
+ // Gets the HTTP Authentication Handler Factory for this context.
+ // The factory is only valid for the lifetime of this URLRequestContext
+ HttpAuthHandlerFactory* http_auth_handler_factory() const {
+ return http_auth_handler_factory_;
+ }
+ void set_http_auth_handler_factory(HttpAuthHandlerFactory* factory) {
+ http_auth_handler_factory_ = factory;
+ }
+
+ // Gets the http transaction factory for this context.
+ HttpTransactionFactory* http_transaction_factory() const {
+ return http_transaction_factory_;
+ }
+ void set_http_transaction_factory(HttpTransactionFactory* factory) {
+ http_transaction_factory_ = factory;
+ }
+
+ // Gets the ftp transaction factory for this context.
+ FtpTransactionFactory* ftp_transaction_factory() const {
+ return ftp_transaction_factory_;
+ }
+ void set_ftp_transaction_factory(FtpTransactionFactory* factory) {
+ ftp_transaction_factory_ = factory;
+ }
+
+ void set_network_delegate(NetworkDelegate* network_delegate) {
+ network_delegate_ = network_delegate;
+ }
+ NetworkDelegate* network_delegate() const { return network_delegate_; }
+
+ void set_http_server_properties(
+ HttpServerProperties* http_server_properties) {
+ http_server_properties_ = http_server_properties;
+ }
+ HttpServerProperties* http_server_properties() const {
+ return http_server_properties_;
+ }
+
+ // Gets the cookie store for this context (may be null, in which case
+ // cookies are not stored).
+ CookieStore* cookie_store() const { return cookie_store_.get(); }
+ void set_cookie_store(CookieStore* cookie_store);
+
+ TransportSecurityState* transport_security_state() const {
+ return transport_security_state_;
+ }
+ void set_transport_security_state(
+ TransportSecurityState* state) {
+ transport_security_state_ = state;
+ }
+
+ // Gets the FTP authentication cache for this context.
+ FtpAuthCache* ftp_auth_cache() const {
+#if !defined(DISABLE_FTP_SUPPORT)
+ return ftp_auth_cache_.get();
+#else
+ return NULL;
+#endif
+ }
+
+ // ---------------------------------------------------------------------------
+ // Legacy accessors that delegate to http_user_agent_settings_.
+ // TODO(pauljensen): Remove after all clients are updated to directly access
+ // http_user_agent_settings_.
+ // Gets the value of 'Accept-Charset' header field.
+ std::string GetAcceptCharset() const;
+ // Gets the value of 'Accept-Language' header field.
+ std::string GetAcceptLanguage() const;
+ // Gets the UA string.
+ std::string GetUserAgent() const;
+ // ---------------------------------------------------------------------------
+
+ const URLRequestJobFactory* job_factory() const { return job_factory_; }
+ void set_job_factory(const URLRequestJobFactory* job_factory) {
+ job_factory_ = job_factory;
+ }
+
+ // May be NULL.
+ URLRequestThrottlerManager* throttler_manager() const {
+ return throttler_manager_;
+ }
+ void set_throttler_manager(URLRequestThrottlerManager* throttler_manager) {
+ throttler_manager_ = throttler_manager;
+ }
+
+ // Gets the URLRequest objects that hold a reference to this
+ // URLRequestContext.
+ std::set<const URLRequest*>* url_requests() const {
+ return url_requests_.get();
+ }
+
+ void AssertNoURLRequests() const;
+
+ // Get the underlying |HttpUserAgentSettings| implementation that provides
+ // the HTTP Accept-Language, Accept-Charset and User-Agent header values.
+ const HttpUserAgentSettings* http_user_agent_settings() const {
+ return http_user_agent_settings_;
+ }
+ void set_http_user_agent_settings(
+ HttpUserAgentSettings* http_user_agent_settings) {
+ http_user_agent_settings_ = http_user_agent_settings;
+ }
+
+ private:
+ // ---------------------------------------------------------------------------
+ // Important: When adding any new members below, consider whether they need to
+ // be added to CopyFrom.
+ // ---------------------------------------------------------------------------
+
+ // Ownership for these members are not defined here. Clients should either
+ // provide storage elsewhere or have a subclass take ownership.
+ NetLog* net_log_;
+ HostResolver* host_resolver_;
+ CertVerifier* cert_verifier_;
+ ServerBoundCertService* server_bound_cert_service_;
+ FraudulentCertificateReporter* fraudulent_certificate_reporter_;
+ HttpAuthHandlerFactory* http_auth_handler_factory_;
+ ProxyService* proxy_service_;
+ scoped_refptr<SSLConfigService> ssl_config_service_;
+ NetworkDelegate* network_delegate_;
+ HttpServerProperties* http_server_properties_;
+ HttpUserAgentSettings* http_user_agent_settings_;
+ scoped_refptr<CookieStore> cookie_store_;
+ TransportSecurityState* transport_security_state_;
+#if !defined(DISABLE_FTP_SUPPORT)
+ scoped_ptr<FtpAuthCache> ftp_auth_cache_;
+#endif
+ // The charset of the referrer where this request comes from. It's not
+ // used in communication with a server but is used to construct a suggested
+ // filename for file download.
+ HttpTransactionFactory* http_transaction_factory_;
+ FtpTransactionFactory* ftp_transaction_factory_;
+ const URLRequestJobFactory* job_factory_;
+ URLRequestThrottlerManager* throttler_manager_;
+
+ // ---------------------------------------------------------------------------
+ // Important: When adding any new members below, consider whether they need to
+ // be added to CopyFrom.
+ // ---------------------------------------------------------------------------
+
+ scoped_ptr<std::set<const URLRequest*> > url_requests_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContext);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_H_
diff --git a/src/net/url_request/url_request_context_builder.cc b/src/net/url_request/url_request_context_builder.cc
new file mode 100644
index 0000000..232ed22
--- /dev/null
+++ b/src/net/url_request/url_request_context_builder.cc
@@ -0,0 +1,304 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_context_builder.h"
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/string_util.h"
+#include "base/threading/thread.h"
+#include "base/thread_task_runner_handle.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/base/transport_security_state.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/static_http_user_agent_settings.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_storage.h"
+
+namespace net {
+
+namespace {
+
+class BasicNetworkDelegate : public NetworkDelegate {
+ public:
+ BasicNetworkDelegate() {}
+ virtual ~BasicNetworkDelegate() {}
+
+ private:
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE {
+ return OK;
+ }
+
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) OVERRIDE {}
+
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers)
+ OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) OVERRIDE {}
+
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE {}
+
+ virtual void OnRawBytesRead(const URLRequest& request,
+ int bytes_read) OVERRIDE {}
+
+ virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE {}
+
+ virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE {}
+
+ virtual void OnPACScriptError(int line_number,
+ const string16& error) OVERRIDE {}
+
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE {
+ return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanAccessFile(const net::URLRequest& request,
+ const FilePath& path) const OVERRIDE {
+ return true;
+ }
+
+ virtual bool OnCanThrottleRequest(const URLRequest& request) const OVERRIDE {
+ return false;
+ }
+
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE {
+ return OK;
+ }
+
+ virtual void OnRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state) OVERRIDE {
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(BasicNetworkDelegate);
+};
+
+class BasicURLRequestContext : public URLRequestContext {
+ public:
+ BasicURLRequestContext()
+ : cache_thread_("Cache Thread"),
+ file_thread_("File Thread"),
+ ALLOW_THIS_IN_INITIALIZER_LIST(storage_(this)) {}
+
+ URLRequestContextStorage* storage() {
+ return &storage_;
+ }
+
+ void StartCacheThread() {
+ cache_thread_.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_IO, 0));
+ }
+
+ scoped_refptr<base::MessageLoopProxy> cache_message_loop_proxy() {
+ DCHECK(cache_thread_.IsRunning());
+ return cache_thread_.message_loop_proxy();
+ }
+
+ void StartFileThread() {
+ file_thread_.StartWithOptions(
+ base::Thread::Options(MessageLoop::TYPE_DEFAULT, 0));
+ }
+
+ MessageLoop* file_message_loop() {
+ DCHECK(file_thread_.IsRunning());
+ return file_thread_.message_loop();
+ }
+
+ protected:
+ virtual ~BasicURLRequestContext() {}
+
+ private:
+ base::Thread cache_thread_;
+ base::Thread file_thread_;
+ URLRequestContextStorage storage_;
+ DISALLOW_COPY_AND_ASSIGN(BasicURLRequestContext);
+};
+
+} // namespace
+
+URLRequestContextBuilder::HttpCacheParams::HttpCacheParams()
+ : type(IN_MEMORY),
+ max_size(0) {}
+URLRequestContextBuilder::HttpCacheParams::~HttpCacheParams() {}
+
+URLRequestContextBuilder::HttpNetworkSessionParams::HttpNetworkSessionParams()
+ : ignore_certificate_errors(false),
+ host_mapping_rules(NULL),
+ http_pipelining_enabled(false),
+ testing_fixed_http_port(0),
+ testing_fixed_https_port(0),
+ trusted_spdy_proxy() {}
+
+URLRequestContextBuilder::HttpNetworkSessionParams::~HttpNetworkSessionParams()
+{}
+
+URLRequestContextBuilder::URLRequestContextBuilder()
+ : ftp_enabled_(false),
+ http_cache_enabled_(true) {}
+URLRequestContextBuilder::~URLRequestContextBuilder() {}
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+void URLRequestContextBuilder::set_proxy_config_service(
+ ProxyConfigService* proxy_config_service) {
+ proxy_config_service_.reset(proxy_config_service);
+}
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+URLRequestContext* URLRequestContextBuilder::Build() {
+#if __LB_ENABLE_NATIVE_HTTP_STACK__
+ return NULL;
+#else
+ BasicURLRequestContext* context = new BasicURLRequestContext;
+ URLRequestContextStorage* storage = context->storage();
+
+ storage->set_http_user_agent_settings(new StaticHttpUserAgentSettings(
+ accept_language_, accept_charset_, user_agent_));
+
+ if (!network_delegate_)
+ network_delegate_.reset(new BasicNetworkDelegate);
+ storage->set_network_delegate(network_delegate_.release());
+
+ storage->set_host_resolver(net::HostResolver::CreateDefaultResolver(NULL));
+
+#if !DISABLE_FTP_SUPPORT
+ if (ftp_enabled_) {
+ storage->set_ftp_transaction_factory(
+ new FtpNetworkLayer(context->host_resolver()));
+ }
+#endif
+
+ context->StartFileThread();
+
+ // TODO(willchan): Switch to using this code when
+ // ProxyService::CreateSystemProxyConfigService()'s signature doesn't suck.
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ ProxyConfigService* proxy_config_service = proxy_config_service_.release();
+#else
+ ProxyConfigService* proxy_config_service =
+ ProxyService::CreateSystemProxyConfigService(
+ base::ThreadTaskRunnerHandle::Get(),
+ context->file_message_loop());
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+ storage->set_proxy_service(
+ ProxyService::CreateUsingSystemProxyResolver(
+ proxy_config_service,
+ 4, // TODO(willchan): Find a better constant somewhere.
+ context->net_log()));
+ storage->set_ssl_config_service(new net::SSLConfigServiceDefaults);
+ storage->set_http_auth_handler_factory(
+ net::HttpAuthHandlerRegistryFactory::CreateDefault(
+ context->host_resolver()));
+ storage->set_cookie_store(new CookieMonster(NULL, NULL));
+ storage->set_transport_security_state(new net::TransportSecurityState());
+ storage->set_http_server_properties(new net::HttpServerPropertiesImpl);
+ storage->set_cert_verifier(CertVerifier::CreateDefault());
+
+ net::HttpNetworkSession::Params network_session_params;
+ network_session_params.host_resolver = context->host_resolver();
+ network_session_params.cert_verifier = context->cert_verifier();
+ network_session_params.transport_security_state =
+ context->transport_security_state();
+ network_session_params.proxy_service = context->proxy_service();
+ network_session_params.ssl_config_service =
+ context->ssl_config_service();
+ network_session_params.http_auth_handler_factory =
+ context->http_auth_handler_factory();
+ network_session_params.network_delegate =
+ context->network_delegate();
+ network_session_params.http_server_properties =
+ context->http_server_properties();
+ network_session_params.net_log = context->net_log();
+ network_session_params.ignore_certificate_errors =
+ http_network_session_params_.ignore_certificate_errors;
+ network_session_params.host_mapping_rules =
+ http_network_session_params_.host_mapping_rules;
+ network_session_params.http_pipelining_enabled =
+ http_network_session_params_.http_pipelining_enabled;
+ network_session_params.testing_fixed_http_port =
+ http_network_session_params_.testing_fixed_http_port;
+ network_session_params.testing_fixed_https_port =
+ http_network_session_params_.testing_fixed_https_port;
+ network_session_params.trusted_spdy_proxy =
+ http_network_session_params_.trusted_spdy_proxy;
+
+ HttpTransactionFactory* http_transaction_factory = NULL;
+ if (http_cache_enabled_) {
+ network_session_params.server_bound_cert_service =
+ context->server_bound_cert_service();
+ HttpCache::BackendFactory* http_cache_backend = NULL;
+ if (http_cache_params_.type == HttpCacheParams::DISK) {
+ context->StartCacheThread();
+ http_cache_backend =
+ new HttpCache::DefaultBackend(DISK_CACHE,
+ http_cache_params_.path,
+ http_cache_params_.max_size,
+ context->cache_message_loop_proxy());
+ } else {
+ http_cache_backend =
+ HttpCache::DefaultBackend::InMemory(http_cache_params_.max_size);
+ }
+
+ http_transaction_factory = new HttpCache(
+ network_session_params, http_cache_backend);
+ } else {
+ scoped_refptr<net::HttpNetworkSession> network_session(
+ new net::HttpNetworkSession(network_session_params));
+
+ http_transaction_factory = new HttpNetworkLayer(network_session);
+ }
+ storage->set_http_transaction_factory(http_transaction_factory);
+
+ // TODO(willchan): Support sdch.
+
+ return context;
+#endif
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_context_builder.h b/src/net/url_request/url_request_context_builder.h
new file mode 100644
index 0000000..fd21f8a
--- /dev/null
+++ b/src/net/url_request/url_request_context_builder.h
@@ -0,0 +1,135 @@
+// Copyright (c) 2012 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.
+
+// This class is useful for building a simple URLRequestContext. Most creators
+// of new URLRequestContexts should use this helper class to construct it. Call
+// any configuration params, and when done, invoke Build() to construct the
+// URLRequestContext. This URLRequestContext will own all its own storage.
+//
+// URLRequestContextBuilder and its associated params classes are initially
+// populated with "sane" default values. Read through the comments to figure out
+// what these are.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_BUILDER_H_
+#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_BUILDER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/file_path.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "build/build_config.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class HostMappingRules;
+class ProxyConfigService;
+class URLRequestContext;
+class NetworkDelegate;
+
+class NET_EXPORT URLRequestContextBuilder {
+ public:
+ struct NET_EXPORT HttpCacheParams {
+ enum Type {
+ IN_MEMORY,
+ DISK,
+ };
+
+ HttpCacheParams();
+ ~HttpCacheParams();
+
+ // The type of HTTP cache. Default is IN_MEMORY.
+ Type type;
+
+ // The max size of the cache in bytes. Default is algorithmically determined
+ // based off available disk space.
+ int max_size;
+
+ // The cache path (when type is DISK).
+ FilePath path;
+ };
+
+ struct NET_EXPORT HttpNetworkSessionParams {
+ HttpNetworkSessionParams();
+ ~HttpNetworkSessionParams();
+
+ // These fields mirror those in net::HttpNetworkSession::Params;
+ bool ignore_certificate_errors;
+ HostMappingRules* host_mapping_rules;
+ bool http_pipelining_enabled;
+ uint16 testing_fixed_http_port;
+ uint16 testing_fixed_https_port;
+ std::string trusted_spdy_proxy;
+ };
+
+ URLRequestContextBuilder();
+ ~URLRequestContextBuilder();
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ void set_proxy_config_service(ProxyConfigService* proxy_config_service);
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+ // Call these functions to specify hard-coded Accept-Language,
+ // Accept-Charset, or User-Agent header values for all requests that don't
+ // have the headers already set.
+ void set_accept_language(const std::string& accept_language) {
+ accept_language_ = accept_language;
+ }
+ void set_accept_charset(const std::string& accept_charset) {
+ accept_charset_ = accept_charset;
+ }
+ void set_user_agent(const std::string& user_agent) {
+ user_agent_ = user_agent;
+ }
+
+ // By default it's disabled.
+ void set_ftp_enabled(bool enable) {
+ ftp_enabled_ = enable;
+ }
+
+ // Uses BasicNetworkDelegate by default. Note that calling Build will unset
+ // any custom delegate in builder, so this must be called each time before
+ // Build is called.
+ void set_network_delegate(NetworkDelegate* delegate) {
+ network_delegate_.reset(delegate);
+ }
+
+ // By default HttpCache is enabled with a default constructed HttpCacheParams.
+ void EnableHttpCache(const HttpCacheParams& params) {
+ http_cache_params_ = params;
+ }
+
+ void DisableHttpCache() {
+ http_cache_params_ = HttpCacheParams();
+ }
+
+ // Override default net::HttpNetworkSession::Params settings.
+ void set_http_network_session_params(
+ const HttpNetworkSessionParams& http_network_session_params) {
+ http_network_session_params_ = http_network_session_params;
+ }
+
+ URLRequestContext* Build();
+
+ private:
+ std::string accept_language_;
+ std::string accept_charset_;
+ std::string user_agent_;
+ bool ftp_enabled_;
+ bool http_cache_enabled_;
+ HttpCacheParams http_cache_params_;
+ HttpNetworkSessionParams http_network_session_params_;
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ scoped_ptr<ProxyConfigService> proxy_config_service_;
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+ scoped_ptr<NetworkDelegate> network_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContextBuilder);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_BUILDER_H_
diff --git a/src/net/url_request/url_request_context_builder_unittest.cc b/src/net/url_request/url_request_context_builder_unittest.cc
new file mode 100644
index 0000000..bc93416
--- /dev/null
+++ b/src/net/url_request/url_request_context_builder_unittest.cc
@@ -0,0 +1,83 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_context_builder.h"
+
+#include "build/build_config.h"
+#include "net/test/test_server.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+#include "net/proxy/proxy_config.h"
+#include "net/proxy/proxy_config_service_fixed.h"
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+
+namespace net {
+
+namespace {
+
+// A subclass of TestServer that uses a statically-configured hostname. This is
+// to work around mysterious failures in chrome_frame_net_tests. See:
+// http://crbug.com/114369
+class LocalHttpTestServer : public TestServer {
+ public:
+ explicit LocalHttpTestServer(const FilePath& document_root)
+ : TestServer(TestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ document_root) {}
+ LocalHttpTestServer()
+ : TestServer(TestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ FilePath()) {}
+};
+
+class URLRequestContextBuilderTest : public PlatformTest {
+ protected:
+ URLRequestContextBuilderTest()
+ : test_server_(
+ FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest"))) {
+#if defined(OS_LINUX) || defined(OS_ANDROID)
+ builder_.set_proxy_config_service(
+ new ProxyConfigServiceFixed(ProxyConfig::CreateDirect()));
+#endif // defined(OS_LINUX) || defined(OS_ANDROID)
+ }
+
+ LocalHttpTestServer test_server_;
+ URLRequestContextBuilder builder_;
+};
+
+TEST_F(URLRequestContextBuilderTest, DefaultSettings) {
+ ASSERT_TRUE(test_server_.Start());
+
+ scoped_ptr<URLRequestContext> context(builder_.Build());
+ TestDelegate delegate;
+ URLRequest request(
+ test_server_.GetURL("echoheader?Foo"), &delegate, context.get());
+ request.set_method("GET");
+ request.SetExtraRequestHeaderByName("Foo", "Bar", false);
+ request.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("Bar", delegate.data_received());
+}
+
+TEST_F(URLRequestContextBuilderTest, UserAgent) {
+ ASSERT_TRUE(test_server_.Start());
+
+ builder_.set_user_agent("Bar");
+ scoped_ptr<URLRequestContext> context(builder_.Build());
+ TestDelegate delegate;
+ URLRequest request(
+ test_server_.GetURL("echoheader?User-Agent"), &delegate, context.get());
+ request.set_method("GET");
+ request.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("Bar", delegate.data_received());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/src/net/url_request/url_request_context_getter.cc b/src/net/url_request/url_request_context_getter.cc
new file mode 100644
index 0000000..44b7bf4
--- /dev/null
+++ b/src/net/url_request/url_request_context_getter.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_context_getter.h"
+
+#include "base/location.h"
+#include "base/single_thread_task_runner.h"
+#include "net/url_request/url_request_context.h"
+
+namespace net {
+
+URLRequestContextGetter::URLRequestContextGetter() {}
+
+URLRequestContextGetter::~URLRequestContextGetter() {}
+
+void URLRequestContextGetter::OnDestruct() const {
+ scoped_refptr<base::SingleThreadTaskRunner> network_task_runner =
+ GetNetworkTaskRunner();
+ DCHECK(network_task_runner);
+ if (network_task_runner) {
+ if (network_task_runner->BelongsToCurrentThread()) {
+ delete this;
+ } else {
+ if (!network_task_runner->DeleteSoon(FROM_HERE, this)) {
+ // Can't force-delete the object here, because some derived classes
+ // can only be deleted on the owning thread, so just emit a warning to
+ // aid in debugging.
+ DLOG(WARNING) << "URLRequestContextGetter leaking due to no owning"
+ << " thread.";
+ }
+ }
+ }
+ // If no IO message loop proxy was available, we will just leak memory.
+ // This is also true if the IO thread is gone.
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_context_getter.h b/src/net/url_request/url_request_context_getter.h
new file mode 100644
index 0000000..4c8e4af
--- /dev/null
+++ b/src/net/url_request/url_request_context_getter.h
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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.
+
+#ifndef CHROME_COMMON_NET_URL_REQUEST_CONTEXT_GETTER_H_
+#define CHROME_COMMON_NET_URL_REQUEST_CONTEXT_GETTER_H_
+
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner_helpers.h"
+#include "net/base/net_export.h"
+
+namespace base {
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace net {
+class CookieStore;
+class URLRequestContext;
+
+struct URLRequestContextGetterTraits;
+
+// Interface for retrieving an net::URLRequestContext.
+class NET_EXPORT URLRequestContextGetter
+ : public base::RefCountedThreadSafe<URLRequestContextGetter,
+ URLRequestContextGetterTraits> {
+ public:
+ virtual URLRequestContext* GetURLRequestContext() = 0;
+
+ // Returns a SingleThreadTaskRunner corresponding to the thread on
+ // which the network IO happens (the thread on which the returned
+ // net::URLRequestContext may be used).
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetNetworkTaskRunner() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<URLRequestContextGetter,
+ URLRequestContextGetterTraits>;
+ friend class base::DeleteHelper<URLRequestContextGetter>;
+ friend struct URLRequestContextGetterTraits;
+
+ URLRequestContextGetter();
+ virtual ~URLRequestContextGetter();
+
+ private:
+ // OnDestruct is meant to ensure deletion on the thread on which the request
+ // IO happens.
+ void OnDestruct() const;
+};
+
+struct URLRequestContextGetterTraits {
+ static void Destruct(const URLRequestContextGetter* context_getter) {
+ context_getter->OnDestruct();
+ }
+};
+
+} // namespace net
+
+#endif // CHROME_COMMON_NET_URL_REQUEST_CONTEXT_GETTER_H_
diff --git a/src/net/url_request/url_request_context_storage.cc b/src/net/url_request/url_request_context_storage.cc
new file mode 100644
index 0000000..cc57078
--- /dev/null
+++ b/src/net/url_request/url_request_context_storage.cc
@@ -0,0 +1,133 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_context_storage.h"
+
+#include "base/logging.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/host_resolver.h"
+#include "net/base/net_log.h"
+#include "net/base/network_delegate.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/cookies/cookie_store.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_server_properties.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/fraudulent_certificate_reporter.h"
+#include "net/url_request/http_user_agent_settings.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job_factory.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace net {
+
+URLRequestContextStorage::URLRequestContextStorage(URLRequestContext* context)
+ : context_(context) {
+ DCHECK(context);
+}
+
+URLRequestContextStorage::~URLRequestContextStorage() {}
+
+void URLRequestContextStorage::set_net_log(NetLog* net_log) {
+ context_->set_net_log(net_log);
+ net_log_.reset(net_log);
+}
+
+void URLRequestContextStorage::set_host_resolver(
+ scoped_ptr<HostResolver> host_resolver) {
+ context_->set_host_resolver(host_resolver.get());
+ host_resolver_ = host_resolver.Pass();
+}
+
+void URLRequestContextStorage::set_cert_verifier(CertVerifier* cert_verifier) {
+ context_->set_cert_verifier(cert_verifier);
+ cert_verifier_.reset(cert_verifier);
+}
+
+void URLRequestContextStorage::set_server_bound_cert_service(
+ ServerBoundCertService* server_bound_cert_service) {
+ context_->set_server_bound_cert_service(server_bound_cert_service);
+ server_bound_cert_service_.reset(server_bound_cert_service);
+}
+
+void URLRequestContextStorage::set_fraudulent_certificate_reporter(
+ FraudulentCertificateReporter* fraudulent_certificate_reporter) {
+ context_->set_fraudulent_certificate_reporter(
+ fraudulent_certificate_reporter);
+ fraudulent_certificate_reporter_.reset(fraudulent_certificate_reporter);
+}
+
+void URLRequestContextStorage::set_http_auth_handler_factory(
+ HttpAuthHandlerFactory* http_auth_handler_factory) {
+ context_->set_http_auth_handler_factory(http_auth_handler_factory);
+ http_auth_handler_factory_.reset(http_auth_handler_factory);
+}
+
+void URLRequestContextStorage::set_proxy_service(ProxyService* proxy_service) {
+ context_->set_proxy_service(proxy_service);
+ proxy_service_.reset(proxy_service);
+}
+
+void URLRequestContextStorage::set_ssl_config_service(
+ SSLConfigService* ssl_config_service) {
+ context_->set_ssl_config_service(ssl_config_service);
+ ssl_config_service_ = ssl_config_service;
+}
+
+void URLRequestContextStorage::set_network_delegate(
+ NetworkDelegate* network_delegate) {
+ context_->set_network_delegate(network_delegate);
+ network_delegate_.reset(network_delegate);
+}
+
+void URLRequestContextStorage::set_http_server_properties(
+ HttpServerProperties* http_server_properties) {
+ context_->set_http_server_properties(http_server_properties);
+ http_server_properties_.reset(http_server_properties);
+}
+
+void URLRequestContextStorage::set_cookie_store(CookieStore* cookie_store) {
+ context_->set_cookie_store(cookie_store);
+ cookie_store_ = cookie_store;
+}
+
+void URLRequestContextStorage::set_transport_security_state(
+ TransportSecurityState* transport_security_state) {
+ context_->set_transport_security_state(transport_security_state);
+ transport_security_state_.reset(transport_security_state);
+}
+
+void URLRequestContextStorage::set_http_transaction_factory(
+ HttpTransactionFactory* http_transaction_factory) {
+ context_->set_http_transaction_factory(http_transaction_factory);
+ http_transaction_factory_.reset(http_transaction_factory);
+}
+
+void URLRequestContextStorage::set_ftp_transaction_factory(
+ FtpTransactionFactory* ftp_transaction_factory) {
+ context_->set_ftp_transaction_factory(ftp_transaction_factory);
+ ftp_transaction_factory_.reset(ftp_transaction_factory);
+}
+
+void URLRequestContextStorage::set_job_factory(
+ URLRequestJobFactory* job_factory) {
+ context_->set_job_factory(job_factory);
+ job_factory_.reset(job_factory);
+}
+
+void URLRequestContextStorage::set_throttler_manager(
+ URLRequestThrottlerManager* throttler_manager) {
+ context_->set_throttler_manager(throttler_manager);
+ throttler_manager_.reset(throttler_manager);
+}
+
+void URLRequestContextStorage::set_http_user_agent_settings(
+ HttpUserAgentSettings* http_user_agent_settings) {
+ context_->set_http_user_agent_settings(http_user_agent_settings);
+ http_user_agent_settings_.reset(http_user_agent_settings);
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_context_storage.h b/src/net/url_request/url_request_context_storage.h
new file mode 100644
index 0000000..62f4c2a
--- /dev/null
+++ b/src/net/url_request/url_request_context_storage.h
@@ -0,0 +1,104 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_CONTEXT_STORAGE_H_
+#define NET_URL_REQUEST_URL_REQUEST_CONTEXT_STORAGE_H_
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class CertVerifier;
+class CookieStore;
+class FraudulentCertificateReporter;
+class FtpTransactionFactory;
+class HostResolver;
+class HttpAuthHandlerFactory;
+class HttpServerProperties;
+class HttpTransactionFactory;
+class HttpUserAgentSettings;
+class NetLog;
+class NetworkDelegate;
+class ServerBoundCertService;
+class ProxyService;
+class SSLConfigService;
+class TransportSecurityState;
+class URLRequestContext;
+class URLRequestJobFactory;
+class URLRequestThrottlerManager;
+
+// URLRequestContextStorage is a helper class that provides storage for unowned
+// member variables of URLRequestContext.
+class NET_EXPORT URLRequestContextStorage {
+ public:
+ // Note that URLRequestContextStorage does not acquire a reference to
+ // URLRequestContext, since it is often designed to be embedded in a
+ // URLRequestContext subclass.
+ explicit URLRequestContextStorage(URLRequestContext* context);
+ ~URLRequestContextStorage();
+
+ // These setters will set both the member variables and call the setter on the
+ // URLRequestContext object. In all cases, ownership is passed to |this|.
+
+ void set_net_log(NetLog* net_log);
+ void set_host_resolver(scoped_ptr<HostResolver> host_resolver);
+ void set_cert_verifier(CertVerifier* cert_verifier);
+ void set_server_bound_cert_service(
+ ServerBoundCertService* server_bound_cert_service);
+ void set_fraudulent_certificate_reporter(
+ FraudulentCertificateReporter* fraudulent_certificate_reporter);
+ void set_http_auth_handler_factory(
+ HttpAuthHandlerFactory* http_auth_handler_factory);
+ void set_proxy_service(ProxyService* proxy_service);
+ void set_ssl_config_service(SSLConfigService* ssl_config_service);
+ void set_network_delegate(NetworkDelegate* network_delegate);
+ void set_http_server_properties(HttpServerProperties* http_server_properties);
+ void set_cookie_store(CookieStore* cookie_store);
+ void set_transport_security_state(
+ TransportSecurityState* transport_security_state);
+ void set_http_transaction_factory(
+ HttpTransactionFactory* http_transaction_factory);
+ void set_ftp_transaction_factory(
+ FtpTransactionFactory* ftp_transaction_factory);
+ void set_job_factory(URLRequestJobFactory* job_factory);
+ void set_throttler_manager(URLRequestThrottlerManager* throttler_manager);
+ void set_http_user_agent_settings(
+ HttpUserAgentSettings* http_user_agent_settings);
+
+ private:
+ // We use a raw pointer to prevent reference cycles, since
+ // URLRequestContextStorage can often be contained within a URLRequestContext
+ // subclass.
+ URLRequestContext* const context_;
+
+ // Owned members.
+ scoped_ptr<NetLog> net_log_;
+ scoped_ptr<HostResolver> host_resolver_;
+ scoped_ptr<CertVerifier> cert_verifier_;
+ scoped_ptr<ServerBoundCertService> server_bound_cert_service_;
+ scoped_ptr<FraudulentCertificateReporter> fraudulent_certificate_reporter_;
+ scoped_ptr<HttpAuthHandlerFactory> http_auth_handler_factory_;
+ scoped_ptr<ProxyService> proxy_service_;
+ // TODO(willchan): Remove refcounting on these members.
+ scoped_refptr<SSLConfigService> ssl_config_service_;
+ scoped_ptr<NetworkDelegate> network_delegate_;
+ scoped_ptr<HttpServerProperties> http_server_properties_;
+ scoped_ptr<HttpUserAgentSettings> http_user_agent_settings_;
+ scoped_refptr<CookieStore> cookie_store_;
+ scoped_ptr<TransportSecurityState> transport_security_state_;
+
+ scoped_ptr<HttpTransactionFactory> http_transaction_factory_;
+ scoped_ptr<FtpTransactionFactory> ftp_transaction_factory_;
+ scoped_ptr<URLRequestJobFactory> job_factory_;
+ scoped_ptr<URLRequestThrottlerManager> throttler_manager_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestContextStorage);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_CONTEXT_STORAGE_H_
diff --git a/src/net/url_request/url_request_data_job.cc b/src/net/url_request/url_request_data_job.cc
new file mode 100644
index 0000000..5dffe32
--- /dev/null
+++ b/src/net/url_request/url_request_data_job.cc
@@ -0,0 +1,41 @@
+// Copyright (c) 2012 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.
+
+// Simple implementation of a data: protocol handler.
+
+#include "net/url_request/url_request_data_job.h"
+
+#include "net/base/data_url.h"
+#include "net/base/net_errors.h"
+
+namespace net {
+
+URLRequestDataJob::URLRequestDataJob(
+ URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestSimpleJob(request, network_delegate) {
+}
+
+// static
+URLRequestJob* URLRequestDataJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ return new URLRequestDataJob(request, network_delegate);
+}
+
+int URLRequestDataJob::GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const CompletionCallback& callback) const {
+ // Check if data URL is valid. If not, don't bother to try to extract data.
+ // Otherwise, parse the data from the data URL.
+ const GURL& url = request_->url();
+ if (!url.is_valid())
+ return ERR_INVALID_URL;
+ return DataURL::Parse(url, mime_type, charset, data)? OK: ERR_INVALID_URL;
+}
+
+URLRequestDataJob::~URLRequestDataJob() {
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_data_job.h b/src/net/url_request/url_request_data_job.h
new file mode 100644
index 0000000..7ab6561
--- /dev/null
+++ b/src/net/url_request/url_request_data_job.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H_
+
+#include <string>
+
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_simple_job.h"
+
+namespace net {
+
+class URLRequest;
+
+class URLRequestDataJob : public URLRequestSimpleJob {
+ public:
+ URLRequestDataJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ static URLRequest::ProtocolFactory Factory;
+
+ // URLRequestSimpleJob
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const CompletionCallback& callback) const OVERRIDE;
+
+ private:
+ virtual ~URLRequestDataJob();
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestDataJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_DATA_JOB_H_
diff --git a/src/net/url_request/url_request_error_job.cc b/src/net/url_request/url_request_error_job.cc
new file mode 100644
index 0000000..71069f2
--- /dev/null
+++ b/src/net/url_request/url_request_error_job.cc
@@ -0,0 +1,33 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_error_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+URLRequestErrorJob::URLRequestErrorJob(
+ URLRequest* request, NetworkDelegate* network_delegate, int error)
+ : URLRequestJob(request, network_delegate),
+ error_(error),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {}
+
+URLRequestErrorJob::~URLRequestErrorJob() {}
+
+void URLRequestErrorJob::Start() {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestErrorJob::StartAsync, weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestErrorJob::StartAsync() {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error_));
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_error_job.h b/src/net/url_request/url_request_error_job.h
new file mode 100644
index 0000000..7c162f0
--- /dev/null
+++ b/src/net/url_request/url_request_error_job.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011 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.
+//
+// Invalid URLs go through this URLRequestJob class rather than being
+// passed to the default job handler.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_ERROR_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_ERROR_JOB_H_
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class NET_EXPORT URLRequestErrorJob : public URLRequestJob {
+ public:
+ URLRequestErrorJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ int error);
+
+ virtual void Start() OVERRIDE;
+
+ private:
+ virtual ~URLRequestErrorJob();
+
+ void StartAsync();
+
+ int error_;
+
+ base::WeakPtrFactory<URLRequestErrorJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_ERROR_JOB_H_
diff --git a/src/net/url_request/url_request_file_dir_job.cc b/src/net/url_request/url_request_file_dir_job.cc
new file mode 100644
index 0000000..445c1eb
--- /dev/null
+++ b/src/net/url_request/url_request_file_dir_job.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_file_dir_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/sys_string_conversions.h"
+#include "base/utf_string_conversions.h"
+#include "base/time.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/url_request/url_request_status.h"
+
+#if defined(OS_POSIX)
+#include <sys/stat.h>
+#endif
+
+namespace net {
+
+URLRequestFileDirJob::URLRequestFileDirJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const FilePath& dir_path)
+ : URLRequestJob(request, network_delegate),
+ ALLOW_THIS_IN_INITIALIZER_LIST(lister_(dir_path, this)),
+ dir_path_(dir_path),
+ canceled_(false),
+ list_complete_(false),
+ wrote_header_(false),
+ read_pending_(false),
+ read_buffer_length_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+void URLRequestFileDirJob::StartAsync() {
+ lister_.Start();
+
+ NotifyHeadersComplete();
+}
+
+void URLRequestFileDirJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestFileDirJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestFileDirJob::Kill() {
+ if (canceled_)
+ return;
+
+ canceled_ = true;
+
+ if (!list_complete_)
+ lister_.Cancel();
+
+ URLRequestJob::Kill();
+
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+bool URLRequestFileDirJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ if (is_done())
+ return true;
+
+ if (FillReadBuffer(buf->data(), buf_size, bytes_read))
+ return true;
+
+ // We are waiting for more data
+ read_pending_ = true;
+ read_buffer_ = buf;
+ read_buffer_length_ = buf_size;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ return false;
+}
+
+bool URLRequestFileDirJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = "text/html";
+ return true;
+}
+
+bool URLRequestFileDirJob::GetCharset(std::string* charset) {
+ // All the filenames are converted to UTF-8 before being added.
+ *charset = "utf-8";
+ return true;
+}
+
+void URLRequestFileDirJob::OnListFile(
+ const DirectoryLister::DirectoryListerData& data) {
+ // We wait to write out the header until we get the first file, so that we
+ // can catch errors from DirectoryLister and show an error page.
+ if (!wrote_header_) {
+#if defined(OS_WIN)
+ const string16& title = dir_path_.value();
+#elif defined(OS_POSIX)
+ // TODO(jungshik): Add SysNativeMBToUTF16 to sys_string_conversions.
+ // On Mac, need to add NFKC->NFC conversion either here or in file_path.
+ // On Linux, the file system encoding is not defined, but we assume that
+ // SysNativeMBToWide takes care of it at least for now. We can try something
+ // more sophisticated if necessary later.
+ const string16& title = WideToUTF16(
+ base::SysNativeMBToWide(dir_path_.value()));
+#endif
+ data_.append(GetDirectoryListingHeader(title));
+ wrote_header_ = true;
+ }
+
+#if defined(OS_WIN)
+ int64 size = (static_cast<unsigned __int64>(data.info.nFileSizeHigh) << 32) |
+ data.info.nFileSizeLow;
+
+ // Note that we should not convert ftLastWriteTime to the local time because
+ // ICU's datetime formatting APIs expect time in UTC and take into account
+ // the timezone before formatting.
+ data_.append(GetDirectoryListingEntry(
+ data.info.cFileName,
+ std::string(),
+ ((data.info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0),
+ size,
+ base::Time::FromFileTime(data.info.ftLastWriteTime)));
+#elif defined(OS_POSIX)
+ // TOOD(jungshik): The same issue as for the directory name.
+ data_.append(GetDirectoryListingEntry(
+ WideToUTF16(base::SysNativeMBToWide(data.info.filename)),
+ data.info.filename,
+ S_ISDIR(data.info.stat.st_mode),
+ data.info.stat.st_size,
+ base::Time::FromTimeT(data.info.stat.st_mtime)));
+#endif
+
+ // TODO(darin): coalesce more?
+ CompleteRead();
+}
+
+void URLRequestFileDirJob::OnListDone(int error) {
+ DCHECK(!canceled_);
+ if (error != OK) {
+ read_pending_ = false;
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, error));
+ } else {
+ list_complete_ = true;
+ CompleteRead();
+ }
+}
+
+URLRequestFileDirJob::~URLRequestFileDirJob() {}
+
+void URLRequestFileDirJob::CompleteRead() {
+ if (read_pending_) {
+ int bytes_read;
+ if (FillReadBuffer(read_buffer_->data(), read_buffer_length_,
+ &bytes_read)) {
+ // We completed the read, so reset the read buffer.
+ read_pending_ = false;
+ read_buffer_ = NULL;
+ read_buffer_length_ = 0;
+
+ SetStatus(URLRequestStatus());
+ NotifyReadComplete(bytes_read);
+ } else {
+ NOTREACHED();
+ // TODO: Better error code.
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, 0));
+ }
+ }
+}
+
+bool URLRequestFileDirJob::FillReadBuffer(char* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+
+ *bytes_read = 0;
+
+ int count = std::min(buf_size, static_cast<int>(data_.size()));
+ if (count) {
+ memcpy(buf, &data_[0], count);
+ data_.erase(0, count);
+ *bytes_read = count;
+ return true;
+ } else if (list_complete_) {
+ // EOF
+ return true;
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_file_dir_job.h b/src/net/url_request/url_request_file_dir_job.h
new file mode 100644
index 0000000..bc6cccb
--- /dev/null
+++ b/src/net/url_request/url_request_file_dir_job.h
@@ -0,0 +1,81 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H_
+
+#include <string>
+
+#include "base/file_path.h"
+#include "base/file_util.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/directory_lister.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class URLRequestFileDirJob
+ : public URLRequestJob,
+ public DirectoryLister::DirectoryListerDelegate {
+ public:
+ URLRequestFileDirJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const FilePath& dir_path);
+
+ bool list_complete() const { return list_complete_; }
+
+ virtual void StartAsync();
+
+ // Overridden from URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+
+ // Overridden from DirectoryLister::DirectoryListerDelegate:
+ virtual void OnListFile(
+ const DirectoryLister::DirectoryListerData& data) OVERRIDE;
+ virtual void OnListDone(int error) OVERRIDE;
+
+ private:
+ virtual ~URLRequestFileDirJob();
+
+ void CloseLister();
+
+ // When we have data and a read has been pending, this function
+ // will fill the response buffer and notify the request
+ // appropriately.
+ void CompleteRead();
+
+ // Fills a buffer with the output.
+ bool FillReadBuffer(char *buf, int buf_size, int *bytes_read);
+
+ DirectoryLister lister_;
+ FilePath dir_path_;
+ std::string data_;
+ bool canceled_;
+
+ // Indicates whether we have the complete list of the dir
+ bool list_complete_;
+
+ // Indicates whether we have written the HTML header
+ bool wrote_header_;
+
+ // To simulate Async IO, we hold onto the Reader's buffer while
+ // we wait for IO to complete. When done, we fill the buffer
+ // manually.
+ bool read_pending_;
+ scoped_refptr<IOBuffer> read_buffer_;
+ int read_buffer_length_;
+ base::WeakPtrFactory<URLRequestFileDirJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFileDirJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILE_DIR_JOB_H_
diff --git a/src/net/url_request/url_request_file_job.cc b/src/net/url_request/url_request_file_job.cc
new file mode 100644
index 0000000..e6a17ab
--- /dev/null
+++ b/src/net/url_request/url_request_file_job.cc
@@ -0,0 +1,335 @@
+// Copyright (c) 2012 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.
+
+// For loading files, we make use of overlapped i/o to ensure that reading from
+// the filesystem (e.g., a network filesystem) does not block the calling
+// thread. An alternative approach would be to use a background thread or pool
+// of threads, but it seems better to leverage the operating system's ability
+// to do background file reads for us.
+//
+// Since overlapped reads require a 'static' buffer for the duration of the
+// asynchronous read, the URLRequestFileJob keeps a buffer as a member var. In
+// URLRequestFileJob::Read, data is simply copied from the object's buffer into
+// the given buffer. If there is no data to copy, the URLRequestFileJob
+// attempts to read more from the file to fill its buffer. If reading from the
+// file does not complete synchronously, then the URLRequestFileJob waits for a
+// signal from the OS that the overlapped read has completed. It does so by
+// leveraging the MessageLoop::WatchObject API.
+
+#include "net/url_request/url_request_file_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/platform_file.h"
+#include "base/string_util.h"
+#include "base/synchronization/lock.h"
+#include "base/threading/worker_pool.h"
+#include "base/threading/thread_restrictions.h"
+#include "build/build_config.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/file_stream.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/http/http_util.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_dir_job.h"
+
+#if defined(OS_WIN)
+#include "base/win/shortcut.h"
+#endif
+
+namespace net {
+
+URLRequestFileJob::FileMetaInfo::FileMetaInfo()
+ : file_size(0),
+ mime_type_result(false),
+ file_exists(false),
+ is_directory(false) {
+}
+
+URLRequestFileJob::URLRequestFileJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const FilePath& file_path)
+ : URLRequestJob(request, network_delegate),
+ file_path_(file_path),
+ stream_(new FileStream(NULL)),
+ remaining_bytes_(0),
+ weak_ptr_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
+}
+
+// static
+URLRequestJob* URLRequestFileJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ FilePath file_path;
+ const bool is_file = FileURLToFilePath(request->url(), &file_path);
+
+ // Check file access permissions.
+ if (!network_delegate ||
+ !network_delegate->CanAccessFile(*request, file_path)) {
+ return new URLRequestErrorJob(request, network_delegate, ERR_ACCESS_DENIED);
+ }
+
+
+ // We need to decide whether to create URLRequestFileJob for file access or
+ // URLRequestFileDirJob for directory access. To avoid accessing the
+ // filesystem, we only look at the path string here.
+ // The code in the URLRequestFileJob::Start() method discovers that a path,
+ // which doesn't end with a slash, should really be treated as a directory,
+ // and it then redirects to the URLRequestFileDirJob.
+ if (is_file &&
+ file_util::EndsWithSeparator(file_path) &&
+ file_path.IsAbsolute())
+#if defined(COBALT)
+ // We don't support FileDirJob.
+ return new URLRequestErrorJob(request, network_delegate, ERR_ACCESS_DENIED);
+#else
+ return new URLRequestFileDirJob(request, network_delegate, file_path);
+#endif
+
+ // Use a regular file request job for all non-directories (including invalid
+ // file names).
+ return new URLRequestFileJob(request, network_delegate, file_path);
+}
+
+void URLRequestFileJob::Start() {
+ FileMetaInfo* meta_info = new FileMetaInfo();
+ base::WorkerPool::PostTaskAndReply(
+ FROM_HERE,
+ base::Bind(&URLRequestFileJob::FetchMetaInfo, file_path_,
+ base::Unretained(meta_info)),
+ base::Bind(&URLRequestFileJob::DidFetchMetaInfo,
+ weak_ptr_factory_.GetWeakPtr(),
+ base::Owned(meta_info)),
+ true);
+}
+
+void URLRequestFileJob::Kill() {
+ stream_.reset();
+ weak_ptr_factory_.InvalidateWeakPtrs();
+
+ URLRequestJob::Kill();
+}
+
+bool URLRequestFileJob::ReadRawData(IOBuffer* dest, int dest_size,
+ int *bytes_read) {
+ DCHECK_NE(dest_size, 0);
+ DCHECK(bytes_read);
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ < dest_size)
+ dest_size = static_cast<int>(remaining_bytes_);
+
+ // If we should copy zero bytes because |remaining_bytes_| is zero, short
+ // circuit here.
+ if (!dest_size) {
+ *bytes_read = 0;
+ return true;
+ }
+
+ int rv = stream_->Read(dest, dest_size,
+ base::Bind(&URLRequestFileJob::DidRead,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv >= 0) {
+ // Data is immediately available.
+ *bytes_read = rv;
+ remaining_bytes_ -= rv;
+ DCHECK_GE(remaining_bytes_, 0);
+ return true;
+ }
+
+ // Otherwise, a read error occured. We may just need to wait...
+ if (rv == ERR_IO_PENDING) {
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+bool URLRequestFileJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (meta_info_.is_directory) {
+ // This happens when we discovered the file is a directory, so needs a
+ // slash at the end of the path.
+ std::string new_path = request_->url().path();
+ new_path.push_back('/');
+ GURL::Replacements replacements;
+ replacements.SetPathStr(new_path);
+
+ *location = request_->url().ReplaceComponents(replacements);
+ *http_status_code = 301; // simulate a permanent redirect
+ return true;
+ }
+
+#if defined(OS_WIN)
+ // Follow a Windows shortcut.
+ // We just resolve .lnk file, ignore others.
+ if (!LowerCaseEqualsASCII(file_path_.Extension(), ".lnk"))
+ return false;
+
+ FilePath new_path = file_path_;
+ bool resolved;
+ resolved = base::win::ResolveShortcut(new_path, &new_path, NULL);
+
+ // If shortcut is not resolved succesfully, do not redirect.
+ if (!resolved)
+ return false;
+
+ *location = FilePathToFileURL(new_path);
+ *http_status_code = 301;
+ return true;
+#else
+ return false;
+#endif
+}
+
+Filter* URLRequestFileJob::SetupFilter() const {
+ // Bug 9936 - .svgz files needs to be decompressed.
+ return LowerCaseEqualsASCII(file_path_.Extension(), ".svgz")
+ ? Filter::GZipFactory() : NULL;
+}
+
+bool URLRequestFileJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(request_);
+ if (meta_info_.mime_type_result) {
+ *mime_type = meta_info_.mime_type;
+ return true;
+ }
+ return false;
+}
+
+void URLRequestFileJob::SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) {
+ std::string range_header;
+ if (headers.GetHeader(HttpRequestHeaders::kRange, &range_header)) {
+ // We only care about "Range" header here.
+ std::vector<HttpByteRange> ranges;
+ if (HttpUtil::ParseRangeHeader(range_header, &ranges)) {
+ if (ranges.size() == 1) {
+ byte_range_ = ranges[0];
+ } else {
+ // We don't support multiple range requests in one single URL request,
+ // because we need to do multipart encoding here.
+ // TODO(hclam): decide whether we want to support multiple range
+ // requests.
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ }
+ }
+ }
+}
+
+URLRequestFileJob::~URLRequestFileJob() {
+}
+
+void URLRequestFileJob::FetchMetaInfo(const FilePath& file_path,
+ FileMetaInfo* meta_info) {
+ base::PlatformFileInfo platform_info;
+ meta_info->file_exists = file_util::GetFileInfo(file_path, &platform_info);
+ if (meta_info->file_exists) {
+ meta_info->file_size = platform_info.size;
+ meta_info->is_directory = platform_info.is_directory;
+ }
+ // On Windows GetMimeTypeFromFile() goes to the registry. Thus it should be
+ // done in WorkerPool.
+ meta_info->mime_type_result = GetMimeTypeFromFile(file_path,
+ &meta_info->mime_type);
+}
+
+void URLRequestFileJob::DidFetchMetaInfo(const FileMetaInfo* meta_info) {
+ meta_info_ = *meta_info;
+
+ // We use URLRequestFileJob to handle files as well as directories without
+ // trailing slash.
+ // If a directory does not exist, we return ERR_FILE_NOT_FOUND. Otherwise,
+ // we will append trailing slash and redirect to FileDirJob.
+ // A special case is "\" on Windows. We should resolve as invalid.
+ // However, Windows resolves "\" to "C:\", thus reports it as existent.
+ // So what happens is we append it with trailing slash and redirect it to
+ // FileDirJob where it is resolved as invalid.
+ if (!meta_info_.file_exists) {
+ DidOpen(ERR_FILE_NOT_FOUND);
+ return;
+ }
+ if (meta_info_.is_directory) {
+ DidOpen(OK);
+ return;
+ }
+
+ int flags = base::PLATFORM_FILE_OPEN |
+ base::PLATFORM_FILE_READ |
+ base::PLATFORM_FILE_ASYNC;
+ int rv = stream_->Open(file_path_, flags,
+ base::Bind(&URLRequestFileJob::DidOpen,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != ERR_IO_PENDING)
+ DidOpen(rv);
+}
+
+void URLRequestFileJob::DidOpen(int result) {
+ if (result != OK) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ return;
+ }
+
+ if (!byte_range_.ComputeBounds(meta_info_.file_size)) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ remaining_bytes_ = byte_range_.last_byte_position() -
+ byte_range_.first_byte_position() + 1;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ if (remaining_bytes_ > 0 && byte_range_.first_byte_position() != 0) {
+ int rv = stream_->Seek(FROM_BEGIN, byte_range_.first_byte_position(),
+ base::Bind(&URLRequestFileJob::DidSeek,
+ weak_ptr_factory_.GetWeakPtr()));
+ if (rv != ERR_IO_PENDING) {
+ // stream_->Seek() failed, so pass an intentionally erroneous value
+ // into DidSeek().
+ DidSeek(-1);
+ }
+ } else {
+ // We didn't need to call stream_->Seek() at all, so we pass to DidSeek()
+ // the value that would mean seek success. This way we skip the code
+ // handling seek failure.
+ DidSeek(byte_range_.first_byte_position());
+ }
+}
+
+void URLRequestFileJob::DidSeek(int64 result) {
+ if (result != byte_range_.first_byte_position()) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_REQUEST_RANGE_NOT_SATISFIABLE));
+ return;
+ }
+
+ set_expected_content_size(remaining_bytes_);
+ NotifyHeadersComplete();
+}
+
+void URLRequestFileJob::DidRead(int result) {
+ if (result > 0) {
+ SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
+ } else if (result == 0) {
+ NotifyDone(URLRequestStatus());
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+
+ remaining_bytes_ -= result;
+ DCHECK_GE(remaining_bytes_, 0);
+
+ NotifyReadComplete(result);
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_file_job.h b/src/net/url_request/url_request_file_job.h
new file mode 100644
index 0000000..9c2199c
--- /dev/null
+++ b/src/net/url_request/url_request_file_job.h
@@ -0,0 +1,107 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FILE_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FILE_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/file_path.h"
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/http/http_byte_range.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace base{
+struct PlatformFileInfo;
+}
+namespace file_util {
+struct FileInfo;
+}
+
+namespace net {
+
+class FileStream;
+
+// A request job that handles reading file URLs
+class NET_EXPORT URLRequestFileJob : public URLRequestJob {
+ public:
+ URLRequestFileJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const FilePath& file_path);
+
+ static URLRequest::ProtocolFactory Factory;
+
+ // URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+ virtual Filter* SetupFilter() const OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) OVERRIDE;
+
+ protected:
+ virtual ~URLRequestFileJob();
+
+ // The OS-specific full path name of the file
+ FilePath file_path_;
+
+ private:
+ // Meta information about the file. It's used as a member in the
+ // URLRequestFileJob and also passed between threads because disk access is
+ // necessary to obtain it.
+ struct FileMetaInfo {
+ FileMetaInfo();
+
+ // Size of the file.
+ int64 file_size;
+ // Mime type associated with the file.
+ std::string mime_type;
+ // Result returned from GetMimeTypeFromFile(), i.e. flag showing whether
+ // obtaining of the mime type was successful.
+ bool mime_type_result;
+ // Flag showing whether the file exists.
+ bool file_exists;
+ // Flag showing whether the file name actually refers to a directory.
+ bool is_directory;
+ };
+
+ // Fetches file info on a background thread.
+ static void FetchMetaInfo(const FilePath& file_path,
+ FileMetaInfo* meta_info);
+
+ // Callback after fetching file info on a background thread.
+ void DidFetchMetaInfo(const FileMetaInfo* meta_info);
+
+ // Callback after opening file on a background thread.
+ void DidOpen(int result);
+
+ // Callback after seeking to the beginning of |byte_range_| in the file
+ // on a background thread.
+ void DidSeek(int64 result);
+
+ // Callback after data is asynchronously read from the file.
+ void DidRead(int result);
+
+ scoped_ptr<FileStream> stream_;
+ FileMetaInfo meta_info_;
+
+ HttpByteRange byte_range_;
+ int64 remaining_bytes_;
+
+ base::WeakPtrFactory<URLRequestFileJob> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFileJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILE_JOB_H_
diff --git a/src/net/url_request/url_request_filter.cc b/src/net/url_request/url_request_filter.cc
new file mode 100644
index 0000000..01b6069
--- /dev/null
+++ b/src/net/url_request/url_request_filter.cc
@@ -0,0 +1,150 @@
+// Copyright (c) 2011 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 "net/url_request/url_request_filter.h"
+
+#include <set>
+
+#include "base/logging.h"
+
+namespace net {
+
+URLRequestFilter* URLRequestFilter::shared_instance_ = NULL;
+
+URLRequestFilter::~URLRequestFilter() {}
+
+// static
+URLRequestJob* URLRequestFilter::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ // Returning null here just means that the built-in handler will be used.
+ return GetInstance()->FindRequestHandler(request, network_delegate, scheme);
+}
+
+// static
+URLRequestFilter* URLRequestFilter::GetInstance() {
+ if (!shared_instance_)
+ shared_instance_ = new URLRequestFilter;
+ return shared_instance_;
+}
+
+void URLRequestFilter::AddHostnameHandler(const std::string& scheme,
+ const std::string& hostname, URLRequest::ProtocolFactory* factory) {
+ hostname_handler_map_[make_pair(scheme, hostname)] = factory;
+
+ // Register with the ProtocolFactory.
+ URLRequest::Deprecated::RegisterProtocolFactory(
+ scheme, &URLRequestFilter::Factory);
+
+#ifndef NDEBUG
+ // Check to see if we're masking URLs in the url_handler_map_.
+ for (UrlHandlerMap::const_iterator i = url_handler_map_.begin();
+ i != url_handler_map_.end(); ++i) {
+ const GURL& url = GURL(i->first);
+ HostnameHandlerMap::iterator host_it =
+ hostname_handler_map_.find(make_pair(url.scheme(), url.host()));
+ if (host_it != hostname_handler_map_.end())
+ NOTREACHED();
+ }
+#endif // !NDEBUG
+}
+
+void URLRequestFilter::RemoveHostnameHandler(const std::string& scheme,
+ const std::string& hostname) {
+ HostnameHandlerMap::iterator iter =
+ hostname_handler_map_.find(make_pair(scheme, hostname));
+ DCHECK(iter != hostname_handler_map_.end());
+
+ hostname_handler_map_.erase(iter);
+ // Note that we don't unregister from the URLRequest ProtocolFactory as
+ // this would left no protocol factory for the scheme.
+ // URLRequestFilter::Factory will keep forwarding the requests to the
+ // URLRequestInetJob.
+}
+
+bool URLRequestFilter::AddUrlHandler(
+ const GURL& url,
+ URLRequest::ProtocolFactory* factory) {
+ if (!url.is_valid())
+ return false;
+ url_handler_map_[url.spec()] = factory;
+
+ // Register with the ProtocolFactory.
+ URLRequest::Deprecated::RegisterProtocolFactory(url.scheme(),
+ &URLRequestFilter::Factory);
+#ifndef NDEBUG
+ // Check to see if this URL is masked by a hostname handler.
+ HostnameHandlerMap::iterator host_it =
+ hostname_handler_map_.find(make_pair(url.scheme(), url.host()));
+ if (host_it != hostname_handler_map_.end())
+ NOTREACHED();
+#endif // !NDEBUG
+
+ return true;
+}
+
+void URLRequestFilter::RemoveUrlHandler(const GURL& url) {
+ UrlHandlerMap::iterator iter = url_handler_map_.find(url.spec());
+ DCHECK(iter != url_handler_map_.end());
+
+ url_handler_map_.erase(iter);
+ // Note that we don't unregister from the URLRequest ProtocolFactory as
+ // this would left no protocol factory for the scheme.
+ // URLRequestFilter::Factory will keep forwarding the requests to the
+ // URLRequestInetJob.
+}
+
+void URLRequestFilter::ClearHandlers() {
+ // Unregister with the ProtocolFactory.
+ std::set<std::string> schemes;
+ for (UrlHandlerMap::const_iterator i = url_handler_map_.begin();
+ i != url_handler_map_.end(); ++i) {
+ schemes.insert(GURL(i->first).scheme());
+ }
+ for (HostnameHandlerMap::const_iterator i = hostname_handler_map_.begin();
+ i != hostname_handler_map_.end(); ++i) {
+ schemes.insert(i->first.first);
+ }
+ for (std::set<std::string>::const_iterator scheme = schemes.begin();
+ scheme != schemes.end(); ++scheme) {
+ URLRequest::Deprecated::RegisterProtocolFactory(*scheme, NULL);
+ }
+
+ url_handler_map_.clear();
+ hostname_handler_map_.clear();
+ hit_count_ = 0;
+}
+
+URLRequestFilter::URLRequestFilter() : hit_count_(0) { }
+
+URLRequestJob* URLRequestFilter::FindRequestHandler(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ URLRequestJob* job = NULL;
+ if (request->url().is_valid()) {
+ // Check the hostname map first.
+ const std::string& hostname = request->url().host();
+
+ HostnameHandlerMap::iterator i =
+ hostname_handler_map_.find(make_pair(scheme, hostname));
+ if (i != hostname_handler_map_.end())
+ job = i->second(request, network_delegate, scheme);
+
+ if (!job) {
+ // Not in the hostname map, check the url map.
+ const std::string& url = request->url().spec();
+ UrlHandlerMap::iterator i = url_handler_map_.find(url);
+ if (i != url_handler_map_.end())
+ job = i->second(request, network_delegate, scheme);
+ }
+ }
+ if (job) {
+ DVLOG(1) << "URLRequestFilter hit for " << request->url().spec();
+ hit_count_++;
+ }
+ return job;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_filter.h b/src/net/url_request/url_request_filter.h
new file mode 100644
index 0000000..c039300
--- /dev/null
+++ b/src/net/url_request/url_request_filter.h
@@ -0,0 +1,94 @@
+// Copyright (c) 2011 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.
+//
+// A class to help filter URLRequest jobs based on the URL of the request
+// rather than just the scheme. Example usage:
+//
+// // Use as an "http" handler.
+// URLRequest::RegisterProtocolFactory("http", &URLRequestFilter::Factory);
+// // Add special handling for the URL http://foo.com/
+// URLRequestFilter::GetInstance()->AddUrlHandler(
+// GURL("http://foo.com/"),
+// &URLRequestCustomJob::Factory);
+//
+// If URLRequestFilter::Factory can't find a handle for the request, it passes
+// it through to URLRequestInetJob::Factory and lets the default network stack
+// handle it.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FILTER_H_
+#define NET_URL_REQUEST_URL_REQUEST_FILTER_H_
+
+#include <map>
+#include <string>
+
+#include "base/hash_tables.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request.h"
+
+class GURL;
+
+namespace net {
+class URLRequestJob;
+
+class NET_EXPORT URLRequestFilter {
+ public:
+ // scheme,hostname -> ProtocolFactory
+ typedef std::map<std::pair<std::string, std::string>,
+ URLRequest::ProtocolFactory*> HostnameHandlerMap;
+ typedef base::hash_map<std::string, URLRequest::ProtocolFactory*>
+ UrlHandlerMap;
+
+ ~URLRequestFilter();
+
+ static URLRequest::ProtocolFactory Factory;
+
+ // Singleton instance for use.
+ static URLRequestFilter* GetInstance();
+
+ void AddHostnameHandler(const std::string& scheme,
+ const std::string& hostname,
+ URLRequest::ProtocolFactory* factory);
+ void RemoveHostnameHandler(const std::string& scheme,
+ const std::string& hostname);
+
+ // Returns true if we successfully added the URL handler. This will replace
+ // old handlers for the URL if one existed.
+ bool AddUrlHandler(const GURL& url,
+ URLRequest::ProtocolFactory* factory);
+
+ void RemoveUrlHandler(const GURL& url);
+
+ // Clear all the existing URL handlers and unregister with the
+ // ProtocolFactory. Resets the hit count.
+ void ClearHandlers();
+
+ // Returns the number of times a handler was used to service a request.
+ int hit_count() const { return hit_count_; }
+
+ protected:
+ URLRequestFilter();
+
+ // Helper method that looks up the request in the url_handler_map_.
+ URLRequestJob* FindRequestHandler(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ // Maps hostnames to factories. Hostnames take priority over URLs.
+ HostnameHandlerMap hostname_handler_map_;
+
+ // Maps URLs to factories.
+ UrlHandlerMap url_handler_map_;
+
+ int hit_count_;
+
+ private:
+ // Singleton instance.
+ static URLRequestFilter* shared_instance_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFilter);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FILTER_H_
diff --git a/src/net/url_request/url_request_filter_unittest.cc b/src/net/url_request/url_request_filter_unittest.cc
new file mode 100644
index 0000000..9f11675
--- /dev/null
+++ b/src/net/url_request/url_request_filter_unittest.cc
@@ -0,0 +1,131 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_filter.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_job_factory.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 net {
+
+namespace {
+
+URLRequestTestJob* job_a;
+
+URLRequestJob* FactoryA(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ job_a = new URLRequestTestJob(request, network_delegate);
+ return job_a;
+}
+
+URLRequestTestJob* job_b;
+
+URLRequestJob* FactoryB(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ job_b = new URLRequestTestJob(request, network_delegate);
+ return job_b;
+}
+
+TEST(URLRequestFilter, BasicMatching) {
+ TestDelegate delegate;
+ TestURLRequestContext request_context;
+
+ GURL url_1("http://foo.com/");
+ TestURLRequest request_1(url_1, &delegate, &request_context);
+
+ GURL url_2("http://bar.com/");
+ TestURLRequest request_2(url_2, &delegate, &request_context);
+
+ // Check AddUrlHandler checks for invalid URLs.
+ EXPECT_FALSE(URLRequestFilter::GetInstance()->AddUrlHandler(GURL(),
+ &FactoryA));
+
+ // Check URL matching.
+ URLRequestFilter::GetInstance()->ClearHandlers();
+ EXPECT_TRUE(URLRequestFilter::GetInstance()->AddUrlHandler(url_1,
+ &FactoryA));
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, request_context.network_delegate(), url_1.scheme());
+ EXPECT_EQ(job_a, found);
+ EXPECT_TRUE(job_a != NULL);
+ job_a = NULL;
+ }
+ EXPECT_EQ(URLRequestFilter::GetInstance()->hit_count(), 1);
+
+ // Check we don't match other URLs.
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_2, request_context.network_delegate(), url_2.scheme()) == NULL);
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we can overwrite URL handler.
+ EXPECT_TRUE(URLRequestFilter::GetInstance()->AddUrlHandler(url_1,
+ &FactoryB));
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, request_context.network_delegate(), url_1.scheme());
+ EXPECT_EQ(job_b, found);
+ EXPECT_TRUE(job_b != NULL);
+ job_b = NULL;
+ }
+ EXPECT_EQ(2, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we can remove URL matching.
+ URLRequestFilter::GetInstance()->RemoveUrlHandler(url_1);
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_1, request_context.network_delegate(), url_1.scheme()) == NULL);
+ EXPECT_EQ(URLRequestFilter::GetInstance()->hit_count(), 2);
+
+ // Check hostname matching.
+ URLRequestFilter::GetInstance()->ClearHandlers();
+ EXPECT_EQ(0, URLRequestFilter::GetInstance()->hit_count());
+ URLRequestFilter::GetInstance()->AddHostnameHandler(url_1.scheme(),
+ url_1.host(),
+ &FactoryB);
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, request_context.network_delegate(), url_1.scheme());
+ EXPECT_EQ(job_b, found);
+ EXPECT_TRUE(job_b != NULL);
+ job_b = NULL;
+ }
+ EXPECT_EQ(1, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we don't match other hostnames.
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_2, request_context.network_delegate(), url_2.scheme()) == NULL);
+ EXPECT_EQ(URLRequestFilter::GetInstance()->hit_count(), 1);
+
+ // Check we can overwrite hostname handler.
+ URLRequestFilter::GetInstance()->AddHostnameHandler(url_1.scheme(),
+ url_1.host(),
+ &FactoryA);
+ {
+ scoped_refptr<URLRequestJob> found = URLRequestFilter::Factory(
+ &request_1, request_context.network_delegate(), url_1.scheme());
+ EXPECT_EQ(job_a, found);
+ EXPECT_TRUE(job_a != NULL);
+ job_a = NULL;
+ }
+ EXPECT_EQ(2, URLRequestFilter::GetInstance()->hit_count());
+
+ // Check we can remove hostname matching.
+ URLRequestFilter::GetInstance()->RemoveHostnameHandler(url_1.scheme(),
+ url_1.host());
+ EXPECT_TRUE(URLRequestFilter::Factory(
+ &request_1, request_context.network_delegate(), url_1.scheme()) == NULL);
+ EXPECT_EQ(2, URLRequestFilter::GetInstance()->hit_count());
+}
+
+} // namespace
+
+} // namespace net
diff --git a/src/net/url_request/url_request_ftp_job.cc b/src/net/url_request/url_request_ftp_job.cc
new file mode 100644
index 0000000..27021a3
--- /dev/null
+++ b/src/net/url_request/url_request_ftp_job.cc
@@ -0,0 +1,264 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_ftp_job.h"
+
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/ftp/ftp_response_info.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+
+namespace net {
+
+URLRequestFtpJob::URLRequestFtpJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ FtpTransactionFactory* ftp_transaction_factory,
+ FtpAuthCache* ftp_auth_cache)
+ : URLRequestJob(request, network_delegate),
+ read_in_progress_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+ ftp_transaction_factory_(ftp_transaction_factory),
+ ftp_auth_cache_(ftp_auth_cache) {
+ DCHECK(ftp_transaction_factory);
+ DCHECK(ftp_auth_cache);
+}
+
+// static
+URLRequestJob* URLRequestFtpJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ DCHECK_EQ(scheme, "ftp");
+
+ int port = request->url().IntPort();
+ if (request->url().has_port() &&
+ !IsPortAllowedByFtp(port) && !IsPortAllowedByOverride(port)) {
+ return new URLRequestErrorJob(request,
+ network_delegate,
+ ERR_UNSAFE_PORT);
+ }
+
+ return new URLRequestFtpJob(request,
+ network_delegate,
+ request->context()->ftp_transaction_factory(),
+ request->context()->ftp_auth_cache());
+}
+
+bool URLRequestFtpJob::GetMimeType(std::string* mime_type) const {
+ if (transaction_->GetResponseInfo()->is_directory_listing) {
+ *mime_type = "text/vnd.chromium.ftp-dir";
+ return true;
+ }
+ return false;
+}
+
+HostPortPair URLRequestFtpJob::GetSocketAddress() const {
+ if (!transaction_.get()) {
+ return HostPortPair();
+ }
+ return transaction_->GetResponseInfo()->socket_address;
+}
+
+URLRequestFtpJob::~URLRequestFtpJob() {
+}
+
+void URLRequestFtpJob::StartTransaction() {
+ // Create a transaction.
+ DCHECK(!transaction_.get());
+
+ transaction_.reset(ftp_transaction_factory_->CreateTransaction());
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ int rv;
+ if (transaction_.get()) {
+ rv = transaction_->Start(
+ &request_info_,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ base::Unretained(this)),
+ request_->net_log());
+ if (rv == ERR_IO_PENDING)
+ return;
+ } else {
+ rv = ERR_FAILED;
+ }
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void URLRequestFtpJob::OnStartCompleted(int result) {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+
+ // Note that transaction_ may be NULL due to a creation failure.
+ if (transaction_.get()) {
+ // FTP obviously doesn't have HTTP Content-Length header. We have to pass
+ // the content size information manually.
+ set_expected_content_size(
+ transaction_->GetResponseInfo()->expected_content_size);
+ }
+
+ if (result == OK) {
+ NotifyHeadersComplete();
+ } else if (transaction_.get() &&
+ transaction_->GetResponseInfo()->needs_auth) {
+ GURL origin = request_->url().GetOrigin();
+ if (server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH) {
+ ftp_auth_cache_->Remove(origin, server_auth_->credentials);
+ } else if (!server_auth_) {
+ server_auth_ = new AuthData();
+ }
+ server_auth_->state = AUTH_STATE_NEED_AUTH;
+
+ FtpAuthCache::Entry* cached_auth = ftp_auth_cache_->Lookup(origin);
+ if (cached_auth) {
+ // Retry using cached auth data.
+ SetAuth(cached_auth->credentials);
+ } else {
+ // Prompt for a username/password.
+ NotifyHeadersComplete();
+ }
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestFtpJob::OnReadCompleted(int result) {
+ read_in_progress_ = false;
+ if (result == 0) {
+ NotifyDone(URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ } else {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+ }
+ NotifyReadComplete(result);
+}
+
+void URLRequestFtpJob::RestartTransactionWithAuth() {
+ DCHECK(server_auth_ && server_auth_->state == AUTH_STATE_HAVE_AUTH);
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv = transaction_->RestartWithAuth(
+ server_auth_->credentials,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ base::Unretained(this)));
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void URLRequestFtpJob::Start() {
+ DCHECK(!transaction_.get());
+ request_info_.url = request_->url();
+ StartTransaction();
+}
+
+void URLRequestFtpJob::Kill() {
+ if (!transaction_.get())
+ return;
+ transaction_.reset();
+ URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+}
+
+LoadState URLRequestFtpJob::GetLoadState() const {
+ return transaction_.get() ?
+ transaction_->GetLoadState() : LOAD_STATE_IDLE;
+}
+
+bool URLRequestFtpJob::NeedsAuth() {
+ // Note that we only have to worry about cases where an actual FTP server
+ // requires auth (and not a proxy), because connecting to FTP via proxy
+ // effectively means the browser communicates via HTTP, and uses HTTP's
+ // Proxy-Authenticate protocol when proxy servers require auth.
+ return server_auth_ && server_auth_->state == AUTH_STATE_NEED_AUTH;
+}
+
+void URLRequestFtpJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK((server_auth_ != NULL) &&
+ (server_auth_->state == AUTH_STATE_NEED_AUTH));
+ scoped_refptr<AuthChallengeInfo> auth_info(new AuthChallengeInfo);
+ auth_info->is_proxy = false;
+ auth_info->challenger = HostPortPair::FromURL(request_->url());
+ // scheme and realm are kept empty.
+ DCHECK(auth_info->scheme.empty());
+ DCHECK(auth_info->realm.empty());
+ result->swap(auth_info);
+}
+
+void URLRequestFtpJob::SetAuth(const AuthCredentials& credentials) {
+ DCHECK(NeedsAuth());
+ server_auth_->state = AUTH_STATE_HAVE_AUTH;
+ server_auth_->credentials = credentials;
+
+ ftp_auth_cache_->Add(request_->url().GetOrigin(), server_auth_->credentials);
+
+ RestartTransactionWithAuth();
+}
+
+void URLRequestFtpJob::CancelAuth() {
+ DCHECK(NeedsAuth());
+ server_auth_->state = AUTH_STATE_CANCELED;
+
+ // Once the auth is cancelled, we proceed with the request as though
+ // there were no auth. Schedule this for later so that we don't cause
+ // any recursing into the caller as a result of this call.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestFtpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), OK));
+}
+
+UploadProgress URLRequestFtpJob::GetUploadProgress() const {
+ return UploadProgress();
+}
+
+bool URLRequestFtpJob::ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) {
+ DCHECK_NE(buf_size, 0);
+ DCHECK(bytes_read);
+ DCHECK(!read_in_progress_);
+
+ int rv = transaction_->Read(buf, buf_size,
+ base::Bind(&URLRequestFtpJob::OnReadCompleted,
+ base::Unretained(this)));
+ if (rv >= 0) {
+ *bytes_read = rv;
+ return true;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ read_in_progress_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+ return false;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_ftp_job.h b/src/net/url_request/url_request_ftp_job.h
new file mode 100644
index 0000000..d05e3d8
--- /dev/null
+++ b/src/net/url_request/url_request_ftp_job.h
@@ -0,0 +1,86 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/ftp/ftp_request_info.h"
+#include "net/ftp/ftp_transaction.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class NetworkDelegate;
+class FtpTransactionFactory;
+class FtpAuthCache;
+
+// A URLRequestJob subclass that is built on top of FtpTransaction. It
+// provides an implementation for FTP.
+class URLRequestFtpJob : public URLRequestJob {
+ public:
+ URLRequestFtpJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ FtpTransactionFactory* ftp_transaction_factory,
+ FtpAuthCache* ftp_auth_cache);
+
+ // TODO(shalev): get rid of this function in favor of FtpProtocolHandler.
+ static URLRequestJob* Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ // Overridden from URLRequestJob:
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+
+ private:
+ virtual ~URLRequestFtpJob();
+
+ void StartTransaction();
+
+ void OnStartCompleted(int result);
+ void OnReadCompleted(int result);
+
+ void RestartTransactionWithAuth();
+
+ void LogFtpServerType(char server_type);
+
+ // Overridden from URLRequestJob:
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual bool NeedsAuth() OVERRIDE;
+ virtual void GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* auth_info) OVERRIDE;
+ virtual void SetAuth(const AuthCredentials& credentials) OVERRIDE;
+ virtual void CancelAuth() OVERRIDE;
+
+ // TODO(ibrar): Yet to give another look at this function.
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+
+ FtpRequestInfo request_info_;
+ scoped_ptr<FtpTransaction> transaction_;
+
+ bool read_in_progress_;
+
+ scoped_refptr<AuthData> server_auth_;
+
+ base::WeakPtrFactory<URLRequestFtpJob> weak_factory_;
+
+ FtpTransactionFactory* ftp_transaction_factory_;
+ FtpAuthCache* ftp_auth_cache_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestFtpJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_FTP_JOB_H_
diff --git a/src/net/url_request/url_request_ftp_job_unittest.cc b/src/net/url_request/url_request_ftp_job_unittest.cc
new file mode 100644
index 0000000..e13a80d
--- /dev/null
+++ b/src/net/url_request/url_request_ftp_job_unittest.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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 "net/ftp/ftp_auth_cache.h"
+#include "net/ftp/ftp_transaction.h"
+#include "net/ftp/ftp_transaction_factory.h"
+#include "net/url_request/ftp_protocol_handler.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_ftp_job.h"
+#include "net/url_request/url_request_status.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::Return;
+using ::testing::_;
+
+namespace net {
+
+class MockFtpTransactionFactory : public FtpTransactionFactory {
+ public:
+ MOCK_METHOD0(CreateTransaction, FtpTransaction*());
+ MOCK_METHOD1(Suspend, void(bool suspend));
+};
+
+class MockURLRequestDelegate : public URLRequest::Delegate {
+ public:
+ MOCK_METHOD3(OnReceivedRedirect, void(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect));
+ MOCK_METHOD2(OnAuthRequired, void(URLRequest* request,
+ AuthChallengeInfo* auth_info));
+ MOCK_METHOD2(OnCertificateRequested,
+ void(URLRequest* request,
+ SSLCertRequestInfo* cert_request_info));
+ MOCK_METHOD3(OnSSLCertificateError, void(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal));
+ MOCK_METHOD1(OnResponseStarted, void(URLRequest* request));
+ MOCK_METHOD2(OnReadCompleted, void(URLRequest* request, int bytes_read));
+};
+
+ACTION_P(HandleOnResponseStarted, expected_status) {
+ EXPECT_EQ(expected_status, arg0->status().status());
+}
+
+TEST(FtpProtocolHandlerTest, CreateTransactionFails) {
+ testing::InSequence in_sequence_;
+
+ ::testing::StrictMock<MockFtpTransactionFactory> ftp_transaction_factory;
+ ::testing::StrictMock<MockURLRequestDelegate> delegate;
+ FtpAuthCache ftp_auth_cache;
+
+ GURL url("ftp://example.com");
+ URLRequestContext context;
+ URLRequest url_request(url, &delegate, &context);
+
+ FtpProtocolHandler ftp_protocol_handler(
+ &ftp_transaction_factory, &ftp_auth_cache);
+
+ scoped_refptr<URLRequestJob> ftp_job(
+ ftp_protocol_handler.MaybeCreateJob(&url_request, NULL));
+ ASSERT_TRUE(ftp_job.get());
+
+ EXPECT_CALL(ftp_transaction_factory, CreateTransaction())
+ .WillOnce(Return(static_cast<FtpTransaction*>(NULL)));
+ ftp_job->Start();
+ EXPECT_CALL(delegate, OnResponseStarted(_))
+ .WillOnce(HandleOnResponseStarted(URLRequestStatus::FAILED));
+ MessageLoop::current()->RunUntilIdle();
+ EXPECT_FALSE(url_request.is_pending());
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_http_job.cc b/src/net/url_request/url_request_http_job.cc
new file mode 100644
index 0000000..4977f8c
--- /dev/null
+++ b/src/net/url_request/url_request_http_job.cc
@@ -0,0 +1,1631 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_http_job.h"
+
+#include "base/base_switches.h"
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/file_version_info.h"
+#include "base/message_loop.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "net/base/cert_status_flags.h"
+#include "net/base/filter.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_flags.h"
+#include "net/base/mime_util.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_util.h"
+#include "net/base/network_delegate.h"
+#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
+#include "net/base/sdch_manager.h"
+#endif
+#include "net/base/ssl_cert_request_info.h"
+#include "net/base/ssl_config_service.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/http/http_status_code.h"
+#include "net/http/http_transaction.h"
+#include "net/http/http_transaction_delegate.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/http/http_util.h"
+#include "net/url_request/fraudulent_certificate_reporter.h"
+#include "net/url_request/http_user_agent_settings.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_redirect_job.h"
+#include "net/url_request/url_request_throttler_header_adapter.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace net {
+
+class URLRequestHttpJob::HttpFilterContext : public FilterContext {
+ public:
+ explicit HttpFilterContext(URLRequestHttpJob* job);
+ virtual ~HttpFilterContext();
+
+ // FilterContext implementation.
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetURL(GURL* gurl) const OVERRIDE;
+ virtual base::Time GetRequestTime() const OVERRIDE;
+ virtual bool IsCachedContent() const OVERRIDE;
+ virtual bool IsDownload() const OVERRIDE;
+ virtual bool IsSdchResponse() const OVERRIDE;
+ virtual int64 GetByteReadCount() const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual void RecordPacketStats(StatisticSelector statistic) const OVERRIDE;
+
+ // Method to allow us to reset filter context for a response that should have
+ // been SDCH encoded when there is an update due to an explicit HTTP header.
+ void ResetSdchResponseToFalse();
+
+ private:
+ URLRequestHttpJob* job_;
+
+ DISALLOW_COPY_AND_ASSIGN(HttpFilterContext);
+};
+
+class URLRequestHttpJob::HttpTransactionDelegateImpl
+ : public HttpTransactionDelegate {
+ public:
+ explicit HttpTransactionDelegateImpl(URLRequest* request)
+ : request_(request),
+ network_delegate_(request->context()->network_delegate()),
+ cache_active_(false),
+ network_active_(false) {
+ }
+ virtual ~HttpTransactionDelegateImpl() {
+ OnDetachRequest();
+ }
+ void OnDetachRequest() {
+ if (request_ == NULL || network_delegate_ == NULL)
+ return;
+ network_delegate_->NotifyRequestWaitStateChange(
+ *request_,
+ NetworkDelegate::REQUEST_WAIT_STATE_RESET);
+ cache_active_ = false;
+ network_active_ = false;
+ request_ = NULL;
+ }
+ virtual void OnCacheActionStart() OVERRIDE {
+ if (request_ == NULL || network_delegate_ == NULL)
+ return;
+ DCHECK(!cache_active_ && !network_active_);
+ cache_active_ = true;
+ network_delegate_->NotifyRequestWaitStateChange(
+ *request_,
+ NetworkDelegate::REQUEST_WAIT_STATE_CACHE_START);
+ }
+ virtual void OnCacheActionFinish() OVERRIDE {
+ if (request_ == NULL || network_delegate_ == NULL)
+ return;
+ DCHECK(cache_active_ && !network_active_);
+ cache_active_ = false;
+ network_delegate_->NotifyRequestWaitStateChange(
+ *request_,
+ NetworkDelegate::REQUEST_WAIT_STATE_CACHE_FINISH);
+ }
+ virtual void OnNetworkActionStart() OVERRIDE {
+ if (request_ == NULL || network_delegate_ == NULL)
+ return;
+ DCHECK(!cache_active_ && !network_active_);
+ network_active_ = true;
+ network_delegate_->NotifyRequestWaitStateChange(
+ *request_,
+ NetworkDelegate::REQUEST_WAIT_STATE_NETWORK_START);
+ }
+ virtual void OnNetworkActionFinish() OVERRIDE {
+ if (request_ == NULL || network_delegate_ == NULL)
+ return;
+ DCHECK(!cache_active_ && network_active_);
+ network_active_ = false;
+ network_delegate_->NotifyRequestWaitStateChange(
+ *request_,
+ NetworkDelegate::REQUEST_WAIT_STATE_NETWORK_FINISH);
+ }
+ private:
+ URLRequest* request_;
+ NetworkDelegate* network_delegate_;
+ bool cache_active_;
+ bool network_active_;
+};
+
+URLRequestHttpJob::HttpFilterContext::HttpFilterContext(URLRequestHttpJob* job)
+ : job_(job) {
+ DCHECK(job_);
+}
+
+URLRequestHttpJob::HttpFilterContext::~HttpFilterContext() {
+}
+
+bool URLRequestHttpJob::HttpFilterContext::GetMimeType(
+ std::string* mime_type) const {
+ return job_->GetMimeType(mime_type);
+}
+
+bool URLRequestHttpJob::HttpFilterContext::GetURL(GURL* gurl) const {
+ if (!job_->request())
+ return false;
+ *gurl = job_->request()->url();
+ return true;
+}
+
+base::Time URLRequestHttpJob::HttpFilterContext::GetRequestTime() const {
+ return job_->request() ? job_->request()->request_time() : base::Time();
+}
+
+bool URLRequestHttpJob::HttpFilterContext::IsCachedContent() const {
+ return job_->is_cached_content_;
+}
+
+bool URLRequestHttpJob::HttpFilterContext::IsDownload() const {
+ return (job_->request_info_.load_flags & LOAD_IS_DOWNLOAD) != 0;
+}
+
+void URLRequestHttpJob::HttpFilterContext::ResetSdchResponseToFalse() {
+ DCHECK(job_->sdch_dictionary_advertised_);
+ job_->sdch_dictionary_advertised_ = false;
+}
+
+bool URLRequestHttpJob::HttpFilterContext::IsSdchResponse() const {
+ return job_->sdch_dictionary_advertised_;
+}
+
+int64 URLRequestHttpJob::HttpFilterContext::GetByteReadCount() const {
+ return job_->filter_input_byte_count();
+}
+
+int URLRequestHttpJob::HttpFilterContext::GetResponseCode() const {
+ return job_->GetResponseCode();
+}
+
+void URLRequestHttpJob::HttpFilterContext::RecordPacketStats(
+ StatisticSelector statistic) const {
+ job_->RecordPacketStats(statistic);
+}
+
+// TODO(darin): make sure the port blocking code is not lost
+// static
+URLRequestJob* URLRequestHttpJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ DCHECK(scheme == "http" || scheme == "https");
+
+ if (!request->context()->http_transaction_factory()) {
+ NOTREACHED() << "requires a valid context";
+ return new URLRequestErrorJob(
+ request, network_delegate, ERR_INVALID_ARGUMENT);
+ }
+
+ GURL redirect_url;
+ if (request->GetHSTSRedirect(&redirect_url)) {
+ return new URLRequestRedirectJob(
+ request, network_delegate, redirect_url,
+ // Use status code 307 to preserve the method, so POST requests work.
+ URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT);
+ }
+ return new URLRequestHttpJob(request,
+ network_delegate,
+ request->context()->http_user_agent_settings());
+}
+
+
+URLRequestHttpJob::URLRequestHttpJob(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const HttpUserAgentSettings* http_user_agent_settings)
+ : URLRequestJob(request, network_delegate),
+ response_info_(NULL),
+ response_cookies_save_index_(0),
+ proxy_auth_state_(AUTH_STATE_DONT_NEED_AUTH),
+ server_auth_state_(AUTH_STATE_DONT_NEED_AUTH),
+ ALLOW_THIS_IN_INITIALIZER_LIST(start_callback_(
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ base::Unretained(this)))),
+ ALLOW_THIS_IN_INITIALIZER_LIST(notify_before_headers_sent_callback_(
+ base::Bind(&URLRequestHttpJob::NotifyBeforeSendHeadersCallback,
+ base::Unretained(this)))),
+ read_in_progress_(false),
+ transaction_(NULL),
+ throttling_entry_(NULL),
+ sdch_dictionary_advertised_(false),
+ sdch_test_activated_(false),
+ sdch_test_control_(false),
+ is_cached_content_(false),
+ request_creation_time_(),
+ packet_timing_enabled_(false),
+ done_(false),
+ bytes_observed_in_packets_(0),
+ request_time_snapshot_(),
+ final_packet_time_(),
+ ALLOW_THIS_IN_INITIALIZER_LIST(
+ filter_context_(new HttpFilterContext(this))),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)),
+ ALLOW_THIS_IN_INITIALIZER_LIST(on_headers_received_callback_(
+ base::Bind(&URLRequestHttpJob::OnHeadersReceivedCallback,
+ base::Unretained(this)))),
+ awaiting_callback_(false),
+ http_transaction_delegate_(new HttpTransactionDelegateImpl(request)),
+ http_user_agent_settings_(http_user_agent_settings) {
+ URLRequestThrottlerManager* manager = request->context()->throttler_manager();
+ if (manager)
+ throttling_entry_ = manager->RegisterRequestUrl(request->url());
+
+ ResetTimer();
+}
+
+void URLRequestHttpJob::NotifyHeadersComplete() {
+ DCHECK(!response_info_);
+
+ response_info_ = transaction_->GetResponseInfo();
+
+ // Save boolean, as we'll need this info at destruction time, and filters may
+ // also need this info.
+ is_cached_content_ = response_info_->was_cached;
+
+ if (!is_cached_content_ && throttling_entry_) {
+ URLRequestThrottlerHeaderAdapter response_adapter(GetResponseHeaders());
+ throttling_entry_->UpdateWithResponse(request_info_.url.host(),
+ &response_adapter);
+ }
+
+ // The ordering of these calls is not important.
+ ProcessStrictTransportSecurityHeader();
+ ProcessPublicKeyPinsHeader();
+
+#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
+ if (SdchManager::Global() &&
+ SdchManager::Global()->IsInSupportedDomain(request_->url())) {
+ const std::string name = "Get-Dictionary";
+ std::string url_text;
+ void* iter = NULL;
+ // TODO(jar): We need to not fetch dictionaries the first time they are
+ // seen, but rather wait until we can justify their usefulness.
+ // For now, we will only fetch the first dictionary, which will at least
+ // require multiple suggestions before we get additional ones for this site.
+ // Eventually we should wait until a dictionary is requested several times
+ // before we even download it (so that we don't waste memory or bandwidth).
+ if (GetResponseHeaders()->EnumerateHeader(&iter, name, &url_text)) {
+ // request_->url() won't be valid in the destructor, so we use an
+ // alternate copy.
+ DCHECK_EQ(request_->url(), request_info_.url);
+ // Resolve suggested URL relative to request url.
+ sdch_dictionary_url_ = request_info_.url.Resolve(url_text);
+ }
+ }
+#endif
+
+ // The HTTP transaction may be restarted several times for the purposes
+ // of sending authorization information. Each time it restarts, we get
+ // notified of the headers completion so that we can update the cookie store.
+ if (transaction_->IsReadyToRestartForAuth()) {
+ DCHECK(!response_info_->auth_challenge.get());
+ // TODO(battre): This breaks the webrequest API for
+ // URLRequestTestHTTP.BasicAuthWithCookies
+ // where OnBeforeSendHeaders -> OnSendHeaders -> OnBeforeSendHeaders
+ // occurs.
+ RestartTransactionWithAuth(AuthCredentials());
+ return;
+ }
+
+ URLRequestJob::NotifyHeadersComplete();
+}
+
+void URLRequestHttpJob::NotifyDone(const URLRequestStatus& status) {
+ DoneWithRequest(FINISHED);
+ URLRequestJob::NotifyDone(status);
+}
+
+void URLRequestHttpJob::DestroyTransaction() {
+ DCHECK(transaction_.get());
+
+ DoneWithRequest(ABORTED);
+ transaction_.reset();
+ response_info_ = NULL;
+}
+
+void URLRequestHttpJob::StartTransaction() {
+ if (request_->context()->network_delegate()) {
+ int rv = request_->context()->network_delegate()->NotifyBeforeSendHeaders(
+ request_, notify_before_headers_sent_callback_,
+ &request_info_.extra_headers);
+ // If an extension blocks the request, we rely on the callback to
+ // MaybeStartTransactionInternal().
+ if (rv == ERR_IO_PENDING) {
+ SetBlockedOnDelegate();
+ return;
+ }
+ MaybeStartTransactionInternal(rv);
+ return;
+ }
+ StartTransactionInternal();
+}
+
+void URLRequestHttpJob::NotifyBeforeSendHeadersCallback(int result) {
+ SetUnblockedOnDelegate();
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, GetStatus().status());
+
+ MaybeStartTransactionInternal(result);
+}
+
+void URLRequestHttpJob::MaybeStartTransactionInternal(int result) {
+ if (result == OK) {
+ StartTransactionInternal();
+ } else {
+ std::string source("delegate");
+ request_->net_log().AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source", &source));
+ NotifyCanceled();
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestHttpJob::StartTransactionInternal() {
+ // NOTE: This method assumes that request_info_ is already setup properly.
+
+ // If we already have a transaction, then we should restart the transaction
+ // with auth provided by auth_credentials_.
+
+ int rv;
+
+ if (request_->context()->network_delegate()) {
+ request_->context()->network_delegate()->NotifySendHeaders(
+ request_, request_info_.extra_headers);
+ }
+
+ if (transaction_.get()) {
+ rv = transaction_->RestartWithAuth(auth_credentials_, start_callback_);
+ auth_credentials_ = AuthCredentials();
+ } else {
+ DCHECK(request_->context()->http_transaction_factory());
+
+ rv = request_->context()->http_transaction_factory()->CreateTransaction(
+ &transaction_, http_transaction_delegate_.get());
+ if (rv == OK) {
+ if (!throttling_entry_ ||
+ !throttling_entry_->ShouldRejectRequest(*request_)) {
+ rv = transaction_->Start(
+ &request_info_, start_callback_, request_->net_log());
+ start_time_ = base::TimeTicks::Now();
+ } else {
+ // Special error code for the exponential back-off module.
+ rv = ERR_TEMPORARILY_THROTTLED;
+ }
+ }
+ }
+
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void URLRequestHttpJob::AddExtraHeaders() {
+ // Supply Accept-Encoding field only if it is not already provided.
+ // It should be provided IF the content is known to have restrictions on
+ // potential encoding, such as streaming multi-media.
+ // For details see bug 47381.
+ // TODO(jar, enal): jpeg files etc. should set up a request header if
+ // possible. Right now it is done only by buffered_resource_loader and
+ // simple_data_source.
+ if (!request_info_.extra_headers.HasHeader(
+ HttpRequestHeaders::kAcceptEncoding)) {
+#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
+ bool advertise_sdch = SdchManager::Global() &&
+ SdchManager::Global()->IsInSupportedDomain(request_->url());
+ std::string avail_dictionaries;
+ if (advertise_sdch) {
+ SdchManager::Global()->GetAvailDictionaryList(request_->url(),
+ &avail_dictionaries);
+
+ // The AllowLatencyExperiment() is only true if we've successfully done a
+ // full SDCH compression recently in this browser session for this host.
+ // Note that for this path, there might be no applicable dictionaries,
+ // and hence we can't participate in the experiment.
+ if (!avail_dictionaries.empty() &&
+ SdchManager::Global()->AllowLatencyExperiment(request_->url())) {
+ // We are participating in the test (or control), and hence we'll
+ // eventually record statistics via either SDCH_EXPERIMENT_DECODE or
+ // SDCH_EXPERIMENT_HOLDBACK, and we'll need some packet timing data.
+ packet_timing_enabled_ = true;
+ if (base::RandDouble() < .01) {
+ sdch_test_control_ = true; // 1% probability.
+ advertise_sdch = false;
+ } else {
+ sdch_test_activated_ = true;
+ }
+ }
+ }
+#else
+ bool advertise_sdch = false;
+#endif
+
+ // Supply Accept-Encoding headers first so that it is more likely that they
+ // will be in the first transmitted packet. This can sometimes make it
+ // easier to filter and analyze the streams to assure that a proxy has not
+ // damaged these headers. Some proxies deliberately corrupt Accept-Encoding
+ // headers.
+ if (!advertise_sdch) {
+ // Tell the server what compression formats we support (other than SDCH).
+ request_info_.extra_headers.SetHeader(
+ HttpRequestHeaders::kAcceptEncoding, "gzip,deflate");
+ } else {
+#if !defined(__LB_SHELL__) && !defined(COBALT) && !defined(OS_STARBOARD)
+ // Include SDCH in acceptable list.
+ request_info_.extra_headers.SetHeader(
+ HttpRequestHeaders::kAcceptEncoding, "gzip,deflate,sdch");
+ if (!avail_dictionaries.empty()) {
+ request_info_.extra_headers.SetHeader(
+ kAvailDictionaryHeader,
+ avail_dictionaries);
+ sdch_dictionary_advertised_ = true;
+ // Since we're tagging this transaction as advertising a dictionary,
+ // we'll definitely employ an SDCH filter (or tentative sdch filter)
+ // when we get a response. When done, we'll record histograms via
+ // SDCH_DECODE or SDCH_PASSTHROUGH. Hence we need to record packet
+ // arrival times.
+ packet_timing_enabled_ = true;
+ }
+#endif
+ }
+ }
+
+ if (http_user_agent_settings_) {
+ // Only add default Accept-Language and Accept-Charset if the request
+ // didn't have them specified.
+ std::string accept_language =
+ http_user_agent_settings_->GetAcceptLanguage();
+ if (!accept_language.empty()) {
+ request_info_.extra_headers.SetHeaderIfMissing(
+ HttpRequestHeaders::kAcceptLanguage,
+ accept_language);
+ }
+ std::string accept_charset = http_user_agent_settings_->GetAcceptCharset();
+ if (!accept_charset.empty()) {
+ request_info_.extra_headers.SetHeaderIfMissing(
+ HttpRequestHeaders::kAcceptCharset,
+ accept_charset);
+ }
+ }
+}
+
+void URLRequestHttpJob::AddCookieHeaderAndStart() {
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ // If the request was destroyed, then there is no more work to do.
+ if (!request_)
+ return;
+
+ CookieStore* cookie_store = request_->context()->cookie_store();
+ if (cookie_store && !(request_info_.load_flags & LOAD_DO_NOT_SEND_COOKIES)) {
+ net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster();
+ if (cookie_monster) {
+ cookie_monster->GetAllCookiesForURLAsync(
+ request_->url(),
+ base::Bind(&URLRequestHttpJob::CheckCookiePolicyAndLoad,
+ weak_factory_.GetWeakPtr()));
+ } else {
+ CheckCookiePolicyAndLoad(CookieList());
+ }
+ } else {
+ DoStartTransaction();
+ }
+}
+
+void URLRequestHttpJob::DoLoadCookies() {
+ CookieOptions options;
+ options.set_include_httponly();
+ request_->context()->cookie_store()->GetCookiesWithInfoAsync(
+ request_->url(), options,
+ base::Bind(&URLRequestHttpJob::OnCookiesLoaded,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestHttpJob::CheckCookiePolicyAndLoad(
+ const CookieList& cookie_list) {
+ if (CanGetCookies(cookie_list))
+ DoLoadCookies();
+ else
+ DoStartTransaction();
+}
+
+void URLRequestHttpJob::OnCookiesLoaded(
+ const std::string& cookie_line,
+ const std::vector<net::CookieStore::CookieInfo>& cookie_infos) {
+ if (!cookie_line.empty()) {
+ request_info_.extra_headers.SetHeader(
+ HttpRequestHeaders::kCookie, cookie_line);
+ }
+ DoStartTransaction();
+}
+
+void URLRequestHttpJob::DoStartTransaction() {
+ // We may have been canceled while retrieving cookies.
+ if (GetStatus().is_success()) {
+ StartTransaction();
+ } else {
+ NotifyCanceled();
+ }
+}
+
+void URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete(int result) {
+ if (result != net::OK) {
+ std::string source("delegate");
+ request_->net_log().AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source", &source));
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ return;
+ }
+
+ DCHECK(transaction_.get());
+
+ const HttpResponseInfo* response_info = transaction_->GetResponseInfo();
+ DCHECK(response_info);
+
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+
+ FetchResponseCookies(&response_cookies_);
+
+ if (!GetResponseHeaders()->GetDateValue(&response_date_))
+ response_date_ = base::Time();
+
+ // Now, loop over the response cookies, and attempt to persist each.
+ SaveNextCookie();
+}
+
+// If the save occurs synchronously, SaveNextCookie will loop and save the next
+// cookie. If the save is deferred, the callback is responsible for continuing
+// to iterate through the cookies.
+// TODO(erikwright): Modify the CookieStore API to indicate via return value
+// whether it completed synchronously or asynchronously.
+// See http://crbug.com/131066.
+void URLRequestHttpJob::SaveNextCookie() {
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ // Used to communicate with the callback. See the implementation of
+ // OnCookieSaved.
+ scoped_refptr<SharedBoolean> callback_pending = new SharedBoolean(false);
+ scoped_refptr<SharedBoolean> save_next_cookie_running =
+ new SharedBoolean(true);
+
+ if (!(request_info_.load_flags & LOAD_DO_NOT_SAVE_COOKIES) &&
+ request_->context()->cookie_store() &&
+ response_cookies_.size() > 0) {
+ CookieOptions options;
+ options.set_include_httponly();
+ options.set_server_time(response_date_);
+
+ net::CookieStore::SetCookiesCallback callback(
+ base::Bind(&URLRequestHttpJob::OnCookieSaved,
+ weak_factory_.GetWeakPtr(),
+ save_next_cookie_running,
+ callback_pending));
+
+ // Loop through the cookies as long as SetCookieWithOptionsAsync completes
+ // synchronously.
+ while (!callback_pending->data &&
+ response_cookies_save_index_ < response_cookies_.size()) {
+ if (CanSetCookie(
+ response_cookies_[response_cookies_save_index_], &options)) {
+ callback_pending->data = true;
+ request_->context()->cookie_store()->SetCookieWithOptionsAsync(
+ request_->url(), response_cookies_[response_cookies_save_index_],
+ options, callback);
+ }
+ ++response_cookies_save_index_;
+ }
+ }
+
+ save_next_cookie_running->data = false;
+
+ if (!callback_pending->data) {
+ response_cookies_.clear();
+ response_cookies_save_index_ = 0;
+ SetStatus(URLRequestStatus()); // Clear the IO_PENDING status
+ NotifyHeadersComplete();
+ return;
+ }
+}
+
+// |save_next_cookie_running| is true when the callback is bound and set to
+// false when SaveNextCookie exits, allowing the callback to determine if the
+// save occurred synchronously or asynchronously.
+// |callback_pending| is false when the callback is invoked and will be set to
+// true by the callback, allowing SaveNextCookie to detect whether the save
+// occurred synchronously.
+// See SaveNextCookie() for more information.
+void URLRequestHttpJob::OnCookieSaved(
+ scoped_refptr<SharedBoolean> save_next_cookie_running,
+ scoped_refptr<SharedBoolean> callback_pending,
+ bool cookie_status) {
+ callback_pending->data = false;
+
+ // If we were called synchronously, return.
+ if (save_next_cookie_running->data) {
+ return;
+ }
+
+ // We were called asynchronously, so trigger the next save.
+ // We may have been canceled within OnSetCookie.
+ if (GetStatus().is_success()) {
+ SaveNextCookie();
+ } else {
+ NotifyCanceled();
+ }
+}
+
+void URLRequestHttpJob::FetchResponseCookies(
+ std::vector<std::string>* cookies) {
+ const std::string name = "Set-Cookie";
+ std::string value;
+
+ void* iter = NULL;
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ while (headers->EnumerateHeader(&iter, name, &value)) {
+ if (!value.empty())
+ cookies->push_back(value);
+ }
+}
+
+// NOTE: |ProcessStrictTransportSecurityHeader| and
+// |ProcessPublicKeyPinsHeader| have very similar structures, by design.
+// They manipulate different parts of |TransportSecurityState::DomainState|,
+// and they must remain complementary. If, in future changes here, there is
+// any conflict between their policies (such as in |domain_state.mode|), you
+// should resolve the conflict in favor of the more strict policy.
+void URLRequestHttpJob::ProcessStrictTransportSecurityHeader() {
+ DCHECK(response_info_);
+
+ const URLRequestContext* ctx = request_->context();
+ const SSLInfo& ssl_info = response_info_->ssl_info;
+
+ // Only accept strict transport security headers on HTTPS connections that
+ // have no certificate errors.
+ if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) ||
+ !ctx->transport_security_state()) {
+ return;
+ }
+
+ TransportSecurityState* security_state = ctx->transport_security_state();
+ TransportSecurityState::DomainState domain_state;
+ const std::string& host = request_info_.url.host();
+
+ bool sni_available =
+ SSLConfigService::IsSNIAvailable(ctx->ssl_config_service());
+ if (!security_state->GetDomainState(host, sni_available, &domain_state))
+ // |GetDomainState| may have altered |domain_state| while searching. If
+ // not found, start with a fresh state.
+ domain_state.upgrade_mode =
+ TransportSecurityState::DomainState::MODE_FORCE_HTTPS;
+
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ std::string value;
+ void* iter = NULL;
+ base::Time now = base::Time::Now();
+
+ // http://tools.ietf.org/html/draft-ietf-websec-strict-transport-sec:
+ //
+ // If a UA receives more than one STS header field in a HTTP response
+ // message over secure transport, then the UA MUST process only the
+ // first such header field.
+ bool seen_sts = false;
+ while (headers->EnumerateHeader(&iter, "Strict-Transport-Security", &value)) {
+ if (seen_sts)
+ return;
+ seen_sts = true;
+ TransportSecurityState::DomainState domain_state;
+ if (domain_state.ParseSTSHeader(now, value))
+ security_state->EnableHost(host, domain_state);
+ }
+}
+
+void URLRequestHttpJob::ProcessPublicKeyPinsHeader() {
+ DCHECK(response_info_);
+
+ const URLRequestContext* ctx = request_->context();
+ const SSLInfo& ssl_info = response_info_->ssl_info;
+
+ // Only accept public key pins headers on HTTPS connections that have no
+ // certificate errors.
+ if (!ssl_info.is_valid() || IsCertStatusError(ssl_info.cert_status) ||
+ !ctx->transport_security_state()) {
+ return;
+ }
+
+ TransportSecurityState* security_state = ctx->transport_security_state();
+ TransportSecurityState::DomainState domain_state;
+ const std::string& host = request_info_.url.host();
+
+ bool sni_available =
+ SSLConfigService::IsSNIAvailable(ctx->ssl_config_service());
+ if (!security_state->GetDomainState(host, sni_available, &domain_state))
+ // |GetDomainState| may have altered |domain_state| while searching. If
+ // not found, start with a fresh state.
+ domain_state.upgrade_mode =
+ TransportSecurityState::DomainState::MODE_DEFAULT;
+
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ void* iter = NULL;
+ std::string value;
+ base::Time now = base::Time::Now();
+
+ while (headers->EnumerateHeader(&iter, "Public-Key-Pins", &value)) {
+ // Note that ParsePinsHeader updates |domain_state| (iff the header parses
+ // correctly), but does not completely overwrite it. It just updates the
+ // dynamic pinning metadata.
+ if (domain_state.ParsePinsHeader(now, value, ssl_info))
+ security_state->EnableHost(host, domain_state);
+ }
+}
+
+void URLRequestHttpJob::OnStartCompleted(int result) {
+ RecordTimer();
+
+ // If the request was destroyed, then there is no more work to do.
+ if (!request_)
+ return;
+
+ // If the transaction was destroyed, then the job was cancelled, and
+ // we can just ignore this notification.
+ if (!transaction_.get())
+ return;
+
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+
+ const URLRequestContext* context = request_->context();
+
+ if (result == ERR_SSL_PINNED_KEY_NOT_IN_CERT_CHAIN &&
+ transaction_->GetResponseInfo() != NULL) {
+ FraudulentCertificateReporter* reporter =
+ context->fraudulent_certificate_reporter();
+ if (reporter != NULL) {
+ const SSLInfo& ssl_info = transaction_->GetResponseInfo()->ssl_info;
+ bool sni_available = SSLConfigService::IsSNIAvailable(
+ context->ssl_config_service());
+ const std::string& host = request_->url().host();
+
+ reporter->SendReport(host, ssl_info, sni_available);
+ }
+ }
+
+ if (result == OK) {
+ scoped_refptr<HttpResponseHeaders> headers = GetResponseHeaders();
+ if (context->network_delegate()) {
+ // Note that |this| may not be deleted until
+ // |on_headers_received_callback_| or
+ // |NetworkDelegate::URLRequestDestroyed()| has been called.
+ int error = context->network_delegate()->
+ NotifyHeadersReceived(request_, on_headers_received_callback_,
+ headers, &override_response_headers_);
+ if (error != net::OK) {
+ if (error == net::ERR_IO_PENDING) {
+ awaiting_callback_ = true;
+ SetBlockedOnDelegate();
+ } else {
+ std::string source("delegate");
+ request_->net_log().AddEvent(NetLog::TYPE_CANCELLED,
+ NetLog::StringCallback("source",
+ &source));
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, error));
+ }
+ return;
+ }
+ }
+
+ SaveCookiesAndNotifyHeadersComplete(net::OK);
+ } else if (IsCertificateError(result)) {
+ // We encountered an SSL certificate error. Ask our delegate to decide
+ // what we should do.
+
+ TransportSecurityState::DomainState domain_state;
+ const URLRequestContext* context = request_->context();
+ const bool fatal =
+ context->transport_security_state() &&
+ context->transport_security_state()->GetDomainState(
+ request_info_.url.host(),
+ SSLConfigService::IsSNIAvailable(context->ssl_config_service()),
+ &domain_state);
+ NotifySSLCertificateError(transaction_->GetResponseInfo()->ssl_info, fatal);
+ } else if (result == ERR_SSL_CLIENT_AUTH_CERT_NEEDED) {
+ NotifyCertificateRequested(
+ transaction_->GetResponseInfo()->cert_request_info);
+ } else {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+void URLRequestHttpJob::OnHeadersReceivedCallback(int result) {
+ SetUnblockedOnDelegate();
+ awaiting_callback_ = false;
+
+ // Check that there are no callbacks to already canceled requests.
+ DCHECK_NE(URLRequestStatus::CANCELED, GetStatus().status());
+
+ SaveCookiesAndNotifyHeadersComplete(result);
+}
+
+void URLRequestHttpJob::OnReadCompleted(int result) {
+ read_in_progress_ = false;
+
+ if (ShouldFixMismatchedContentLength(result))
+ result = OK;
+
+ if (result == OK) {
+ NotifyDone(URLRequestStatus());
+ } else if (result < 0) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, result));
+ } else {
+ // Clear the IO_PENDING status
+ SetStatus(URLRequestStatus());
+ }
+
+ NotifyReadComplete(result);
+}
+
+void URLRequestHttpJob::RestartTransactionWithAuth(
+ const AuthCredentials& credentials) {
+ auth_credentials_ = credentials;
+
+ // These will be reset in OnStartCompleted.
+ response_info_ = NULL;
+ response_cookies_.clear();
+
+ ResetTimer();
+
+ // Update the cookies, since the cookie store may have been updated from the
+ // headers in the 401/407. Since cookies were already appended to
+ // extra_headers, we need to strip them out before adding them again.
+ request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kCookie);
+
+ AddCookieHeaderAndStart();
+}
+
+void URLRequestHttpJob::SetUpload(UploadDataStream* upload) {
+ DCHECK(!transaction_.get()) << "cannot change once started";
+ request_info_.upload_data_stream = upload;
+}
+
+void URLRequestHttpJob::SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) {
+ DCHECK(!transaction_.get()) << "cannot change once started";
+ request_info_.extra_headers.CopyFrom(headers);
+}
+
+void URLRequestHttpJob::Start() {
+ DCHECK(!transaction_.get());
+
+ // Ensure that we do not send username and password fields in the referrer.
+ GURL referrer(request_->GetSanitizedReferrer());
+
+ request_info_.url = request_->url();
+ request_info_.method = request_->method();
+ request_info_.load_flags = request_->load_flags();
+ request_info_.priority = request_->priority();
+ request_info_.request_id = request_->identifier();
+
+ // Strip Referer from request_info_.extra_headers to prevent, e.g., plugins
+ // from overriding headers that are controlled using other means. Otherwise a
+ // plugin could set a referrer although sending the referrer is inhibited.
+ request_info_.extra_headers.RemoveHeader(HttpRequestHeaders::kReferer);
+
+ // Our consumer should have made sure that this is a safe referrer. See for
+ // instance WebCore::FrameLoader::HideReferrer.
+ if (referrer.is_valid()) {
+ request_info_.extra_headers.SetHeader(HttpRequestHeaders::kReferer,
+ referrer.spec());
+ }
+
+ request_info_.extra_headers.SetHeaderIfMissing(
+ HttpRequestHeaders::kUserAgent,
+ http_user_agent_settings_ ? http_user_agent_settings_->GetUserAgent()
+ : EmptyString());
+
+ AddExtraHeaders();
+ AddCookieHeaderAndStart();
+}
+
+void URLRequestHttpJob::Kill() {
+ http_transaction_delegate_->OnDetachRequest();
+
+ if (!transaction_.get())
+ return;
+
+ weak_factory_.InvalidateWeakPtrs();
+ DestroyTransaction();
+ URLRequestJob::Kill();
+}
+
+LoadState URLRequestHttpJob::GetLoadState() const {
+ return transaction_.get() ?
+ transaction_->GetLoadState() : LOAD_STATE_IDLE;
+}
+
+UploadProgress URLRequestHttpJob::GetUploadProgress() const {
+ return transaction_.get() ?
+ transaction_->GetUploadProgress() : UploadProgress();
+}
+
+bool URLRequestHttpJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return false;
+
+ return GetResponseHeaders()->GetMimeType(mime_type);
+}
+
+bool URLRequestHttpJob::GetCharset(std::string* charset) {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return false;
+
+ return GetResponseHeaders()->GetCharset(charset);
+}
+
+void URLRequestHttpJob::GetResponseInfo(HttpResponseInfo* info) {
+ DCHECK(request_);
+ DCHECK(transaction_.get());
+
+ if (response_info_) {
+ *info = *response_info_;
+ if (override_response_headers_)
+ info->headers = override_response_headers_;
+ }
+}
+
+bool URLRequestHttpJob::GetResponseCookies(std::vector<std::string>* cookies) {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return false;
+
+ // TODO(darin): Why are we extracting response cookies again? Perhaps we
+ // should just leverage response_cookies_.
+
+ cookies->clear();
+ FetchResponseCookies(cookies);
+ return true;
+}
+
+int URLRequestHttpJob::GetResponseCode() const {
+ DCHECK(transaction_.get());
+
+ if (!response_info_)
+ return -1;
+
+ return GetResponseHeaders()->response_code();
+}
+
+Filter* URLRequestHttpJob::SetupFilter() const {
+ DCHECK(transaction_.get());
+ if (!response_info_)
+ return NULL;
+
+ std::vector<Filter::FilterType> encoding_types;
+ std::string encoding_type;
+ HttpResponseHeaders* headers = GetResponseHeaders();
+ void* iter = NULL;
+ while (headers->EnumerateHeader(&iter, "Content-Encoding", &encoding_type)) {
+ encoding_types.push_back(Filter::ConvertEncodingToType(encoding_type));
+ }
+
+ if (filter_context_->IsSdchResponse()) {
+ // We are wary of proxies that discard or damage SDCH encoding. If a server
+ // explicitly states that this is not SDCH content, then we can correct our
+ // assumption that this is an SDCH response, and avoid the need to recover
+ // as though the content is corrupted (when we discover it is not SDCH
+ // encoded).
+ std::string sdch_response_status;
+ iter = NULL;
+ while (headers->EnumerateHeader(&iter, "X-Sdch-Encode",
+ &sdch_response_status)) {
+ if (sdch_response_status == "0") {
+ filter_context_->ResetSdchResponseToFalse();
+ break;
+ }
+ }
+ }
+
+ // Even if encoding types are empty, there is a chance that we need to add
+ // some decoding, as some proxies strip encoding completely. In such cases,
+ // we may need to add (for example) SDCH filtering (when the context suggests
+ // it is appropriate).
+ Filter::FixupEncodingTypes(*filter_context_, &encoding_types);
+
+ return !encoding_types.empty()
+ ? Filter::Factory(encoding_types, *filter_context_) : NULL;
+}
+
+bool URLRequestHttpJob::IsSafeRedirect(const GURL& location) {
+ // We only allow redirects to certain "safe" protocols. This does not
+ // restrict redirects to externally handled protocols. Our consumer would
+ // need to take care of those.
+
+ if (!URLRequest::IsHandledURL(location))
+ return true;
+
+ static const char* kSafeSchemes[] = {
+ "http",
+ "https",
+ "ftp"
+ };
+
+ for (size_t i = 0; i < arraysize(kSafeSchemes); ++i) {
+ if (location.SchemeIs(kSafeSchemes[i]))
+ return true;
+ }
+
+ return false;
+}
+
+bool URLRequestHttpJob::NeedsAuth() {
+ int code = GetResponseCode();
+ if (code == -1)
+ return false;
+
+ // Check if we need either Proxy or WWW Authentication. This could happen
+ // because we either provided no auth info, or provided incorrect info.
+ switch (code) {
+ case 407:
+ if (proxy_auth_state_ == AUTH_STATE_CANCELED)
+ return false;
+ proxy_auth_state_ = AUTH_STATE_NEED_AUTH;
+ return true;
+ case 401:
+ if (server_auth_state_ == AUTH_STATE_CANCELED)
+ return false;
+ server_auth_state_ = AUTH_STATE_NEED_AUTH;
+ return true;
+ }
+ return false;
+}
+
+void URLRequestHttpJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* result) {
+ DCHECK(transaction_.get());
+ DCHECK(response_info_);
+
+ // sanity checks:
+ DCHECK(proxy_auth_state_ == AUTH_STATE_NEED_AUTH ||
+ server_auth_state_ == AUTH_STATE_NEED_AUTH);
+ DCHECK((GetResponseHeaders()->response_code() == HTTP_UNAUTHORIZED) ||
+ (GetResponseHeaders()->response_code() ==
+ HTTP_PROXY_AUTHENTICATION_REQUIRED));
+
+ *result = response_info_->auth_challenge;
+}
+
+void URLRequestHttpJob::SetAuth(const AuthCredentials& credentials) {
+ DCHECK(transaction_.get());
+
+ // Proxy gets set first, then WWW.
+ if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
+ proxy_auth_state_ = AUTH_STATE_HAVE_AUTH;
+ } else {
+ DCHECK_EQ(server_auth_state_, AUTH_STATE_NEED_AUTH);
+ server_auth_state_ = AUTH_STATE_HAVE_AUTH;
+ }
+
+ RestartTransactionWithAuth(credentials);
+}
+
+void URLRequestHttpJob::CancelAuth() {
+ // Proxy gets set first, then WWW.
+ if (proxy_auth_state_ == AUTH_STATE_NEED_AUTH) {
+ proxy_auth_state_ = AUTH_STATE_CANCELED;
+ } else {
+ DCHECK_EQ(server_auth_state_, AUTH_STATE_NEED_AUTH);
+ server_auth_state_ = AUTH_STATE_CANCELED;
+ }
+
+ // These will be reset in OnStartCompleted.
+ response_info_ = NULL;
+ response_cookies_.clear();
+
+ ResetTimer();
+
+ // OK, let the consumer read the error page...
+ //
+ // Because we set the AUTH_STATE_CANCELED flag, NeedsAuth will return false,
+ // which will cause the consumer to receive OnResponseStarted instead of
+ // OnAuthRequired.
+ //
+ // We have to do this via InvokeLater to avoid "recursing" the consumer.
+ //
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), OK));
+}
+
+void URLRequestHttpJob::ContinueWithCertificate(
+ X509Certificate* client_cert) {
+ DCHECK(transaction_.get());
+
+ DCHECK(!response_info_) << "should not have a response yet";
+
+ ResetTimer();
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv = transaction_->RestartWithCertificate(client_cert, start_callback_);
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+void URLRequestHttpJob::ContinueDespiteLastError() {
+ // If the transaction was destroyed, then the job was cancelled.
+ if (!transaction_.get())
+ return;
+
+ DCHECK(!response_info_) << "should not have a response yet";
+
+ ResetTimer();
+
+ // No matter what, we want to report our status as IO pending since we will
+ // be notifying our consumer asynchronously via OnStartCompleted.
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+
+ int rv = transaction_->RestartIgnoringLastError(start_callback_);
+ if (rv == ERR_IO_PENDING)
+ return;
+
+ // The transaction started synchronously, but we need to notify the
+ // URLRequest delegate via the message loop.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestHttpJob::OnStartCompleted,
+ weak_factory_.GetWeakPtr(), rv));
+}
+
+bool URLRequestHttpJob::ShouldFixMismatchedContentLength(int rv) const {
+ // Some servers send the body compressed, but specify the content length as
+ // the uncompressed size. Although this violates the HTTP spec we want to
+ // support it (as IE and FireFox do), but *only* for an exact match.
+ // See http://crbug.com/79694.
+ if (rv == net::ERR_CONTENT_LENGTH_MISMATCH ||
+ rv == net::ERR_INCOMPLETE_CHUNKED_ENCODING) {
+ if (request_ && request_->response_headers()) {
+ int64 expected_length = request_->response_headers()->GetContentLength();
+ VLOG(1) << __FUNCTION__ << "() "
+ << "\"" << request_->url().spec() << "\""
+ << " content-length = " << expected_length
+ << " pre total = " << prefilter_bytes_read()
+ << " post total = " << postfilter_bytes_read();
+ if (postfilter_bytes_read() == expected_length) {
+ // Clear the error.
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+bool URLRequestHttpJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK_NE(buf_size, 0);
+ DCHECK(bytes_read);
+ DCHECK(!read_in_progress_);
+
+ int rv = transaction_->Read(
+ buf, buf_size,
+ base::Bind(&URLRequestHttpJob::OnReadCompleted, base::Unretained(this)));
+
+ if (ShouldFixMismatchedContentLength(rv))
+ rv = 0;
+
+ if (rv >= 0) {
+ *bytes_read = rv;
+ if (!rv)
+ DoneWithRequest(FINISHED);
+ return true;
+ }
+
+ if (rv == ERR_IO_PENDING) {
+ read_in_progress_ = true;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ } else {
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+ }
+
+ return false;
+}
+
+void URLRequestHttpJob::StopCaching() {
+ if (transaction_.get())
+ transaction_->StopCaching();
+}
+
+void URLRequestHttpJob::DoneReading() {
+ if (transaction_.get())
+ transaction_->DoneReading();
+ DoneWithRequest(FINISHED);
+}
+
+HostPortPair URLRequestHttpJob::GetSocketAddress() const {
+ return response_info_ ? response_info_->socket_address : HostPortPair();
+}
+
+URLRequestHttpJob::~URLRequestHttpJob() {
+ CHECK(!awaiting_callback_);
+
+#if !defined(__LB_SHELL__) && !defined(OS_STARBOARD)
+ DCHECK(!sdch_test_control_ || !sdch_test_activated_);
+ if (!is_cached_content_) {
+ if (sdch_test_control_)
+ RecordPacketStats(FilterContext::SDCH_EXPERIMENT_HOLDBACK);
+ if (sdch_test_activated_)
+ RecordPacketStats(FilterContext::SDCH_EXPERIMENT_DECODE);
+ }
+ // Make sure SDCH filters are told to emit histogram data while
+ // filter_context_ is still alive.
+ DestroyFilters();
+
+ if (sdch_dictionary_url_.is_valid()) {
+ // Prior to reaching the destructor, request_ has been set to a NULL
+ // pointer, so request_->url() is no longer valid in the destructor, and we
+ // use an alternate copy |request_info_.url|.
+ SdchManager* manager = SdchManager::Global();
+ // To be extra safe, since this is a "different time" from when we decided
+ // to get the dictionary, we'll validate that an SdchManager is available.
+ // At shutdown time, care is taken to be sure that we don't delete this
+ // globally useful instance "too soon," so this check is just defensive
+ // coding to assure that IF the system is shutting down, we don't have any
+ // problem if the manager was deleted ahead of time.
+ if (manager) // Defensive programming.
+ manager->FetchDictionary(request_info_.url, sdch_dictionary_url_);
+ }
+#endif
+ DoneWithRequest(ABORTED);
+}
+
+void URLRequestHttpJob::RecordTimer() {
+ if (request_creation_time_.is_null()) {
+ NOTREACHED()
+ << "The same transaction shouldn't start twice without new timing.";
+ return;
+ }
+
+ base::TimeDelta to_start = base::Time::Now() - request_creation_time_;
+ request_creation_time_ = base::Time();
+
+ UMA_HISTOGRAM_MEDIUM_TIMES("Net.HttpTimeToFirstByte", to_start);
+
+ static const bool use_overlapped_read_histogram =
+ base::FieldTrialList::TrialExists("OverlappedReadImpact");
+ if (use_overlapped_read_histogram) {
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpTimeToFirstByte",
+ "OverlappedReadImpact"),
+ to_start);
+ }
+
+ static const bool use_warm_socket_impact_histogram =
+ base::FieldTrialList::TrialExists("WarmSocketImpact");
+ if (use_warm_socket_impact_histogram) {
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpTimeToFirstByte",
+ "WarmSocketImpact"),
+ to_start);
+ }
+
+ static const bool use_prefetch_histogram =
+ base::FieldTrialList::TrialExists("Prefetch");
+ if (use_prefetch_histogram) {
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpTimeToFirstByte",
+ "Prefetch"),
+ to_start);
+ }
+ static const bool use_prerender_histogram =
+ base::FieldTrialList::TrialExists("Prerender");
+ if (use_prerender_histogram) {
+ UMA_HISTOGRAM_MEDIUM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpTimeToFirstByte",
+ "Prerender"),
+ to_start);
+ }
+}
+
+void URLRequestHttpJob::ResetTimer() {
+ if (!request_creation_time_.is_null()) {
+ NOTREACHED()
+ << "The timer was reset before it was recorded.";
+ return;
+ }
+ request_creation_time_ = base::Time::Now();
+}
+
+void URLRequestHttpJob::UpdatePacketReadTimes() {
+ if (!packet_timing_enabled_)
+ return;
+
+ if (filter_input_byte_count() <= bytes_observed_in_packets_) {
+ DCHECK_EQ(filter_input_byte_count(), bytes_observed_in_packets_);
+ return; // No new bytes have arrived.
+ }
+
+ final_packet_time_ = base::Time::Now();
+ if (!bytes_observed_in_packets_)
+ request_time_snapshot_ = request_ ? request_->request_time() : base::Time();
+
+ bytes_observed_in_packets_ = filter_input_byte_count();
+}
+
+void URLRequestHttpJob::RecordPacketStats(
+ FilterContext::StatisticSelector statistic) const {
+ if (!packet_timing_enabled_ || (final_packet_time_ == base::Time()))
+ return;
+
+ base::TimeDelta duration = final_packet_time_ - request_time_snapshot_;
+ switch (statistic) {
+ case FilterContext::SDCH_DECODE: {
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Sdch3.Network_Decode_Bytes_Processed_b",
+ static_cast<int>(bytes_observed_in_packets_), 500, 100000, 100);
+ return;
+ }
+ case FilterContext::SDCH_PASSTHROUGH: {
+ // Despite advertising a dictionary, we handled non-sdch compressed
+ // content.
+ return;
+ }
+
+ case FilterContext::SDCH_EXPERIMENT_DECODE: {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Sdch3.Experiment2_Decode",
+ duration,
+ base::TimeDelta::FromMilliseconds(20),
+ base::TimeDelta::FromMinutes(10), 100);
+ return;
+ }
+ case FilterContext::SDCH_EXPERIMENT_HOLDBACK: {
+ UMA_HISTOGRAM_CUSTOM_TIMES("Sdch3.Experiment2_Holdback",
+ duration,
+ base::TimeDelta::FromMilliseconds(20),
+ base::TimeDelta::FromMinutes(10), 100);
+ return;
+ }
+ default:
+ NOTREACHED();
+ return;
+ }
+}
+
+// The common type of histogram we use for all compression-tracking histograms.
+#define COMPRESSION_HISTOGRAM(name, sample) \
+ do { \
+ UMA_HISTOGRAM_CUSTOM_COUNTS("Net.Compress." name, sample, \
+ 500, 1000000, 100); \
+ } while (0)
+
+void URLRequestHttpJob::RecordCompressionHistograms() {
+ DCHECK(request_);
+ if (!request_)
+ return;
+
+ if (is_cached_content_ || // Don't record cached content
+ !GetStatus().is_success() || // Don't record failed content
+ !IsCompressibleContent() || // Only record compressible content
+ !prefilter_bytes_read()) // Zero-byte responses aren't useful.
+ return;
+
+ // Miniature requests aren't really compressible. Don't count them.
+ const int kMinSize = 16;
+ if (prefilter_bytes_read() < kMinSize)
+ return;
+
+ // Only record for http or https urls.
+ bool is_http = request_->url().SchemeIs("http");
+ bool is_https = request_->url().SchemeIs("https");
+ if (!is_http && !is_https)
+ return;
+
+ int compressed_B = prefilter_bytes_read();
+ int decompressed_B = postfilter_bytes_read();
+ bool was_filtered = HasFilter();
+
+ // We want to record how often downloaded resources are compressed.
+ // But, we recognize that different protocols may have different
+ // properties. So, for each request, we'll put it into one of 3
+ // groups:
+ // a) SSL resources
+ // Proxies cannot tamper with compression headers with SSL.
+ // b) Non-SSL, loaded-via-proxy resources
+ // In this case, we know a proxy might have interfered.
+ // c) Non-SSL, loaded-without-proxy resources
+ // In this case, we know there was no explicit proxy. However,
+ // it is possible that a transparent proxy was still interfering.
+ //
+ // For each group, we record the same 3 histograms.
+
+ if (is_https) {
+ if (was_filtered) {
+ COMPRESSION_HISTOGRAM("SSL.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("SSL.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("SSL.ShouldHaveBeenCompressed", decompressed_B);
+ }
+ return;
+ }
+
+ if (request_->was_fetched_via_proxy()) {
+ if (was_filtered) {
+ COMPRESSION_HISTOGRAM("Proxy.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("Proxy.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("Proxy.ShouldHaveBeenCompressed", decompressed_B);
+ }
+ return;
+ }
+
+ if (was_filtered) {
+ COMPRESSION_HISTOGRAM("NoProxy.BytesBeforeCompression", compressed_B);
+ COMPRESSION_HISTOGRAM("NoProxy.BytesAfterCompression", decompressed_B);
+ } else {
+ COMPRESSION_HISTOGRAM("NoProxy.ShouldHaveBeenCompressed", decompressed_B);
+ }
+}
+
+bool URLRequestHttpJob::IsCompressibleContent() const {
+ std::string mime_type;
+ return GetMimeType(&mime_type) &&
+ (IsSupportedJavascriptMimeType(mime_type.c_str()) ||
+ IsSupportedNonImageMimeType(mime_type.c_str()));
+}
+
+void URLRequestHttpJob::RecordPerfHistograms(CompletionCause reason) {
+ if (start_time_.is_null())
+ return;
+
+ base::TimeDelta total_time = base::TimeTicks::Now() - start_time_;
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTime", total_time);
+
+ if (reason == FINISHED) {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeSuccess", total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeCancel", total_time);
+ }
+
+ if (response_info_) {
+ if (response_info_->was_cached) {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeCached", total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES("Net.HttpJob.TotalTimeNotCached", total_time);
+ }
+ }
+
+ static const bool use_overlapped_read_histogram =
+ base::FieldTrialList::TrialExists("OverlappedReadImpact");
+ if (use_overlapped_read_histogram) {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTime",
+ "OverlappedReadImpact"),
+ total_time);
+
+ if (reason == FINISHED) {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeSuccess",
+ "OverlappedReadImpact"),
+ total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeCancel",
+ "OverlappedReadImpact"),
+ total_time);
+ }
+
+ if (response_info_) {
+ if (response_info_->was_cached) {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeCached",
+ "OverlappedReadImpact"),
+ total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeNotCached",
+ "OverlappedReadImpact"),
+ total_time);
+ }
+ }
+ }
+
+ static const bool cache_sensitivity_analysis =
+ base::FieldTrialList::TrialExists("CacheSensitivityAnalysis");
+ if (cache_sensitivity_analysis) {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTime",
+ "CacheSensitivityAnalysis"),
+ total_time);
+
+ if (reason == FINISHED) {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeSuccess",
+ "CacheSensitivityAnalysis"),
+ total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeCancel",
+ "CacheSensitivityAnalysis"),
+ total_time);
+ }
+
+ if (response_info_) {
+ if (response_info_->was_cached) {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeCached",
+ "CacheSensitivityAnalysis"),
+ total_time);
+ } else {
+ UMA_HISTOGRAM_TIMES(
+ base::FieldTrial::MakeName("Net.HttpJob.TotalTimeNotCached",
+ "CacheSensitivityAnalysis"),
+ total_time);
+ }
+ }
+ }
+
+ start_time_ = base::TimeTicks();
+}
+
+void URLRequestHttpJob::DoneWithRequest(CompletionCause reason) {
+ if (done_)
+ return;
+ done_ = true;
+ RecordPerfHistograms(reason);
+ if (reason == FINISHED) {
+ request_->set_received_response_content_length(prefilter_bytes_read());
+ RecordCompressionHistograms();
+ }
+}
+
+HttpResponseHeaders* URLRequestHttpJob::GetResponseHeaders() const {
+ DCHECK(transaction_.get());
+ DCHECK(transaction_->GetResponseInfo());
+ return override_response_headers_.get() ?
+ override_response_headers_ :
+ transaction_->GetResponseInfo()->headers;
+}
+
+void URLRequestHttpJob::NotifyURLRequestDestroyed() {
+ awaiting_callback_ = false;
+}
+
+void URLRequestHttpJob::OnDetachRequest() {
+ http_transaction_delegate_->OnDetachRequest();
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_http_job.h b/src/net/url_request/url_request_http_job.h
new file mode 100644
index 0000000..9ef3884
--- /dev/null
+++ b/src/net/url_request/url_request_http_job.h
@@ -0,0 +1,258 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/time.h"
+#include "net/base/auth.h"
+#include "net/base/completion_callback.h"
+#include "net/cookies/cookie_store.h"
+#include "net/http/http_request_info.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_throttler_entry_interface.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+class HttpResponseInfo;
+class HttpTransaction;
+class HttpUserAgentSettings;
+class UploadDataStream;
+class URLRequestContext;
+
+// A URLRequestJob subclass that is built on top of HttpTransaction. It
+// provides an implementation for both HTTP and HTTPS.
+class URLRequestHttpJob : public URLRequestJob {
+ public:
+ static URLRequestJob* Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme);
+
+ protected:
+ URLRequestHttpJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const HttpUserAgentSettings* http_user_agent_settings);
+
+ // Shadows URLRequestJob's version of this method so we can grab cookies.
+ void NotifyHeadersComplete();
+
+ // Shadows URLRequestJob's method so we can record histograms.
+ void NotifyDone(const URLRequestStatus& status);
+
+ void DestroyTransaction();
+
+ void AddExtraHeaders();
+ void AddCookieHeaderAndStart();
+ void SaveCookiesAndNotifyHeadersComplete(int result);
+ void SaveNextCookie();
+ void FetchResponseCookies(std::vector<std::string>* cookies);
+
+ // Processes the Strict-Transport-Security header, if one exists.
+ void ProcessStrictTransportSecurityHeader();
+
+ // Processes the Public-Key-Pins header, if one exists.
+ void ProcessPublicKeyPinsHeader();
+
+ // |result| should be net::OK, or the request is canceled.
+ void OnHeadersReceivedCallback(int result);
+ void OnStartCompleted(int result);
+ void OnReadCompleted(int result);
+ void NotifyBeforeSendHeadersCallback(int result);
+
+ void RestartTransactionWithAuth(const AuthCredentials& credentials);
+
+ // Overridden from URLRequestJob:
+ virtual void SetUpload(UploadDataStream* upload) OVERRIDE;
+ virtual void SetExtraRequestHeaders(
+ const HttpRequestHeaders& headers) OVERRIDE;
+ virtual void Start() OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual LoadState GetLoadState() const OVERRIDE;
+ virtual UploadProgress GetUploadProgress() const OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+ virtual void GetResponseInfo(HttpResponseInfo* info) OVERRIDE;
+ virtual bool GetResponseCookies(std::vector<std::string>* cookies) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual Filter* SetupFilter() const OVERRIDE;
+ virtual bool IsSafeRedirect(const GURL& location) OVERRIDE;
+ virtual bool NeedsAuth() OVERRIDE;
+ virtual void GetAuthChallengeInfo(scoped_refptr<AuthChallengeInfo>*) OVERRIDE;
+ virtual void SetAuth(const AuthCredentials& credentials) OVERRIDE;
+ virtual void CancelAuth() OVERRIDE;
+ virtual void ContinueWithCertificate(X509Certificate* client_cert) OVERRIDE;
+ virtual void ContinueDespiteLastError() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) OVERRIDE;
+ virtual void StopCaching() OVERRIDE;
+ virtual void DoneReading() OVERRIDE;
+ virtual HostPortPair GetSocketAddress() const OVERRIDE;
+ virtual void NotifyURLRequestDestroyed() OVERRIDE;
+
+ HttpRequestInfo request_info_;
+ const HttpResponseInfo* response_info_;
+
+ std::vector<std::string> response_cookies_;
+ size_t response_cookies_save_index_;
+ base::Time response_date_;
+
+ // Auth states for proxy and origin server.
+ AuthState proxy_auth_state_;
+ AuthState server_auth_state_;
+ AuthCredentials auth_credentials_;
+
+ CompletionCallback start_callback_;
+ CompletionCallback notify_before_headers_sent_callback_;
+
+ bool read_in_progress_;
+
+ // An URL for an SDCH dictionary as suggested in a Get-Dictionary HTTP header.
+ GURL sdch_dictionary_url_;
+
+ scoped_ptr<HttpTransaction> transaction_;
+
+ // This is used to supervise traffic and enforce exponential
+ // back-off. May be NULL.
+ scoped_refptr<URLRequestThrottlerEntryInterface> throttling_entry_;
+
+ // Indicated if an SDCH dictionary was advertised, and hence an SDCH
+ // compressed response is expected. We use this to help detect (accidental?)
+ // proxy corruption of a response, which sometimes marks SDCH content as
+ // having no content encoding <oops>.
+ bool sdch_dictionary_advertised_;
+
+ // For SDCH latency experiments, when we are able to do SDCH, we may enable
+ // either an SDCH latency test xor a pass through test. The following bools
+ // indicate what we decided on for this instance.
+ bool sdch_test_activated_; // Advertising a dictionary for sdch.
+ bool sdch_test_control_; // Not even accepting-content sdch.
+
+ // For recording of stats, we need to remember if this is cached content.
+ bool is_cached_content_;
+
+ private:
+ enum CompletionCause {
+ ABORTED,
+ FINISHED
+ };
+
+ typedef base::RefCountedData<bool> SharedBoolean;
+
+ class HttpFilterContext;
+ class HttpTransactionDelegateImpl;
+
+ virtual ~URLRequestHttpJob();
+
+ void RecordTimer();
+ void ResetTimer();
+
+ virtual void UpdatePacketReadTimes() OVERRIDE;
+ void RecordPacketStats(FilterContext::StatisticSelector statistic) const;
+
+ void RecordCompressionHistograms();
+ bool IsCompressibleContent() const;
+
+ // Starts the transaction if extensions using the webrequest API do not
+ // object.
+ void StartTransaction();
+ // If |result| is net::OK, calls StartTransactionInternal. Otherwise notifies
+ // cancellation.
+ void MaybeStartTransactionInternal(int result);
+ void StartTransactionInternal();
+
+ void RecordPerfHistograms(CompletionCause reason);
+ void DoneWithRequest(CompletionCause reason);
+
+ // Callback functions for Cookie Monster
+ void DoLoadCookies();
+ void CheckCookiePolicyAndLoad(const CookieList& cookie_list);
+ void OnCookiesLoaded(
+ const std::string& cookie_line,
+ const std::vector<CookieStore::CookieInfo>& cookie_infos);
+ void DoStartTransaction();
+
+ // See the implementation for a description of save_next_cookie_running and
+ // callback_pending.
+ void OnCookieSaved(scoped_refptr<SharedBoolean> save_next_cookie_running,
+ scoped_refptr<SharedBoolean> callback_pending,
+ bool cookie_status);
+
+ // Some servers send the body compressed, but specify the content length as
+ // the uncompressed size. If this is the case, we return true in order
+ // to request to work around this non-adherence to the HTTP standard.
+ // |rv| is the standard return value of a read function indicating the number
+ // of bytes read or, if negative, an error code.
+ bool ShouldFixMismatchedContentLength(int rv) const;
+
+ // Returns the effective response headers, considering that they may be
+ // overridden by |override_response_headers_|.
+ HttpResponseHeaders* GetResponseHeaders() const;
+
+ // Override of the private interface of URLRequestJob.
+ virtual void OnDetachRequest() OVERRIDE;
+
+ base::Time request_creation_time_;
+
+ // Data used for statistics gathering. This data is only used for histograms
+ // and is not required. It is only gathered if packet_timing_enabled_ == true.
+ //
+ // TODO(jar): improve the quality of the gathered info by gathering most times
+ // at a lower point in the network stack, assuring we have actual packet
+ // boundaries, rather than approximations. Also note that input byte count
+ // as gathered here is post-SSL, and post-cache-fetch, and does not reflect
+ // true packet arrival times in such cases.
+
+ // Enable recording of packet arrival times for histogramming.
+ bool packet_timing_enabled_;
+ bool done_; // True when we are done doing work.
+
+ // The number of bytes that have been accounted for in packets (where some of
+ // those packets may possibly have had their time of arrival recorded).
+ int64 bytes_observed_in_packets_;
+
+ // The request time may not be available when we are being destroyed, so we
+ // snapshot it early on.
+ base::Time request_time_snapshot_;
+
+ // Since we don't save all packet times in packet_times_, we save the
+ // last time for use in histograms.
+ base::Time final_packet_time_;
+
+ // The start time for the job, ignoring re-starts.
+ base::TimeTicks start_time_;
+
+ scoped_ptr<HttpFilterContext> filter_context_;
+ base::WeakPtrFactory<URLRequestHttpJob> weak_factory_;
+
+ CompletionCallback on_headers_received_callback_;
+
+ // We allow the network delegate to modify a copy of the response headers.
+ // This prevents modifications of headers that are shared with the underlying
+ // layers of the network stack.
+ scoped_refptr<HttpResponseHeaders> override_response_headers_;
+
+ // Flag used to verify that |this| is not deleted while we are awaiting
+ // a callback from the NetworkDelegate. Used as a fail-fast mechanism.
+ // True if we are waiting a callback and
+ // NetworkDelegate::NotifyURLRequestDestroyed has not been called, yet,
+ // to inform the NetworkDelegate that it may not call back.
+ bool awaiting_callback_;
+
+ scoped_ptr<HttpTransactionDelegateImpl> http_transaction_delegate_;
+
+ const HttpUserAgentSettings* http_user_agent_settings_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestHttpJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_HTTP_JOB_H_
diff --git a/src/net/url_request/url_request_job.cc b/src/net/url_request/url_request_job.cc
new file mode 100644
index 0000000..c929665
--- /dev/null
+++ b/src/net/url_request/url_request_job.cc
@@ -0,0 +1,711 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "base/string_number_conversions.h"
+#include "base/string_util.h"
+#include "net/base/auth.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/io_buffer.h"
+#include "net/base/load_states.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/url_request.h"
+
+namespace net {
+
+URLRequestJob::URLRequestJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : request_(request),
+ done_(false),
+ prefilter_bytes_read_(0),
+ postfilter_bytes_read_(0),
+ filter_input_byte_count_(0),
+ filter_needs_more_output_space_(false),
+ filtered_read_buffer_len_(0),
+ has_handled_response_(false),
+ expected_content_size_(-1),
+ deferred_redirect_status_code_(-1),
+ network_delegate_(network_delegate),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
+ if (system_monitor)
+ base::SystemMonitor::Get()->AddPowerObserver(this);
+}
+
+void URLRequestJob::SetUpload(UploadDataStream* upload) {
+}
+
+void URLRequestJob::SetExtraRequestHeaders(const HttpRequestHeaders& headers) {
+}
+
+void URLRequestJob::Kill() {
+ weak_factory_.InvalidateWeakPtrs();
+ // Make sure the request is notified that we are done. We assume that the
+ // request took care of setting its error status before calling Kill.
+ if (request_)
+ NotifyCanceled();
+}
+
+void URLRequestJob::DetachRequest() {
+ request_ = NULL;
+ OnDetachRequest();
+}
+
+// This function calls ReadData to get stream data. If a filter exists, passes
+// the data to the attached filter. Then returns the output from filter back to
+// the caller.
+bool URLRequestJob::Read(IOBuffer* buf, int buf_size, int *bytes_read) {
+ bool rv = false;
+
+ DCHECK_LT(buf_size, 1000000); // Sanity check.
+ DCHECK(buf);
+ DCHECK(bytes_read);
+ DCHECK(filtered_read_buffer_ == NULL);
+ DCHECK_EQ(0, filtered_read_buffer_len_);
+
+ *bytes_read = 0;
+
+ // Skip Filter if not present.
+ if (!filter_.get()) {
+ rv = ReadRawDataHelper(buf, buf_size, bytes_read);
+ } else {
+ // Save the caller's buffers while we do IO
+ // in the filter's buffers.
+ filtered_read_buffer_ = buf;
+ filtered_read_buffer_len_ = buf_size;
+
+ if (ReadFilteredData(bytes_read)) {
+ rv = true; // We have data to return.
+
+ // It is fine to call DoneReading even if ReadFilteredData receives 0
+ // bytes from the net, but we avoid making that call if we know for
+ // sure that's the case (ReadRawDataHelper path).
+ if (*bytes_read == 0)
+ DoneReading();
+ } else {
+ rv = false; // Error, or a new IO is pending.
+ }
+ }
+ if (rv && *bytes_read == 0)
+ NotifyDone(URLRequestStatus());
+ return rv;
+}
+
+void URLRequestJob::StopCaching() {
+ // Nothing to do here.
+}
+
+LoadState URLRequestJob::GetLoadState() const {
+ return LOAD_STATE_IDLE;
+}
+
+UploadProgress URLRequestJob::GetUploadProgress() const {
+ return UploadProgress();
+}
+
+bool URLRequestJob::GetCharset(std::string* charset) {
+ return false;
+}
+
+void URLRequestJob::GetResponseInfo(HttpResponseInfo* info) {
+}
+
+bool URLRequestJob::GetResponseCookies(std::vector<std::string>* cookies) {
+ return false;
+}
+
+Filter* URLRequestJob::SetupFilter() const {
+ return NULL;
+}
+
+bool URLRequestJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ // For non-HTTP jobs, headers will be null.
+ HttpResponseHeaders* headers = request_->response_headers();
+ if (!headers)
+ return false;
+
+ std::string value;
+ if (!headers->IsRedirect(&value))
+ return false;
+
+ *location = request_->url().Resolve(value);
+ *http_status_code = headers->response_code();
+ return true;
+}
+
+bool URLRequestJob::IsSafeRedirect(const GURL& location) {
+ return true;
+}
+
+bool URLRequestJob::NeedsAuth() {
+ return false;
+}
+
+void URLRequestJob::GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* auth_info) {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::SetAuth(const AuthCredentials& credentials) {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::CancelAuth() {
+ // This will only be called if NeedsAuth() returns true, in which
+ // case the derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::ContinueWithCertificate(
+ X509Certificate* client_cert) {
+ // The derived class should implement this!
+ NOTREACHED();
+}
+
+void URLRequestJob::ContinueDespiteLastError() {
+ // Implementations should know how to recover from errors they generate.
+ // If this code was reached, we are trying to recover from an error that
+ // we don't know how to recover from.
+ NOTREACHED();
+}
+
+void URLRequestJob::FollowDeferredRedirect() {
+ DCHECK(deferred_redirect_status_code_ != -1);
+
+ // NOTE: deferred_redirect_url_ may be invalid, and attempting to redirect to
+ // such an URL will fail inside FollowRedirect. The DCHECK above asserts
+ // that we called OnReceivedRedirect.
+
+ // It is also possible that FollowRedirect will drop the last reference to
+ // this job, so we need to reset our members before calling it.
+
+ SetUnblockedOnDelegate();
+
+ GURL redirect_url = deferred_redirect_url_;
+ int redirect_status_code = deferred_redirect_status_code_;
+
+ deferred_redirect_url_ = GURL();
+ deferred_redirect_status_code_ = -1;
+
+ FollowRedirect(redirect_url, redirect_status_code);
+}
+
+bool URLRequestJob::GetMimeType(std::string* mime_type) const {
+ return false;
+}
+
+int URLRequestJob::GetResponseCode() const {
+ return -1;
+}
+
+HostPortPair URLRequestJob::GetSocketAddress() const {
+ return HostPortPair();
+}
+
+void URLRequestJob::OnSuspend() {
+ Kill();
+}
+
+void URLRequestJob::NotifyURLRequestDestroyed() {
+}
+
+URLRequestJob::~URLRequestJob() {
+ base::SystemMonitor* system_monitor = base::SystemMonitor::Get();
+ if (system_monitor)
+ base::SystemMonitor::Get()->RemovePowerObserver(this);
+}
+
+void URLRequestJob::NotifyCertificateRequested(
+ SSLCertRequestInfo* cert_request_info) {
+ if (!request_)
+ return; // The request was destroyed, so there is no more work to do.
+
+ request_->NotifyCertificateRequested(cert_request_info);
+}
+
+void URLRequestJob::NotifySSLCertificateError(const SSLInfo& ssl_info,
+ bool fatal) {
+ if (!request_)
+ return; // The request was destroyed, so there is no more work to do.
+
+ request_->NotifySSLCertificateError(ssl_info, fatal);
+}
+
+bool URLRequestJob::CanGetCookies(const CookieList& cookie_list) const {
+ if (!request_)
+ return false; // The request was destroyed, so there is no more work to do.
+
+ return request_->CanGetCookies(cookie_list);
+}
+
+bool URLRequestJob::CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const {
+ if (!request_)
+ return false; // The request was destroyed, so there is no more work to do.
+
+ return request_->CanSetCookie(cookie_line, options);
+}
+
+void URLRequestJob::NotifyHeadersComplete() {
+ if (!request_ || !request_->has_delegate())
+ return; // The request was destroyed, so there is no more work to do.
+
+ if (has_handled_response_)
+ return;
+
+ DCHECK(!request_->status().is_io_pending());
+
+ // Initialize to the current time, and let the subclass optionally override
+ // the time stamps if it has that information. The default request_time is
+ // set by URLRequest before it calls our Start method.
+ request_->response_info_.response_time = base::Time::Now();
+ GetResponseInfo(&request_->response_info_);
+
+ // When notifying the delegate, the delegate can release the request
+ // (and thus release 'this'). After calling to the delgate, we must
+ // check the request pointer to see if it still exists, and return
+ // immediately if it has been destroyed. self_preservation ensures our
+ // survival until we can get out of this method.
+ scoped_refptr<URLRequestJob> self_preservation(this);
+
+ GURL new_location;
+ int http_status_code;
+ if (IsRedirectResponse(&new_location, &http_status_code)) {
+ const GURL& url = request_->url();
+
+ // Move the reference fragment of the old location to the new one if the
+ // new one has none. This duplicates mozilla's behavior.
+ if (url.is_valid() && url.has_ref() && !new_location.has_ref()) {
+ GURL::Replacements replacements;
+ // Reference the |ref| directly out of the original URL to avoid a
+ // malloc.
+ replacements.SetRef(url.spec().data(),
+ url.parsed_for_possibly_invalid_spec().ref);
+ new_location = new_location.ReplaceComponents(replacements);
+ }
+
+ bool defer_redirect = false;
+ request_->NotifyReceivedRedirect(new_location, &defer_redirect);
+
+ // Ensure that the request wasn't detached or destroyed in
+ // NotifyReceivedRedirect
+ if (!request_ || !request_->has_delegate())
+ return;
+
+ // If we were not cancelled, then maybe follow the redirect.
+ if (request_->status().is_success()) {
+ if (defer_redirect) {
+ deferred_redirect_url_ = new_location;
+ deferred_redirect_status_code_ = http_status_code;
+ SetBlockedOnDelegate();
+ } else {
+ FollowRedirect(new_location, http_status_code);
+ }
+ return;
+ }
+ } else if (NeedsAuth()) {
+ scoped_refptr<AuthChallengeInfo> auth_info;
+ GetAuthChallengeInfo(&auth_info);
+ // Need to check for a NULL auth_info because the server may have failed
+ // to send a challenge with the 401 response.
+ if (auth_info) {
+ request_->NotifyAuthRequired(auth_info);
+ // Wait for SetAuth or CancelAuth to be called.
+ return;
+ }
+ }
+
+ has_handled_response_ = true;
+ if (request_->status().is_success())
+ filter_.reset(SetupFilter());
+
+ if (!filter_.get()) {
+ std::string content_length;
+ request_->GetResponseHeaderByName("content-length", &content_length);
+ if (!content_length.empty())
+ base::StringToInt64(content_length, &expected_content_size_);
+ }
+
+ request_->NotifyResponseStarted();
+}
+
+void URLRequestJob::NotifyReadComplete(int bytes_read) {
+ if (!request_ || !request_->has_delegate())
+ return; // The request was destroyed, so there is no more work to do.
+
+ // TODO(darin): Bug 1004233. Re-enable this test once all of the chrome
+ // unit_tests have been fixed to not trip this.
+ //DCHECK(!request_->status().is_io_pending());
+
+ // The headers should be complete before reads complete
+ DCHECK(has_handled_response_);
+
+ OnRawReadComplete(bytes_read);
+
+ // Don't notify if we had an error.
+ if (!request_->status().is_success())
+ return;
+
+ // When notifying the delegate, the delegate can release the request
+ // (and thus release 'this'). After calling to the delegate, we must
+ // check the request pointer to see if it still exists, and return
+ // immediately if it has been destroyed. self_preservation ensures our
+ // survival until we can get out of this method.
+ scoped_refptr<URLRequestJob> self_preservation(this);
+
+ if (filter_.get()) {
+ // Tell the filter that it has more data
+ FilteredDataRead(bytes_read);
+
+ // Filter the data.
+ int filter_bytes_read = 0;
+ if (ReadFilteredData(&filter_bytes_read)) {
+ if (!filter_bytes_read)
+ DoneReading();
+ request_->NotifyReadCompleted(filter_bytes_read);
+ }
+ } else {
+ request_->NotifyReadCompleted(bytes_read);
+ }
+ DVLOG(1) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " pre bytes read = " << bytes_read
+ << " pre total = " << prefilter_bytes_read_
+ << " post total = " << postfilter_bytes_read_;
+}
+
+void URLRequestJob::NotifyStartError(const URLRequestStatus &status) {
+ DCHECK(!has_handled_response_);
+ has_handled_response_ = true;
+ if (request_) {
+ request_->set_status(status);
+ request_->NotifyResponseStarted();
+ }
+}
+
+void URLRequestJob::NotifyDone(const URLRequestStatus &status) {
+ DCHECK(!done_) << "Job sending done notification twice";
+ if (done_)
+ return;
+ done_ = true;
+
+ // Unless there was an error, we should have at least tried to handle
+ // the response before getting here.
+ DCHECK(has_handled_response_ || !status.is_success());
+
+ // As with NotifyReadComplete, we need to take care to notice if we were
+ // destroyed during a delegate callback.
+ if (request_) {
+ request_->set_is_pending(false);
+ // With async IO, it's quite possible to have a few outstanding
+ // requests. We could receive a request to Cancel, followed shortly
+ // by a successful IO. For tracking the status(), once there is
+ // an error, we do not change the status back to success. To
+ // enforce this, only set the status if the job is so far
+ // successful.
+ if (request_->status().is_success()) {
+ if (status.status() == URLRequestStatus::FAILED) {
+ request_->net_log().AddEventWithNetErrorCode(NetLog::TYPE_FAILED,
+ status.error());
+ }
+ request_->set_status(status);
+ }
+ }
+
+ // Complete this notification later. This prevents us from re-entering the
+ // delegate if we're done because of a synchronous call.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestJob::CompleteNotifyDone,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestJob::CompleteNotifyDone() {
+ // Check if we should notify the delegate that we're done because of an error.
+ if (request_ &&
+ !request_->status().is_success() &&
+ request_->has_delegate()) {
+ // We report the error differently depending on whether we've called
+ // OnResponseStarted yet.
+ if (has_handled_response_) {
+ // We signal the error by calling OnReadComplete with a bytes_read of -1.
+ request_->NotifyReadCompleted(-1);
+ } else {
+ has_handled_response_ = true;
+ request_->NotifyResponseStarted();
+ }
+ }
+}
+
+void URLRequestJob::NotifyCanceled() {
+ if (!done_) {
+ NotifyDone(URLRequestStatus(URLRequestStatus::CANCELED, ERR_ABORTED));
+ }
+}
+
+void URLRequestJob::NotifyRestartRequired() {
+ DCHECK(!has_handled_response_);
+ if (GetStatus().status() != URLRequestStatus::CANCELED)
+ request_->Restart();
+}
+
+void URLRequestJob::SetBlockedOnDelegate() {
+ request_->SetBlockedOnDelegate();
+}
+
+void URLRequestJob::SetUnblockedOnDelegate() {
+ request_->SetUnblockedOnDelegate();
+}
+
+bool URLRequestJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int *bytes_read) {
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+ return true;
+}
+
+void URLRequestJob::DoneReading() {
+ // Do nothing.
+}
+
+void URLRequestJob::FilteredDataRead(int bytes_read) {
+ DCHECK(filter_.get()); // don't add data if there is no filter
+ filter_->FlushStreamBuffer(bytes_read);
+}
+
+bool URLRequestJob::ReadFilteredData(int* bytes_read) {
+ DCHECK(filter_.get()); // don't add data if there is no filter
+ DCHECK(filtered_read_buffer_ != NULL); // we need to have a buffer to fill
+ DCHECK_GT(filtered_read_buffer_len_, 0); // sanity check
+ DCHECK_LT(filtered_read_buffer_len_, 1000000); // sanity check
+ DCHECK(raw_read_buffer_ == NULL); // there should be no raw read buffer yet
+
+ bool rv = false;
+ *bytes_read = 0;
+
+ if (is_done())
+ return true;
+
+ if (!filter_needs_more_output_space_ && !filter_->stream_data_len()) {
+ // We don't have any raw data to work with, so
+ // read from the socket.
+ int filtered_data_read;
+ if (ReadRawDataForFilter(&filtered_data_read)) {
+ if (filtered_data_read > 0) {
+ filter_->FlushStreamBuffer(filtered_data_read); // Give data to filter.
+ } else {
+ return true; // EOF
+ }
+ } else {
+ return false; // IO Pending (or error)
+ }
+ }
+
+ if ((filter_->stream_data_len() || filter_needs_more_output_space_)
+ && !is_done()) {
+ // Get filtered data.
+ int filtered_data_len = filtered_read_buffer_len_;
+ Filter::FilterStatus status;
+ int output_buffer_size = filtered_data_len;
+ status = filter_->ReadData(filtered_read_buffer_->data(),
+ &filtered_data_len);
+
+ if (filter_needs_more_output_space_ && 0 == filtered_data_len) {
+ // filter_needs_more_output_space_ was mistaken... there are no more bytes
+ // and we should have at least tried to fill up the filter's input buffer.
+ // Correct the state, and try again.
+ filter_needs_more_output_space_ = false;
+ return ReadFilteredData(bytes_read);
+ }
+
+ switch (status) {
+ case Filter::FILTER_DONE: {
+ filter_needs_more_output_space_ = false;
+ *bytes_read = filtered_data_len;
+ postfilter_bytes_read_ += filtered_data_len;
+ rv = true;
+ break;
+ }
+ case Filter::FILTER_NEED_MORE_DATA: {
+ filter_needs_more_output_space_ =
+ (filtered_data_len == output_buffer_size);
+ // We have finished filtering all data currently in the buffer.
+ // There might be some space left in the output buffer. One can
+ // consider reading more data from the stream to feed the filter
+ // and filling up the output buffer. This leads to more complicated
+ // buffer management and data notification mechanisms.
+ // We can revisit this issue if there is a real perf need.
+ if (filtered_data_len > 0) {
+ *bytes_read = filtered_data_len;
+ postfilter_bytes_read_ += filtered_data_len;
+ rv = true;
+ } else {
+ // Read again since we haven't received enough data yet (e.g., we may
+ // not have a complete gzip header yet)
+ rv = ReadFilteredData(bytes_read);
+ }
+ break;
+ }
+ case Filter::FILTER_OK: {
+ filter_needs_more_output_space_ =
+ (filtered_data_len == output_buffer_size);
+ *bytes_read = filtered_data_len;
+ postfilter_bytes_read_ += filtered_data_len;
+ rv = true;
+ break;
+ }
+ case Filter::FILTER_ERROR: {
+ DVLOG(1) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " Filter Error";
+ filter_needs_more_output_space_ = false;
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_CONTENT_DECODING_FAILED));
+ rv = false;
+ break;
+ }
+ default: {
+ NOTREACHED();
+ filter_needs_more_output_space_ = false;
+ rv = false;
+ break;
+ }
+ }
+ DVLOG(2) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " rv = " << rv
+ << " post bytes read = " << filtered_data_len
+ << " pre total = " << prefilter_bytes_read_
+ << " post total = "
+ << postfilter_bytes_read_;
+ // If logging all bytes is enabled, log the filtered bytes read.
+ if (rv && request() && request()->net_log().IsLoggingBytes() &&
+ filtered_data_len > 0) {
+ request()->net_log().AddByteTransferEvent(
+ NetLog::TYPE_URL_REQUEST_JOB_FILTERED_BYTES_READ,
+ filtered_data_len, filtered_read_buffer_->data());
+ }
+ } else {
+ // we are done, or there is no data left.
+ rv = true;
+ }
+
+ if (rv) {
+ // When we successfully finished a read, we no longer need to
+ // save the caller's buffers. Release our reference.
+ filtered_read_buffer_ = NULL;
+ filtered_read_buffer_len_ = 0;
+ }
+ return rv;
+}
+
+const URLRequestStatus URLRequestJob::GetStatus() {
+ if (request_)
+ return request_->status();
+ // If the request is gone, we must be cancelled.
+ return URLRequestStatus(URLRequestStatus::CANCELED,
+ ERR_ABORTED);
+}
+
+void URLRequestJob::SetStatus(const URLRequestStatus &status) {
+ if (request_)
+ request_->set_status(status);
+}
+
+bool URLRequestJob::ReadRawDataForFilter(int* bytes_read) {
+ bool rv = false;
+
+ DCHECK(bytes_read);
+ DCHECK(filter_.get());
+
+ *bytes_read = 0;
+
+ // Get more pre-filtered data if needed.
+ // TODO(mbelshe): is it possible that the filter needs *MORE* data
+ // when there is some data already in the buffer?
+ if (!filter_->stream_data_len() && !is_done()) {
+ IOBuffer* stream_buffer = filter_->stream_buffer();
+ int stream_buffer_size = filter_->stream_buffer_size();
+ rv = ReadRawDataHelper(stream_buffer, stream_buffer_size, bytes_read);
+ }
+ return rv;
+}
+
+bool URLRequestJob::ReadRawDataHelper(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(!request_->status().is_io_pending());
+ DCHECK(raw_read_buffer_ == NULL);
+
+ // Keep a pointer to the read buffer, so we have access to it in the
+ // OnRawReadComplete() callback in the event that the read completes
+ // asynchronously.
+ raw_read_buffer_ = buf;
+ bool rv = ReadRawData(buf, buf_size, bytes_read);
+
+ if (!request_->status().is_io_pending()) {
+ // If |filter_| is NULL, and logging all bytes is enabled, log the raw
+ // bytes read.
+ if (!filter_.get() && request() && request()->net_log().IsLoggingBytes() &&
+ *bytes_read > 0) {
+ request()->net_log().AddByteTransferEvent(
+ NetLog::TYPE_URL_REQUEST_JOB_BYTES_READ,
+ *bytes_read, raw_read_buffer_->data());
+ }
+
+ // If the read completes synchronously, either success or failure,
+ // invoke the OnRawReadComplete callback so we can account for the
+ // completed read.
+ OnRawReadComplete(*bytes_read);
+ }
+ return rv;
+}
+
+void URLRequestJob::FollowRedirect(const GURL& location, int http_status_code) {
+ int rv = request_->Redirect(location, http_status_code);
+ if (rv != OK)
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED, rv));
+}
+
+void URLRequestJob::OnRawReadComplete(int bytes_read) {
+ DCHECK(raw_read_buffer_);
+ if (bytes_read > 0) {
+ RecordBytesRead(bytes_read);
+ }
+ raw_read_buffer_ = NULL;
+}
+
+void URLRequestJob::RecordBytesRead(int bytes_read) {
+ filter_input_byte_count_ += bytes_read;
+ prefilter_bytes_read_ += bytes_read;
+ if (!filter_.get())
+ postfilter_bytes_read_ += bytes_read;
+ DVLOG(2) << __FUNCTION__ << "() "
+ << "\"" << (request_ ? request_->url().spec() : "???") << "\""
+ << " pre bytes read = " << bytes_read
+ << " pre total = " << prefilter_bytes_read_
+ << " post total = " << postfilter_bytes_read_;
+ UpdatePacketReadTimes(); // Facilitate stats recording if it is active.
+ if (network_delegate_)
+ network_delegate_->NotifyRawBytesRead(*request_, bytes_read);
+}
+
+bool URLRequestJob::FilterHasData() {
+ return filter_.get() && filter_->stream_data_len();
+}
+
+void URLRequestJob::UpdatePacketReadTimes() {
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_job.h b/src/net/url_request/url_request_job.h
new file mode 100644
index 0000000..3178285
--- /dev/null
+++ b/src/net/url_request/url_request_job.h
@@ -0,0 +1,388 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_H_
+
+#include <string>
+#include <vector>
+
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/system_monitor/system_monitor.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/filter.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/load_states.h"
+#include "net/base/net_export.h"
+#include "net/base/upload_progress.h"
+#include "net/cookies/canonical_cookie.h"
+
+namespace net {
+
+class AuthChallengeInfo;
+class AuthCredentials;
+class CookieOptions;
+class HttpRequestHeaders;
+class HttpResponseInfo;
+class IOBuffer;
+class NetworkDelegate;
+class SSLCertRequestInfo;
+class SSLInfo;
+class URLRequest;
+class UploadDataStream;
+class URLRequestStatus;
+class X509Certificate;
+
+class NET_EXPORT URLRequestJob : public base::RefCounted<URLRequestJob>,
+ public base::SystemMonitor::PowerObserver {
+ public:
+ explicit URLRequestJob(URLRequest* request,
+ NetworkDelegate* network_delegate);
+
+ // Returns the request that owns this job. THIS POINTER MAY BE NULL if the
+ // request was destroyed.
+ URLRequest* request() const {
+ return request_;
+ }
+
+ // Sets the upload data, most requests have no upload data, so this is a NOP.
+ // Job types supporting upload data will override this.
+ virtual void SetUpload(UploadDataStream* upload_data_stream);
+
+ // Sets extra request headers for Job types that support request headers.
+ virtual void SetExtraRequestHeaders(const HttpRequestHeaders& headers);
+
+ // If any error occurs while starting the Job, NotifyStartError should be
+ // called.
+ // This helps ensure that all errors follow more similar notification code
+ // paths, which should simplify testing.
+ virtual void Start() = 0;
+
+ // This function MUST somehow call NotifyDone/NotifyCanceled or some requests
+ // will get leaked. Certain callers use that message to know when they can
+ // delete their URLRequest object, even when doing a cancel. The default
+ // Kill implementation calls NotifyCanceled, so it is recommended that
+ // subclasses call URLRequestJob::Kill() after doing any additional work.
+ //
+ // The job should endeavor to stop working as soon as is convenient, but must
+ // not send and complete notifications from inside this function. Instead,
+ // complete notifications (including "canceled") should be sent from a
+ // callback run from the message loop.
+ //
+ // The job is not obliged to immediately stop sending data in response to
+ // this call, nor is it obliged to fail with "canceled" unless not all data
+ // was sent as a result. A typical case would be where the job is almost
+ // complete and can succeed before the canceled notification can be
+ // dispatched (from the message loop).
+ //
+ // The job should be prepared to receive multiple calls to kill it, but only
+ // one notification must be issued.
+ virtual void Kill();
+
+ // Called to detach the request from this Job. Results in the Job being
+ // killed off eventually. The job must not use the request pointer any more.
+ void DetachRequest();
+
+ // Called to read post-filtered data from this Job, returning the number of
+ // bytes read, 0 when there is no more data, or -1 if there was an error.
+ // This is just the backend for URLRequest::Read, see that function for
+ // more info.
+ bool Read(IOBuffer* buf, int buf_size, int* bytes_read);
+
+ // Stops further caching of this request, if any. For more info, see
+ // URLRequest::StopCaching().
+ virtual void StopCaching();
+
+ // Called to fetch the current load state for the job.
+ virtual LoadState GetLoadState() const;
+
+ // Called to get the upload progress in bytes.
+ virtual UploadProgress GetUploadProgress() const;
+
+ // Called to fetch the charset for this request. Only makes sense for some
+ // types of requests. Returns true on success. Calling this on a type that
+ // doesn't have a charset will return false.
+ virtual bool GetCharset(std::string* charset);
+
+ // Called to get response info.
+ virtual void GetResponseInfo(HttpResponseInfo* info);
+
+ // Returns the cookie values included in the response, if applicable.
+ // Returns true if applicable.
+ // NOTE: This removes the cookies from the job, so it will only return
+ // useful results once per job.
+ virtual bool GetResponseCookies(std::vector<std::string>* cookies);
+
+ // Called to setup a stream filter for this request. An example of filter is
+ // content encoding/decoding.
+ // Subclasses should return the appropriate Filter, or NULL for no Filter.
+ // This class takes ownership of the returned Filter.
+ //
+ // The default implementation returns NULL.
+ virtual Filter* SetupFilter() const;
+
+ // Called to determine if this response is a redirect. Only makes sense
+ // for some types of requests. This method returns true if the response
+ // is a redirect, and fills in the location param with the URL of the
+ // redirect. The HTTP status code (e.g., 302) is filled into
+ // |*http_status_code| to signify the type of redirect.
+ //
+ // The caller is responsible for following the redirect by setting up an
+ // appropriate replacement Job. Note that the redirected location may be
+ // invalid, the caller should be sure it can handle this.
+ //
+ // The default implementation inspects the response_info_.
+ virtual bool IsRedirectResponse(GURL* location, int* http_status_code);
+
+ // Called to determine if it is okay to redirect this job to the specified
+ // location. This may be used to implement protocol-specific restrictions.
+ // If this function returns false, then the URLRequest will fail
+ // reporting ERR_UNSAFE_REDIRECT.
+ virtual bool IsSafeRedirect(const GURL& location);
+
+ // Called to determine if this response is asking for authentication. Only
+ // makes sense for some types of requests. The caller is responsible for
+ // obtaining the credentials passing them to SetAuth.
+ virtual bool NeedsAuth();
+
+ // Fills the authentication info with the server's response.
+ virtual void GetAuthChallengeInfo(
+ scoped_refptr<AuthChallengeInfo>* auth_info);
+
+ // Resend the request with authentication credentials.
+ virtual void SetAuth(const AuthCredentials& credentials);
+
+ // Display the error page without asking for credentials again.
+ virtual void CancelAuth();
+
+ virtual void ContinueWithCertificate(X509Certificate* client_cert);
+
+ // Continue processing the request ignoring the last error.
+ virtual void ContinueDespiteLastError();
+
+ void FollowDeferredRedirect();
+
+ // Returns true if the Job is done producing response data and has called
+ // NotifyDone on the request.
+ bool is_done() const { return done_; }
+
+ // Get/Set expected content size
+ int64 expected_content_size() const { return expected_content_size_; }
+ void set_expected_content_size(const int64& size) {
+ expected_content_size_ = size;
+ }
+
+ // Whether we have processed the response for that request yet.
+ bool has_response_started() const { return has_handled_response_; }
+
+ // These methods are not applicable to all connections.
+ virtual bool GetMimeType(std::string* mime_type) const;
+ virtual int GetResponseCode() const;
+
+ // Returns the socket address for the connection.
+ // See url_request.h for details.
+ virtual HostPortPair GetSocketAddress() const;
+
+ // base::SystemMonitor::PowerObserver methods:
+ // We invoke URLRequestJob::Kill on suspend (crbug.com/4606).
+ virtual void OnSuspend() OVERRIDE;
+
+ // Called after a NetworkDelegate has been informed that the URLRequest
+ // will be destroyed. This is used to track that no pending callbacks
+ // exist at destruction time of the URLRequestJob, unless they have been
+ // canceled by an explicit NetworkDelegate::NotifyURLRequestDestroyed() call.
+ virtual void NotifyURLRequestDestroyed();
+
+ protected:
+ friend class base::RefCounted<URLRequestJob>;
+ virtual ~URLRequestJob();
+
+ // Notifies the job that a certificate is requested.
+ void NotifyCertificateRequested(SSLCertRequestInfo* cert_request_info);
+
+ // Notifies the job about an SSL certificate error.
+ void NotifySSLCertificateError(const SSLInfo& ssl_info, bool fatal);
+
+ // Delegates to URLRequest::Delegate.
+ bool CanGetCookies(const CookieList& cookie_list) const;
+
+ // Delegates to URLRequest::Delegate.
+ bool CanSetCookie(const std::string& cookie_line,
+ CookieOptions* options) const;
+
+ // Notifies the job that headers have been received.
+ void NotifyHeadersComplete();
+
+ // Notifies the request that the job has completed a Read operation.
+ void NotifyReadComplete(int bytes_read);
+
+ // Notifies the request that a start error has occurred.
+ void NotifyStartError(const URLRequestStatus& status);
+
+ // NotifyDone marks when we are done with a request. It is really
+ // a glorified set_status, but also does internal state checking and
+ // job tracking. It should be called once per request, when the job is
+ // finished doing all IO.
+ void NotifyDone(const URLRequestStatus& status);
+
+ // Some work performed by NotifyDone must be completed on a separate task
+ // so as to avoid re-entering the delegate. This method exists to perform
+ // that work.
+ void CompleteNotifyDone();
+
+ // Used as an asynchronous callback for Kill to notify the URLRequest
+ // that we were canceled.
+ void NotifyCanceled();
+
+ // Notifies the job the request should be restarted.
+ // Should only be called if the job has not started a resposne.
+ void NotifyRestartRequired();
+
+ // Called when the network delegate blocks or unblocks this request when
+ // intercepting certain requests.
+ void SetBlockedOnDelegate();
+ void SetUnblockedOnDelegate();
+
+ // Called to read raw (pre-filtered) data from this Job.
+ // If returning true, data was read from the job. buf will contain
+ // the data, and bytes_read will receive the number of bytes read.
+ // If returning true, and bytes_read is returned as 0, there is no
+ // additional data to be read.
+ // If returning false, an error occurred or an async IO is now pending.
+ // If async IO is pending, the status of the request will be
+ // URLRequestStatus::IO_PENDING, and buf must remain available until the
+ // operation is completed. See comments on URLRequest::Read for more
+ // info.
+ virtual bool ReadRawData(IOBuffer* buf, int buf_size, int *bytes_read);
+
+ // Called to tell the job that a filter has successfully reached the end of
+ // the stream.
+ virtual void DoneReading();
+
+ // Informs the filter that data has been read into its buffer
+ void FilteredDataRead(int bytes_read);
+
+ // Reads filtered data from the request. Returns true if successful,
+ // false otherwise. Note, if there is not enough data received to
+ // return data, this call can issue a new async IO request under
+ // the hood.
+ bool ReadFilteredData(int *bytes_read);
+
+ // Whether the response is being filtered in this job.
+ // Only valid after NotifyHeadersComplete() has been called.
+ bool HasFilter() { return filter_ != NULL; }
+
+ // At or near destruction time, a derived class may request that the filters
+ // be destroyed so that statistics can be gathered while the derived class is
+ // still present to assist in calculations. This is used by URLRequestHttpJob
+ // to get SDCH to emit stats.
+ void DestroyFilters() { filter_.reset(); }
+
+ // The status of the job.
+ const URLRequestStatus GetStatus();
+
+ // Set the status of the job.
+ void SetStatus(const URLRequestStatus& status);
+
+ // The number of bytes read before passing to the filter.
+ int prefilter_bytes_read() const { return prefilter_bytes_read_; }
+
+ // The number of bytes read after passing through the filter.
+ int postfilter_bytes_read() const { return postfilter_bytes_read_; }
+
+ // Total number of bytes read from network (or cache) and typically handed
+ // to filter to process. Used to histogram compression ratios, and error
+ // recovery scenarios in filters.
+ int64 filter_input_byte_count() const { return filter_input_byte_count_; }
+
+ // The request that initiated this job. This value MAY BE NULL if the
+ // request was released by DetachRequest().
+ URLRequest* request_;
+
+ private:
+ // When data filtering is enabled, this function is used to read data
+ // for the filter. Returns true if raw data was read. Returns false if
+ // an error occurred (or we are waiting for IO to complete).
+ bool ReadRawDataForFilter(int *bytes_read);
+
+ // Invokes ReadRawData and records bytes read if the read completes
+ // synchronously.
+ bool ReadRawDataHelper(IOBuffer* buf, int buf_size, int* bytes_read);
+
+ // Called in response to a redirect that was not canceled to follow the
+ // redirect. The current job will be replaced with a new job loading the
+ // given redirect destination.
+ void FollowRedirect(const GURL& location, int http_status_code);
+
+ // Called after every raw read. If |bytes_read| is > 0, this indicates
+ // a successful read of |bytes_read| unfiltered bytes. If |bytes_read|
+ // is 0, this indicates that there is no additional data to read. If
+ // |bytes_read| is < 0, an error occurred and no bytes were read.
+ void OnRawReadComplete(int bytes_read);
+
+ // Updates the profiling info and notifies observers that an additional
+ // |bytes_read| unfiltered bytes have been read for this job.
+ void RecordBytesRead(int bytes_read);
+
+ // Called to query whether there is data available in the filter to be read
+ // out.
+ bool FilterHasData();
+
+ // Subclasses may implement this method to record packet arrival times.
+ // The default implementation does nothing.
+ virtual void UpdatePacketReadTimes();
+
+ // Custom handler for derived classes when the request is detached.
+ virtual void OnDetachRequest() {}
+
+ // Indicates that the job is done producing data, either it has completed
+ // all the data or an error has been encountered. Set exclusively by
+ // NotifyDone so that it is kept in sync with the request.
+ bool done_;
+
+ int prefilter_bytes_read_;
+ int postfilter_bytes_read_;
+ int64 filter_input_byte_count_;
+
+ // The data stream filter which is enabled on demand.
+ scoped_ptr<Filter> filter_;
+
+ // If the filter filled its output buffer, then there is a change that it
+ // still has internal data to emit, and this flag is set.
+ bool filter_needs_more_output_space_;
+
+ // When we filter data, we receive data into the filter buffers. After
+ // processing the filtered data, we return the data in the caller's buffer.
+ // While the async IO is in progress, we save the user buffer here, and
+ // when the IO completes, we fill this in.
+ scoped_refptr<IOBuffer> filtered_read_buffer_;
+ int filtered_read_buffer_len_;
+
+ // We keep a pointer to the read buffer while asynchronous reads are
+ // in progress, so we are able to pass those bytes to job observers.
+ scoped_refptr<IOBuffer> raw_read_buffer_;
+
+ // Used by HandleResponseIfNecessary to track whether we've sent the
+ // OnResponseStarted callback and potentially redirect callbacks as well.
+ bool has_handled_response_;
+
+ // Expected content size
+ int64 expected_content_size_;
+
+ // Set when a redirect is deferred.
+ GURL deferred_redirect_url_;
+ int deferred_redirect_status_code_;
+
+ NetworkDelegate* network_delegate_;
+
+ base::WeakPtrFactory<URLRequestJob> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJob);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_H_
diff --git a/src/net/url_request/url_request_job_factory.cc b/src/net/url_request/url_request_job_factory.cc
new file mode 100644
index 0000000..e07152a
--- /dev/null
+++ b/src/net/url_request/url_request_job_factory.cc
@@ -0,0 +1,22 @@
+// Copyright (c) 2011 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 "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+URLRequestJobFactory::ProtocolHandler::~ProtocolHandler() {}
+
+URLRequestJobFactory::Interceptor::~Interceptor() {}
+
+bool URLRequestJobFactory::Interceptor::WillHandleProtocol(
+ const std::string& protocol) const {
+ return false;
+}
+
+URLRequestJobFactory::URLRequestJobFactory() {}
+
+URLRequestJobFactory::~URLRequestJobFactory() {}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_job_factory.h b/src/net/url_request/url_request_job_factory.h
new file mode 100644
index 0000000..adff7f1
--- /dev/null
+++ b/src/net/url_request/url_request_job_factory.h
@@ -0,0 +1,117 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/threading/non_thread_safe.h"
+#include "net/base/net_export.h"
+
+class GURL;
+
+namespace net {
+
+class NetworkDelegate;
+class URLRequest;
+class URLRequestJob;
+
+class NET_EXPORT URLRequestJobFactory
+ : NON_EXPORTED_BASE(public base::NonThreadSafe) {
+ public:
+ // TODO(shalev): Move this to URLRequestJobFactoryImpl.
+ class NET_EXPORT ProtocolHandler {
+ public:
+ virtual ~ProtocolHandler();
+
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const = 0;
+ };
+
+ // TODO(shalev): Move this to URLRequestJobFactoryImpl.
+ class NET_EXPORT Interceptor {
+ public:
+ virtual ~Interceptor();
+
+ // Called for every request made. Should return a new job to handle the
+ // request if it should be intercepted, or NULL to allow the request to
+ // be handled in the normal manner.
+ virtual URLRequestJob* MaybeIntercept(
+ URLRequest* request, NetworkDelegate* network_delegate) const = 0;
+
+ // Called after having received a redirect response, but prior to the
+ // the request delegate being informed of the redirect. Can return a new
+ // job to replace the existing job if it should be intercepted, or NULL
+ // to allow the normal handling to continue. If a new job is provided,
+ // the delegate never sees the original redirect response, instead the
+ // response produced by the intercept job will be returned.
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ const GURL& location,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const = 0;
+
+ // Called after having received a final response, but prior to the
+ // the request delegate being informed of the response. This is also
+ // called when there is no server response at all to allow interception
+ // on DNS or network errors. Can return a new job to replace the existing
+ // job if it should be intercepted, or NULL to allow the normal handling to
+ // continue. If a new job is provided, the delegate never sees the original
+ // response, instead the response produced by the intercept job will be
+ // returned.
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const = 0;
+
+ // Returns true if this interceptor handles requests for URLs with the
+ // given protocol. Returning false does not imply that this interceptor
+ // can't or won't handle requests with the given protocol.
+ virtual bool WillHandleProtocol(const std::string& protocol) const;
+ };
+
+ URLRequestJobFactory();
+ virtual ~URLRequestJobFactory();
+
+ // TODO(shalev): Remove this from the interface.
+ // Sets the ProtocolHandler for a scheme. Returns true on success, false on
+ // failure (a ProtocolHandler already exists for |scheme|). On success,
+ // URLRequestJobFactory takes ownership of |protocol_handler|.
+ virtual bool SetProtocolHandler(const std::string& scheme,
+ ProtocolHandler* protocol_handler) = 0;
+
+ // TODO(shalev): Remove this from the interface.
+ // Takes ownership of |interceptor|. Adds it to the end of the Interceptor
+ // list.
+ virtual void AddInterceptor(Interceptor* interceptor) = 0;
+
+ // TODO(shalev): Consolidate MaybeCreateJobWithInterceptor and
+ // MaybeCreateJobWithProtocolHandler into a single method.
+ virtual URLRequestJob* MaybeCreateJobWithInterceptor(
+ URLRequest* request, NetworkDelegate* network_delegate) const = 0;
+
+ virtual URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const = 0;
+
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ const GURL& location,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const = 0;
+
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const = 0;
+
+ virtual bool IsHandledProtocol(const std::string& scheme) const = 0;
+
+ virtual bool IsHandledURL(const GURL& url) const = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobFactory);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_H_
diff --git a/src/net/url_request/url_request_job_factory_impl.cc b/src/net/url_request/url_request_job_factory_impl.cc
new file mode 100644
index 0000000..aaeed79
--- /dev/null
+++ b/src/net/url_request/url_request_job_factory_impl.cc
@@ -0,0 +1,130 @@
+// Copyright (c) 2011 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 "net/url_request/url_request_job_factory_impl.h"
+
+#include "base/stl_util.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/load_flags.h"
+#include "net/url_request/url_request_job_manager.h"
+
+namespace net {
+
+URLRequestJobFactoryImpl::URLRequestJobFactoryImpl() {}
+
+URLRequestJobFactoryImpl::~URLRequestJobFactoryImpl() {
+ STLDeleteValues(&protocol_handler_map_);
+ STLDeleteElements(&interceptors_);
+}
+
+bool URLRequestJobFactoryImpl::SetProtocolHandler(
+ const std::string& scheme,
+ ProtocolHandler* protocol_handler) {
+ DCHECK(CalledOnValidThread());
+
+ if (!protocol_handler) {
+ ProtocolHandlerMap::iterator it = protocol_handler_map_.find(scheme);
+ if (it == protocol_handler_map_.end())
+ return false;
+
+ delete it->second;
+ protocol_handler_map_.erase(it);
+ return true;
+ }
+
+ if (ContainsKey(protocol_handler_map_, scheme))
+ return false;
+ protocol_handler_map_[scheme] = protocol_handler;
+ return true;
+}
+
+void URLRequestJobFactoryImpl::AddInterceptor(Interceptor* interceptor) {
+ DCHECK(CalledOnValidThread());
+ CHECK(interceptor);
+
+ interceptors_.push_back(interceptor);
+}
+
+URLRequestJob* URLRequestJobFactoryImpl::MaybeCreateJobWithInterceptor(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ DCHECK(CalledOnValidThread());
+ URLRequestJob* job = NULL;
+
+ if (!(request->load_flags() & LOAD_DISABLE_INTERCEPT)) {
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ job = (*i)->MaybeIntercept(request, network_delegate);
+ if (job)
+ return job;
+ }
+ }
+ return NULL;
+}
+
+URLRequestJob* URLRequestJobFactoryImpl::MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ DCHECK(CalledOnValidThread());
+ ProtocolHandlerMap::const_iterator it = protocol_handler_map_.find(scheme);
+ if (it == protocol_handler_map_.end())
+ return NULL;
+ return it->second->MaybeCreateJob(request, network_delegate);
+}
+
+URLRequestJob* URLRequestJobFactoryImpl::MaybeInterceptRedirect(
+ const GURL& location,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ DCHECK(CalledOnValidThread());
+ URLRequestJob* job = NULL;
+
+ if (!(request->load_flags() & LOAD_DISABLE_INTERCEPT)) {
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ job = (*i)->MaybeInterceptRedirect(location, request, network_delegate);
+ if (job)
+ return job;
+ }
+ }
+ return NULL;
+}
+
+URLRequestJob* URLRequestJobFactoryImpl::MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ DCHECK(CalledOnValidThread());
+ URLRequestJob* job = NULL;
+
+ if (!(request->load_flags() & LOAD_DISABLE_INTERCEPT)) {
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ job = (*i)->MaybeInterceptResponse(request, network_delegate);
+ if (job)
+ return job;
+ }
+ }
+ return NULL;
+}
+
+bool URLRequestJobFactoryImpl::IsHandledProtocol(
+ const std::string& scheme) const {
+ DCHECK(CalledOnValidThread());
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ if ((*i)->WillHandleProtocol(scheme))
+ return true;
+ }
+ return ContainsKey(protocol_handler_map_, scheme) ||
+ URLRequestJobManager::GetInstance()->SupportsScheme(scheme);
+}
+
+bool URLRequestJobFactoryImpl::IsHandledURL(const GURL& url) const {
+ if (!url.is_valid()) {
+ // We handle error cases.
+ return true;
+ }
+ return IsHandledProtocol(url.scheme());
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_job_factory_impl.h b/src/net/url_request/url_request_job_factory_impl.h
new file mode 100644
index 0000000..ff27185
--- /dev/null
+++ b/src/net/url_request/url_request_job_factory_impl.h
@@ -0,0 +1,52 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_IMPL_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_IMPL_H_
+
+#include <map>
+#include <vector>
+#include "base/basictypes.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+class NET_EXPORT URLRequestJobFactoryImpl : public URLRequestJobFactory {
+ public:
+ URLRequestJobFactoryImpl();
+ virtual ~URLRequestJobFactoryImpl();
+
+ // URLRequestJobFactory implementation
+ virtual bool SetProtocolHandler(const std::string& scheme,
+ ProtocolHandler* protocol_handler) OVERRIDE;
+ virtual void AddInterceptor(Interceptor* interceptor) OVERRIDE;
+ virtual URLRequestJob* MaybeCreateJobWithInterceptor(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual URLRequestJob* MaybeCreateJobWithProtocolHandler(
+ const std::string& scheme,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ const GURL& location,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual bool IsHandledProtocol(const std::string& scheme) const OVERRIDE;
+ virtual bool IsHandledURL(const GURL& url) const OVERRIDE;
+
+ private:
+ typedef std::map<std::string, ProtocolHandler*> ProtocolHandlerMap;
+ typedef std::vector<Interceptor*> InterceptorList;
+
+ ProtocolHandlerMap protocol_handler_map_;
+ InterceptorList interceptors_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobFactoryImpl);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_FACTORY_IMPL_H_
diff --git a/src/net/url_request/url_request_job_factory_impl_unittest.cc b/src/net/url_request/url_request_job_factory_impl_unittest.cc
new file mode 100644
index 0000000..232b21a
--- /dev/null
+++ b/src/net/url_request/url_request_job_factory_impl_unittest.cc
@@ -0,0 +1,218 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_job_factory_impl.h"
+
+#include "base/bind.h"
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+class MockURLRequestJob : public URLRequestJob {
+ public:
+ MockURLRequestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const URLRequestStatus& status)
+ : URLRequestJob(request, network_delegate),
+ status_(status),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {}
+
+ virtual void Start() OVERRIDE {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&MockURLRequestJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+ }
+
+ protected:
+ virtual ~MockURLRequestJob() {}
+
+ private:
+ void StartAsync() {
+ SetStatus(status_);
+ NotifyHeadersComplete();
+ }
+
+ URLRequestStatus status_;
+ base::WeakPtrFactory<MockURLRequestJob> weak_factory_;
+};
+
+class DummyProtocolHandler : public URLRequestJobFactory::ProtocolHandler {
+ public:
+ virtual URLRequestJob* MaybeCreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE {
+ return new MockURLRequestJob(
+ request,
+ network_delegate,
+ URLRequestStatus(URLRequestStatus::SUCCESS, OK));
+ }
+};
+
+class DummyInterceptor : public URLRequestJobFactory::Interceptor {
+ public:
+ DummyInterceptor()
+ : did_intercept_(false),
+ handle_all_protocols_(false) {
+ }
+
+ virtual URLRequestJob* MaybeIntercept(
+ URLRequest* request, NetworkDelegate* network_delegate) const OVERRIDE {
+ did_intercept_ = true;
+ return new MockURLRequestJob(
+ request,
+ network_delegate,
+ URLRequestStatus(URLRequestStatus::FAILED, ERR_FAILED));
+ }
+
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ const GURL& /* location */,
+ URLRequest* /* request */,
+ NetworkDelegate* network_delegate /* network delegate */) const OVERRIDE {
+ return NULL;
+ }
+
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* /* request */,
+ NetworkDelegate* network_delegate /* network delegate */) const OVERRIDE {
+ return NULL;
+ }
+
+ virtual bool WillHandleProtocol(
+ const std::string& /* protocol */) const OVERRIDE {
+ return handle_all_protocols_;
+ }
+
+ mutable bool did_intercept_;
+ mutable bool handle_all_protocols_;
+};
+
+TEST(URLRequestJobFactoryTest, NoProtocolHandler) {
+ TestDelegate delegate;
+ TestURLRequestContext request_context;
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::FAILED, request.status().status());
+ EXPECT_EQ(ERR_UNKNOWN_URL_SCHEME, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, BasicProtocolHandler) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.SetProtocolHandler("foo", new DummyProtocolHandler);
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::SUCCESS, request.status().status());
+ EXPECT_EQ(OK, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, DeleteProtocolHandler) {
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.SetProtocolHandler("foo", new DummyProtocolHandler);
+ job_factory.SetProtocolHandler("foo", NULL);
+}
+
+TEST(URLRequestJobFactoryTest, BasicInterceptor) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.AddInterceptor(new DummyInterceptor);
+ TestURLRequest request(GURL("http://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::FAILED, request.status().status());
+ EXPECT_EQ(ERR_FAILED, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, InterceptorNeedsValidSchemeStill) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.AddInterceptor(new DummyInterceptor);
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::FAILED, request.status().status());
+ EXPECT_EQ(ERR_UNKNOWN_URL_SCHEME, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, InterceptorOverridesProtocolHandler) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ job_factory.SetProtocolHandler("foo", new DummyProtocolHandler);
+ job_factory.AddInterceptor(new DummyInterceptor);
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::FAILED, request.status().status());
+ EXPECT_EQ(ERR_FAILED, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, InterceptorDoesntInterceptUnknownProtocols) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ DummyInterceptor* interceptor = new DummyInterceptor;
+ job_factory.AddInterceptor(interceptor);
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_FALSE(interceptor->did_intercept_);
+}
+
+TEST(URLRequestJobFactoryTest, InterceptorInterceptsHandledUnknownProtocols) {
+ TestDelegate delegate;
+ URLRequestJobFactoryImpl job_factory;
+ TestURLRequestContext request_context;
+ request_context.set_job_factory(&job_factory);
+ DummyInterceptor* interceptor = new DummyInterceptor;
+ interceptor->handle_all_protocols_ = true;
+ job_factory.AddInterceptor(interceptor);
+ TestURLRequest request(GURL("foo://bar"), &delegate, &request_context);
+ request.Start();
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(interceptor->did_intercept_);
+ EXPECT_EQ(URLRequestStatus::FAILED, request.status().status());
+ EXPECT_EQ(ERR_FAILED, request.status().error());
+}
+
+TEST(URLRequestJobFactoryTest, InterceptorAffectsIsHandledProtocol) {
+ DummyInterceptor* interceptor = new DummyInterceptor;
+ URLRequestJobFactoryImpl job_factory;
+ job_factory.AddInterceptor(interceptor);
+ EXPECT_FALSE(interceptor->WillHandleProtocol("anything"));
+ EXPECT_FALSE(job_factory.IsHandledProtocol("anything"));
+ interceptor->handle_all_protocols_ = true;
+ EXPECT_TRUE(interceptor->WillHandleProtocol("anything"));
+ EXPECT_TRUE(job_factory.IsHandledProtocol("anything"));
+}
+
+} // namespace
+
+} // namespace net
diff --git a/src/net/url_request/url_request_job_manager.cc b/src/net/url_request/url_request_job_manager.cc
new file mode 100644
index 0000000..a03e77c
--- /dev/null
+++ b/src/net/url_request/url_request_job_manager.cc
@@ -0,0 +1,290 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_job_manager.h"
+
+#include <algorithm>
+
+#include "base/memory/singleton.h"
+#include "build/build_config.h"
+#include "base/string_util.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/url_request/url_request_about_job.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_data_job.h"
+#include "net/url_request/url_request_error_job.h"
+#include "net/url_request/url_request_file_job.h"
+#include "net/url_request/url_request_ftp_job.h"
+#include "net/url_request/url_request_http_job.h"
+#include "net/url_request/url_request_job_factory.h"
+
+namespace net {
+
+// The built-in set of protocol factories
+namespace {
+
+struct SchemeToFactory {
+ const char* scheme;
+ URLRequest::ProtocolFactory* factory;
+};
+
+} // namespace
+
+#if defined(COBALT)
+static const SchemeToFactory kBuiltinFactories[] = {
+ {"https", URLRequestHttpJob::Factory},
+ {"data", URLRequestDataJob::Factory},
+#if !defined(COBALT_FORCE_HTTPS)
+ { "http", URLRequestHttpJob::Factory },
+#endif
+#if defined(COBALT_ENABLE_FILE_SCHEME)
+ { "file", URLRequestFileJob::Factory },
+#endif
+};
+#else
+static const SchemeToFactory kBuiltinFactories[] = {
+ { "http", URLRequestHttpJob::Factory },
+ { "https", URLRequestHttpJob::Factory },
+#if !defined(DISABLE_FTP_SUPPORT)
+ { "ftp", URLRequestFtpJob::Factory },
+#endif
+ { "file", URLRequestFileJob::Factory },
+ { "about", URLRequestAboutJob::Factory },
+ { "data", URLRequestDataJob::Factory },
+};
+#endif // defined(COBALT)
+
+// static
+URLRequestJobManager* URLRequestJobManager::GetInstance() {
+ return Singleton<URLRequestJobManager>::get();
+}
+
+URLRequestJob* URLRequestJobManager::CreateJob(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ DCHECK(IsAllowedThread());
+
+ // If we are given an invalid URL, then don't even try to inspect the scheme.
+ if (!request->url().is_valid())
+ return new URLRequestErrorJob(request, network_delegate, ERR_INVALID_URL);
+
+ // We do this here to avoid asking interceptors about unsupported schemes.
+ const URLRequestJobFactory* job_factory = NULL;
+ job_factory = request->context()->job_factory();
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+ if (job_factory) {
+ if (!job_factory->IsHandledProtocol(scheme)) {
+ return new URLRequestErrorJob(
+ request, network_delegate, ERR_UNKNOWN_URL_SCHEME);
+ }
+ } else if (!SupportsScheme(scheme)) {
+ return new URLRequestErrorJob(
+ request, network_delegate, ERR_UNKNOWN_URL_SCHEME);
+ }
+
+ // THREAD-SAFETY NOTICE:
+ // We do not need to acquire the lock here since we are only reading our
+ // data structures. They should only be modified on the current thread.
+
+ // See if the request should be intercepted.
+ //
+
+ if (job_factory) {
+ URLRequestJob* job = job_factory->MaybeCreateJobWithInterceptor(
+ request, network_delegate);
+ if (job)
+ return job;
+ }
+
+ // TODO(willchan): Remove this in favor of URLRequestJobFactory::Interceptor.
+ if (!(request->load_flags() & LOAD_DISABLE_INTERCEPT)) {
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ URLRequestJob* job = (*i)->MaybeIntercept(request, network_delegate);
+ if (job)
+ return job;
+ }
+ }
+
+ if (job_factory) {
+ URLRequestJob* job = job_factory->MaybeCreateJobWithProtocolHandler(
+ scheme, request, network_delegate);
+ if (job)
+ return job;
+ }
+
+ // TODO(willchan): Remove this in favor of
+ // URLRequestJobFactory::ProtocolHandler.
+ // See if the request should be handled by a registered protocol factory.
+ // If the registered factory returns null, then we want to fall-back to the
+ // built-in protocol factory.
+ FactoryMap::const_iterator i = factories_.find(scheme);
+ if (i != factories_.end()) {
+ URLRequestJob* job = i->second(request, network_delegate, scheme);
+ if (job)
+ return job;
+ }
+
+ // See if the request should be handled by a built-in protocol factory.
+ for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i) {
+ if (scheme == kBuiltinFactories[i].scheme) {
+ URLRequestJob* job = (kBuiltinFactories[i].factory)(
+ request, network_delegate, scheme);
+ DCHECK(job); // The built-in factories are not expected to fail!
+ return job;
+ }
+ }
+
+ // If we reached here, then it means that a registered protocol factory
+ // wasn't interested in handling the URL. That is fairly unexpected, and we
+ // don't have a specific error to report here :-(
+ LOG(WARNING) << "Failed to map: " << request->url().spec();
+ return new URLRequestErrorJob(request, network_delegate, ERR_FAILED);
+}
+
+URLRequestJob* URLRequestJobManager::MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) const {
+ DCHECK(IsAllowedThread());
+ if (!request->url().is_valid() ||
+ request->load_flags() & LOAD_DISABLE_INTERCEPT ||
+ request->status().status() == URLRequestStatus::CANCELED) {
+ return NULL;
+ }
+
+ const URLRequestJobFactory* job_factory = NULL;
+ job_factory = request->context()->job_factory();
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+ if (job_factory) {
+ if (!job_factory->IsHandledProtocol(scheme)) {
+ return NULL;
+ }
+ } else if (!SupportsScheme(scheme)) {
+ return NULL;
+ }
+
+ URLRequestJob* job = NULL;
+ if (job_factory)
+ job = job_factory->MaybeInterceptRedirect(
+ location, request, network_delegate);
+ if (job)
+ return job;
+
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ job = (*i)->MaybeInterceptRedirect(request, network_delegate, location);
+ if (job)
+ return job;
+ }
+ return NULL;
+}
+
+URLRequestJob* URLRequestJobManager::MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const {
+ DCHECK(IsAllowedThread());
+ if (!request->url().is_valid() ||
+ request->load_flags() & LOAD_DISABLE_INTERCEPT ||
+ request->status().status() == URLRequestStatus::CANCELED) {
+ return NULL;
+ }
+
+ const URLRequestJobFactory* job_factory = NULL;
+ job_factory = request->context()->job_factory();
+
+ const std::string& scheme = request->url().scheme(); // already lowercase
+ if (job_factory) {
+ if (!job_factory->IsHandledProtocol(scheme)) {
+ return NULL;
+ }
+ } else if (!SupportsScheme(scheme)) {
+ return NULL;
+ }
+
+ URLRequestJob* job = NULL;
+ if (job_factory)
+ job = job_factory->MaybeInterceptResponse(request, network_delegate);
+ if (job)
+ return job;
+
+ InterceptorList::const_iterator i;
+ for (i = interceptors_.begin(); i != interceptors_.end(); ++i) {
+ job = (*i)->MaybeInterceptResponse(request, network_delegate);
+ if (job)
+ return job;
+ }
+ return NULL;
+}
+
+bool URLRequestJobManager::SupportsScheme(const std::string& scheme) const {
+ // The set of registered factories may change on another thread.
+ {
+ base::AutoLock locked(lock_);
+ if (factories_.find(scheme) != factories_.end())
+ return true;
+ }
+
+ for (size_t i = 0; i < arraysize(kBuiltinFactories); ++i)
+ if (LowerCaseEqualsASCII(scheme, kBuiltinFactories[i].scheme))
+ return true;
+
+ return false;
+}
+
+URLRequest::ProtocolFactory* URLRequestJobManager::RegisterProtocolFactory(
+ const std::string& scheme,
+ URLRequest::ProtocolFactory* factory) {
+ DCHECK(IsAllowedThread());
+
+ base::AutoLock locked(lock_);
+
+ URLRequest::ProtocolFactory* old_factory;
+ FactoryMap::iterator i = factories_.find(scheme);
+ if (i != factories_.end()) {
+ old_factory = i->second;
+ } else {
+ old_factory = NULL;
+ }
+ if (factory) {
+ factories_[scheme] = factory;
+ } else if (i != factories_.end()) { // uninstall any old one
+ factories_.erase(i);
+ }
+ return old_factory;
+}
+
+void URLRequestJobManager::RegisterRequestInterceptor(
+ URLRequest::Interceptor* interceptor) {
+ DCHECK(IsAllowedThread());
+
+ base::AutoLock locked(lock_);
+
+ DCHECK(std::find(interceptors_.begin(), interceptors_.end(), interceptor) ==
+ interceptors_.end());
+ interceptors_.push_back(interceptor);
+}
+
+void URLRequestJobManager::UnregisterRequestInterceptor(
+ URLRequest::Interceptor* interceptor) {
+ DCHECK(IsAllowedThread());
+
+ base::AutoLock locked(lock_);
+
+ InterceptorList::iterator i =
+ std::find(interceptors_.begin(), interceptors_.end(), interceptor);
+ DCHECK(i != interceptors_.end());
+ interceptors_.erase(i);
+}
+
+URLRequestJobManager::URLRequestJobManager()
+ : allowed_thread_(0),
+ allowed_thread_initialized_(false) {
+}
+
+URLRequestJobManager::~URLRequestJobManager() {}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_job_manager.h b/src/net/url_request/url_request_job_manager.h
new file mode 100644
index 0000000..ea441be
--- /dev/null
+++ b/src/net/url_request/url_request_job_manager.h
@@ -0,0 +1,116 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H_
+#define NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H_
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/synchronization/lock.h"
+#include "base/threading/platform_thread.h"
+#include "net/url_request/url_request.h"
+
+template <typename T> struct DefaultSingletonTraits;
+
+namespace net {
+
+// This class is responsible for managing the set of protocol factories and
+// request interceptors that determine how an URLRequestJob gets created to
+// handle an URLRequest.
+//
+// MULTI-THREADING NOTICE:
+// URLRequest is designed to have all consumers on a single thread, and
+// so no attempt is made to support ProtocolFactory or Interceptor instances
+// being registered/unregistered or in any way poked on multiple threads.
+// However, we do support checking for supported schemes FROM ANY THREAD
+// (i.e., it is safe to call SupportsScheme on any thread).
+//
+class URLRequestJobManager {
+ public:
+ // Returns the singleton instance.
+ static URLRequestJobManager* GetInstance();
+
+ // Instantiate an URLRequestJob implementation based on the registered
+ // interceptors and protocol factories. This will always succeed in
+ // returning a job unless we are--in the extreme case--out of memory.
+ URLRequestJob* CreateJob(URLRequest* request,
+ NetworkDelegate* network_delegate) const;
+
+ // Allows interceptors to hijack the request after examining the new location
+ // of a redirect. Returns NULL if no interceptor intervenes.
+ URLRequestJob* MaybeInterceptRedirect(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) const;
+
+ // Allows interceptors to hijack the request after examining the response
+ // status and headers. This is also called when there is no server response
+ // at all to allow interception of failed requests due to network errors.
+ // Returns NULL if no interceptor intervenes.
+ URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) const;
+
+ // Returns true if there is a protocol factory registered for the given
+ // scheme. Note: also returns true if there is a built-in handler for the
+ // given scheme.
+ bool SupportsScheme(const std::string& scheme) const;
+
+ // Register a protocol factory associated with the given scheme. The factory
+ // parameter may be null to clear any existing association. Returns the
+ // previously registered protocol factory if any.
+ URLRequest::ProtocolFactory* RegisterProtocolFactory(
+ const std::string& scheme, URLRequest::ProtocolFactory* factory);
+
+ // Register/unregister a request interceptor.
+ void RegisterRequestInterceptor(URLRequest::Interceptor* interceptor);
+ void UnregisterRequestInterceptor(URLRequest::Interceptor* interceptor);
+
+ private:
+ typedef std::map<std::string, URLRequest::ProtocolFactory*> FactoryMap;
+ typedef std::vector<URLRequest::Interceptor*> InterceptorList;
+ friend struct DefaultSingletonTraits<URLRequestJobManager>;
+
+ URLRequestJobManager();
+ ~URLRequestJobManager();
+
+ // The first guy to call this function sets the allowed thread. This way we
+ // avoid needing to define that thread externally. Since we expect all
+ // callers to be on the same thread, we don't worry about threads racing to
+ // set the allowed thread.
+ bool IsAllowedThread() const {
+#if 0
+ if (!allowed_thread_initialized_) {
+ allowed_thread_ = base::PlatformThread::CurrentId();
+ allowed_thread_initialized_ = true;
+ }
+ return allowed_thread_ == base::PlatformThread::CurrentId();
+#else
+ // The previous version of this check used GetCurrentThread on Windows to
+ // get thread handles to compare. Unfortunately, GetCurrentThread returns
+ // a constant pseudo-handle (0xFFFFFFFE), and therefore IsAllowedThread
+ // always returned true. The above code that's turned off is the correct
+ // code, but causes the tree to turn red because some caller isn't
+ // respecting our thread requirements. We're turning off the check for now;
+ // bug http://b/issue?id=1338969 has been filed to fix things and turn the
+ // check back on.
+ return true;
+ }
+
+ // We use this to assert that CreateJob and the registration functions all
+ // run on the same thread.
+ mutable base::PlatformThreadId allowed_thread_;
+ mutable bool allowed_thread_initialized_;
+#endif
+
+ mutable base::Lock lock_;
+ FactoryMap factories_;
+ InterceptorList interceptors_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestJobManager);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_JOB_MANAGER_H_
diff --git a/src/net/url_request/url_request_job_unittest.cc b/src/net/url_request/url_request_job_unittest.cc
new file mode 100644
index 0000000..19c2395
--- /dev/null
+++ b/src/net/url_request/url_request_job_unittest.cc
@@ -0,0 +1,79 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_job.h"
+
+#include "net/http/http_transaction_unittest.h"
+#include "net/url_request/url_request_test_util.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// This is a header that signals the end of the data.
+const char kGzipGata[] = "\x1f\x08b\x08\0\0\0\0\0\0\3\3\0\0\0\0\0\0\0\0";
+
+void GZipServer(const net::HttpRequestInfo* request,
+ std::string* response_status, std::string* response_headers,
+ std::string* response_data) {
+ response_data->assign(kGzipGata, sizeof(kGzipGata));
+}
+
+const MockTransaction kGZip_Transaction = {
+ "http://www.google.com/gzyp",
+ "GET",
+ base::Time(),
+ "",
+ net::LOAD_NORMAL,
+ "HTTP/1.1 200 OK",
+ "Cache-Control: max-age=10000\n"
+ "Content-Encoding: gzip\n"
+ "Content-Length: 30\n", // Intentionally wrong.
+ base::Time(),
+ "",
+ TEST_MODE_NORMAL,
+ &GZipServer,
+ 0
+};
+
+} // namespace
+
+TEST(URLRequestJob, TransactionNotifiedWhenDone) {
+ MockNetworkLayer network_layer;
+ net::TestURLRequestContext context;
+ context.set_http_transaction_factory(&network_layer);
+
+ net::TestDelegate d;
+ net::TestURLRequest req(GURL(kGZip_Transaction.url), &d, &context);
+ AddMockTransaction(&kGZip_Transaction);
+
+ req.set_method("GET");
+ req.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(network_layer.done_reading_called());
+
+ RemoveMockTransaction(&kGZip_Transaction);
+}
+
+TEST(URLRequestJob, SyncTransactionNotifiedWhenDone) {
+ MockNetworkLayer network_layer;
+ net::TestURLRequestContext context;
+ context.set_http_transaction_factory(&network_layer);
+
+ net::TestDelegate d;
+ net::TestURLRequest req(GURL(kGZip_Transaction.url), &d, &context);
+ MockTransaction transaction(kGZip_Transaction);
+ transaction.test_mode = TEST_MODE_SYNC_ALL;
+ AddMockTransaction(&transaction);
+
+ req.set_method("GET");
+ req.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(network_layer.done_reading_called());
+
+ RemoveMockTransaction(&transaction);
+}
diff --git a/src/net/url_request/url_request_netlog_params.cc b/src/net/url_request/url_request_netlog_params.cc
new file mode 100644
index 0000000..dbc5357
--- /dev/null
+++ b/src/net/url_request/url_request_netlog_params.cc
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_netlog_params.h"
+
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "googleurl/src/gurl.h"
+
+namespace net {
+
+Value* NetLogURLRequestStartCallback(const GURL* url,
+ const std::string* method,
+ int load_flags,
+ RequestPriority priority,
+ int64 upload_id,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString("url", url->possibly_invalid_spec());
+ dict->SetString("method", *method);
+ dict->SetInteger("load_flags", load_flags);
+ dict->SetInteger("priority", static_cast<int>(priority));
+ if (upload_id > -1)
+ dict->SetString("upload_id", base::Int64ToString(upload_id));
+ return dict;
+}
+
+bool StartEventLoadFlagsFromEventParams(const Value* event_params,
+ int* load_flags) {
+ const DictionaryValue* dict;
+ if (!event_params->GetAsDictionary(&dict) ||
+ !dict->GetInteger("load_flags", load_flags)) {
+ *load_flags = 0;
+ return false;
+ }
+ return true;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_netlog_params.h b/src/net/url_request/url_request_netlog_params.h
new file mode 100644
index 0000000..f2e0dce
--- /dev/null
+++ b/src/net/url_request/url_request_netlog_params.h
@@ -0,0 +1,40 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
+#define NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
+
+#include <string>
+
+#include "net/base/net_export.h"
+#include "net/base/net_log.h"
+#include "net/base/request_priority.h"
+
+class GURL;
+
+namespace base {
+class Value;
+}
+
+namespace net {
+
+// Returns a Value containing NetLog parameters for starting a URLRequest.
+NET_EXPORT base::Value* NetLogURLRequestStartCallback(
+ const GURL* url,
+ const std::string* method,
+ int load_flags,
+ RequestPriority priority,
+ int64 upload_id,
+ NetLog::LogLevel /* log_level */);
+
+// Attempts to extract the load flags from a Value created by the above
+// function. On success, sets |load_flags| accordingly and returns true.
+// On failure, sets |load_flags| to 0.
+NET_EXPORT bool StartEventLoadFlagsFromEventParams(
+ const base::Value* event_params,
+ int* load_flags);
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_NETLOG_PARAMS_H_
diff --git a/src/net/url_request/url_request_redirect_job.cc b/src/net/url_request/url_request_redirect_job.cc
new file mode 100644
index 0000000..e05377f
--- /dev/null
+++ b/src/net/url_request/url_request_redirect_job.cc
@@ -0,0 +1,42 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_redirect_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+
+namespace net {
+
+URLRequestRedirectJob::URLRequestRedirectJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& redirect_destination,
+ StatusCode http_status_code)
+ : URLRequestJob(request, network_delegate),
+ redirect_destination_(redirect_destination),
+ http_status_code_(http_status_code),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {}
+
+void URLRequestRedirectJob::Start() {
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestRedirectJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool URLRequestRedirectJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ *location = redirect_destination_;
+ *http_status_code = http_status_code_;
+ return true;
+}
+
+URLRequestRedirectJob::~URLRequestRedirectJob() {}
+
+void URLRequestRedirectJob::StartAsync() {
+ NotifyHeadersComplete();
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_redirect_job.h b/src/net/url_request/url_request_redirect_job.h
new file mode 100644
index 0000000..198ee76
--- /dev/null
+++ b/src/net/url_request/url_request_redirect_job.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_REDIRECT_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_REDIRECT_JOB_H_
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job.h"
+
+class GURL;
+
+namespace net {
+
+// A URLRequestJob that will redirect the request to the specified
+// URL. This is useful to restart a request at a different URL based
+// on the result of another job.
+class NET_EXPORT URLRequestRedirectJob : public URLRequestJob {
+ public:
+ // Valid status codes for the redirect job. Other 30x codes are theoretically
+ // valid, but unused so far. Both 302 and 307 are temporary redirects, with
+ // the difference being that 302 converts POSTs to GETs and removes upload
+ // data.
+ enum StatusCode {
+ REDIRECT_302_FOUND = 302,
+ REDIRECT_307_TEMPORARY_REDIRECT = 307,
+ };
+
+ // Constructs a job that redirects to the specified URL.
+ URLRequestRedirectJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& redirect_destination,
+ StatusCode http_status_code);
+
+ virtual void Start() OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+
+ private:
+ virtual ~URLRequestRedirectJob();
+
+ void StartAsync();
+
+ const GURL redirect_destination_;
+ const int http_status_code_;
+
+ base::WeakPtrFactory<URLRequestRedirectJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_REDIRECT_JOB_H_
diff --git a/src/net/url_request/url_request_simple_job.cc b/src/net/url_request/url_request_simple_job.cc
new file mode 100644
index 0000000..fda1012
--- /dev/null
+++ b/src/net/url_request/url_request_simple_job.cc
@@ -0,0 +1,75 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_simple_job.h"
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/message_loop.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/url_request/url_request_status.h"
+
+namespace net {
+
+URLRequestSimpleJob::URLRequestSimpleJob(
+ URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestJob(request, network_delegate),
+ data_offset_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {}
+
+void URLRequestSimpleJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&URLRequestSimpleJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+bool URLRequestSimpleJob::GetMimeType(std::string* mime_type) const {
+ *mime_type = mime_type_;
+ return true;
+}
+
+bool URLRequestSimpleJob::GetCharset(std::string* charset) {
+ *charset = charset_;
+ return true;
+}
+
+URLRequestSimpleJob::~URLRequestSimpleJob() {}
+
+bool URLRequestSimpleJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int* bytes_read) {
+ DCHECK(bytes_read);
+ int remaining = static_cast<int>(data_.size()) - data_offset_;
+ if (buf_size > remaining)
+ buf_size = remaining;
+ memcpy(buf->data(), data_.data() + data_offset_, buf_size);
+ data_offset_ += buf_size;
+ *bytes_read = buf_size;
+ return true;
+}
+
+void URLRequestSimpleJob::StartAsync() {
+ if (!request_)
+ return;
+
+ int result = GetData(&mime_type_, &charset_, &data_,
+ base::Bind(&URLRequestSimpleJob::OnGetDataCompleted,
+ weak_factory_.GetWeakPtr()));
+ if (result != ERR_IO_PENDING)
+ OnGetDataCompleted(result);
+}
+
+void URLRequestSimpleJob::OnGetDataCompleted(int result) {
+ if (result == OK) {
+ // Notify that the headers are complete
+ NotifyHeadersComplete();
+ } else {
+ NotifyStartError(URLRequestStatus(URLRequestStatus::FAILED, result));
+ }
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_simple_job.h b/src/net/url_request/url_request_simple_job.h
new file mode 100644
index 0000000..7577151
--- /dev/null
+++ b/src/net/url_request/url_request_simple_job.h
@@ -0,0 +1,63 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/net_export.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+class URLRequest;
+
+class NET_EXPORT URLRequestSimpleJob : public URLRequestJob {
+ public:
+ URLRequestSimpleJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ virtual void Start() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual bool GetCharset(std::string* charset) OVERRIDE;
+
+ protected:
+ virtual ~URLRequestSimpleJob();
+
+ // Subclasses must override the way response data is determined.
+ // The return value should be:
+ // - OK if data is obtained;
+ // - ERR_IO_PENDING if async processing is needed to finish obtaining data.
+ // This is the only case when |callback| should be called after
+ // completion of the operation. In other situations |callback| should
+ // never be called;
+ // - any other ERR_* code to indicate an error. This code will be used
+ // as the error code in the URLRequestStatus when the URLRequest
+ // is finished.
+ virtual int GetData(std::string* mime_type,
+ std::string* charset,
+ std::string* data,
+ const CompletionCallback& callback) const = 0;
+
+ protected:
+ void StartAsync();
+
+ private:
+ void OnGetDataCompleted(int result);
+
+ std::string mime_type_;
+ std::string charset_;
+ std::string data_;
+ int data_offset_;
+ base::WeakPtrFactory<URLRequestSimpleJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_SIMPLE_JOB_H_
diff --git a/src/net/url_request/url_request_status.h b/src/net/url_request/url_request_status.h
new file mode 100644
index 0000000..521a3d4
--- /dev/null
+++ b/src/net/url_request/url_request_status.h
@@ -0,0 +1,62 @@
+// Copyright (c) 2011 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.
+//
+// This file's dependencies should be kept to a minimum so that it can be
+// included in WebKit code that doesn't rely on much of common.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_STATUS_H_
+#define NET_URL_REQUEST_URL_REQUEST_STATUS_H_
+
+namespace net {
+
+// Represents the result of a URL request. It encodes errors and various
+// types of success.
+class URLRequestStatus {
+ public:
+ enum Status {
+ // Request succeeded, |error_| will be 0.
+ SUCCESS = 0,
+
+ // An IO request is pending, and the caller will be informed when it is
+ // completed.
+ IO_PENDING,
+
+ // Request was cancelled programatically.
+ CANCELED,
+
+ // The request failed for some reason. |error_| may have more information.
+ FAILED,
+ };
+
+ URLRequestStatus() : status_(SUCCESS), error_(0) {}
+ URLRequestStatus(Status s, int e) : status_(s), error_(e) {}
+
+ Status status() const { return status_; }
+ void set_status(Status s) { status_ = s; }
+
+ int error() const { return error_; }
+ void set_error(int e) { error_ = e; }
+
+ // Returns true if the status is success, which makes some calling code more
+ // convenient because this is the most common test.
+ bool is_success() const {
+ return status_ == SUCCESS || status_ == IO_PENDING;
+ }
+
+ // Returns true if the request is waiting for IO.
+ bool is_io_pending() const {
+ return status_ == IO_PENDING;
+ }
+
+ private:
+ // Application level status.
+ Status status_;
+
+ // Error code from the network layer if an error was encountered.
+ int error_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_STATUS_H_
diff --git a/src/net/url_request/url_request_test_job.cc b/src/net/url_request/url_request_test_job.cc
new file mode 100644
index 0000000..05b562b
--- /dev/null
+++ b/src/net/url_request/url_request_test_job.cc
@@ -0,0 +1,303 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_test_job.h"
+
+#include <algorithm>
+#include <list>
+
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/lazy_instance.h"
+#include "base/message_loop.h"
+#include "base/string_util.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/http/http_response_headers.h"
+
+namespace net {
+
+namespace {
+
+typedef std::list<URLRequestTestJob*> URLRequestJobList;
+base::LazyInstance<URLRequestJobList>::Leaky
+ g_pending_jobs = LAZY_INSTANCE_INITIALIZER;
+
+} // namespace
+
+// static getters for known URLs
+GURL URLRequestTestJob::test_url_1() {
+ return GURL("test:url1");
+}
+GURL URLRequestTestJob::test_url_2() {
+ return GURL("test:url2");
+}
+GURL URLRequestTestJob::test_url_3() {
+ return GURL("test:url3");
+}
+GURL URLRequestTestJob::test_url_error() {
+ return GURL("test:error");
+}
+
+// static getters for known URL responses
+std::string URLRequestTestJob::test_data_1() {
+ return std::string("<html><title>Test One</title></html>");
+}
+std::string URLRequestTestJob::test_data_2() {
+ return std::string("<html><title>Test Two Two</title></html>");
+}
+std::string URLRequestTestJob::test_data_3() {
+ return std::string("<html><title>Test Three Three Three</title></html>");
+}
+
+// static getter for simple response headers
+std::string URLRequestTestJob::test_headers() {
+ static const char kHeaders[] =
+ "HTTP/1.1 200 OK\0"
+ "Content-type: text/html\0"
+ "\0";
+ return std::string(kHeaders, arraysize(kHeaders));
+}
+
+// static getter for redirect response headers
+std::string URLRequestTestJob::test_redirect_headers() {
+ static const char kHeaders[] =
+ "HTTP/1.1 302 MOVED\0"
+ "Location: somewhere\0"
+ "\0";
+ return std::string(kHeaders, arraysize(kHeaders));
+}
+
+// static getter for error response headers
+std::string URLRequestTestJob::test_error_headers() {
+ static const char kHeaders[] =
+ "HTTP/1.1 500 BOO HOO\0"
+ "\0";
+ return std::string(kHeaders, arraysize(kHeaders));
+}
+
+// static
+URLRequestJob* URLRequestTestJob::Factory(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& scheme) {
+ return new URLRequestTestJob(request, network_delegate);
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : URLRequestJob(request, network_delegate),
+ auto_advance_(false),
+ stage_(WAITING),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ bool auto_advance)
+ : URLRequestJob(request, network_delegate),
+ auto_advance_(auto_advance),
+ stage_(WAITING),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+URLRequestTestJob::URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance)
+ : URLRequestJob(request, network_delegate),
+ auto_advance_(auto_advance),
+ stage_(WAITING),
+ response_headers_(new HttpResponseHeaders(response_headers)),
+ response_data_(response_data),
+ offset_(0),
+ async_buf_(NULL),
+ async_buf_size_(0),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+URLRequestTestJob::~URLRequestTestJob() {
+ g_pending_jobs.Get().erase(
+ std::remove(
+ g_pending_jobs.Get().begin(), g_pending_jobs.Get().end(), this),
+ g_pending_jobs.Get().end());
+}
+
+bool URLRequestTestJob::GetMimeType(std::string* mime_type) const {
+ DCHECK(mime_type);
+ if (!response_headers_)
+ return false;
+ return response_headers_->GetMimeType(mime_type);
+}
+
+void URLRequestTestJob::Start() {
+ // Start reading asynchronously so that all error reporting and data
+ // callbacks happen as they would for network requests.
+ MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&URLRequestTestJob::StartAsync,
+ weak_factory_.GetWeakPtr()));
+}
+
+void URLRequestTestJob::StartAsync() {
+ if (!response_headers_) {
+ response_headers_ = new HttpResponseHeaders(test_headers());
+ if (request_->url().spec() == test_url_1().spec()) {
+ response_data_ = test_data_1();
+ stage_ = DATA_AVAILABLE; // Simulate a synchronous response for this one.
+ } else if (request_->url().spec() == test_url_2().spec()) {
+ response_data_ = test_data_2();
+ } else if (request_->url().spec() == test_url_3().spec()) {
+ response_data_ = test_data_3();
+ } else {
+ AdvanceJob();
+
+ // unexpected url, return error
+ // FIXME(brettw) we may want to use WININET errors or have some more types
+ // of errors
+ NotifyDone(URLRequestStatus(URLRequestStatus::FAILED,
+ ERR_INVALID_URL));
+ // FIXME(brettw): this should emulate a network error, and not just fail
+ // initiating a connection
+ return;
+ }
+ }
+
+ AdvanceJob();
+
+ this->NotifyHeadersComplete();
+}
+
+bool URLRequestTestJob::ReadRawData(IOBuffer* buf, int buf_size,
+ int *bytes_read) {
+ if (stage_ == WAITING) {
+ async_buf_ = buf;
+ async_buf_size_ = buf_size;
+ SetStatus(URLRequestStatus(URLRequestStatus::IO_PENDING, 0));
+ return false;
+ }
+
+ DCHECK(bytes_read);
+ *bytes_read = 0;
+
+ if (offset_ >= static_cast<int>(response_data_.length())) {
+ return true; // done reading
+ }
+
+ int to_read = buf_size;
+ if (to_read + offset_ > static_cast<int>(response_data_.length()))
+ to_read = static_cast<int>(response_data_.length()) - offset_;
+
+ memcpy(buf->data(), &response_data_.c_str()[offset_], to_read);
+ offset_ += to_read;
+
+ *bytes_read = to_read;
+ return true;
+}
+
+void URLRequestTestJob::GetResponseInfo(HttpResponseInfo* info) {
+ if (response_headers_)
+ info->headers = response_headers_;
+}
+
+int URLRequestTestJob::GetResponseCode() const {
+ if (response_headers_)
+ return response_headers_->response_code();
+ return -1;
+}
+
+bool URLRequestTestJob::IsRedirectResponse(GURL* location,
+ int* http_status_code) {
+ if (!response_headers_)
+ return false;
+
+ std::string value;
+ if (!response_headers_->IsRedirect(&value))
+ return false;
+
+ *location = request_->url().Resolve(value);
+ *http_status_code = response_headers_->response_code();
+ return true;
+}
+
+
+void URLRequestTestJob::Kill() {
+ stage_ = DONE;
+ URLRequestJob::Kill();
+ weak_factory_.InvalidateWeakPtrs();
+ g_pending_jobs.Get().erase(
+ std::remove(
+ g_pending_jobs.Get().begin(), g_pending_jobs.Get().end(), this),
+ g_pending_jobs.Get().end());
+}
+
+void URLRequestTestJob::ProcessNextOperation() {
+ switch (stage_) {
+ case WAITING:
+ // Must call AdvanceJob() prior to NotifyReadComplete() since that may
+ // delete |this|.
+ AdvanceJob();
+ stage_ = DATA_AVAILABLE;
+ // OK if ReadRawData wasn't called yet.
+ if (async_buf_) {
+ int bytes_read;
+ if (!ReadRawData(async_buf_, async_buf_size_, &bytes_read))
+ NOTREACHED() << "This should not return false in DATA_AVAILABLE.";
+ SetStatus(URLRequestStatus()); // clear the io pending flag
+ if (NextReadAsync()) {
+ // Make all future reads return io pending until the next
+ // ProcessNextOperation().
+ stage_ = WAITING;
+ }
+ NotifyReadComplete(bytes_read);
+ }
+ break;
+ case DATA_AVAILABLE:
+ AdvanceJob();
+ stage_ = ALL_DATA; // done sending data
+ break;
+ case ALL_DATA:
+ stage_ = DONE;
+ return;
+ case DONE:
+ return;
+ default:
+ NOTREACHED() << "Invalid stage";
+ return;
+ }
+}
+
+bool URLRequestTestJob::NextReadAsync() {
+ return false;
+}
+
+void URLRequestTestJob::AdvanceJob() {
+ if (auto_advance_) {
+ MessageLoop::current()->PostTask(
+ FROM_HERE, base::Bind(&URLRequestTestJob::ProcessNextOperation,
+ weak_factory_.GetWeakPtr()));
+ return;
+ }
+ g_pending_jobs.Get().push_back(this);
+}
+
+// static
+bool URLRequestTestJob::ProcessOnePendingMessage() {
+ if (g_pending_jobs.Get().empty())
+ return false;
+
+ URLRequestTestJob* next_job(g_pending_jobs.Get().front());
+ g_pending_jobs.Get().pop_front();
+
+ DCHECK(!next_job->auto_advance()); // auto_advance jobs should be in this q
+ next_job->ProcessNextOperation();
+ return true;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_test_job.h b/src/net/url_request/url_request_test_job.h
new file mode 100644
index 0000000..0c46d1b
--- /dev/null
+++ b/src/net/url_request/url_request_test_job.h
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_TEST_JOB_H_
+#define NET_URL_REQUEST_URL_REQUEST_TEST_JOB_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_job.h"
+
+namespace net {
+
+// This job type is designed to help with simple unit tests. To use, you
+// probably want to inherit from it to set up the state you want. Then install
+// it as the protocol handler for the "test" scheme.
+//
+// It will respond to three URLs, which you can retrieve using the test_url*
+// getters, which will in turn respond with the corresponding responses returned
+// by test_data*. Any other URLs that begin with "test:" will return an error,
+// which might also be useful, you can use test_url_error() to retreive a
+// standard one.
+//
+// You can override the known URLs or the response data by overriding Start().
+//
+// Optionally, you can also construct test jobs to return a headers and data
+// provided to the contstructor in response to any request url.
+//
+// When a job is created, it gets put on a queue of pending test jobs. To
+// process jobs on this queue, use ProcessOnePendingMessage, which will process
+// one step of the next job. If the job is incomplete, it will be added to the
+// end of the queue.
+//
+// Optionally, you can also construct test jobs that advance automatically
+// without having to call ProcessOnePendingMessage.
+class NET_EXPORT_PRIVATE URLRequestTestJob : public URLRequestJob {
+ public:
+ // Constructs a job to return one of the canned responses depending on the
+ // request url, with auto advance disabled.
+ URLRequestTestJob(URLRequest* request, NetworkDelegate* network_delegate);
+
+ // Constructs a job to return one of the canned responses depending on the
+ // request url, optionally with auto advance enabled.
+ URLRequestTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate,
+ bool auto_advance);
+
+ // Constructs a job to return the given response regardless of the request
+ // url. The headers should include the HTTP status line and be formatted as
+ // expected by HttpResponseHeaders.
+ URLRequestTestJob(URLRequest* request,
+ net::NetworkDelegate* network_delegate,
+ const std::string& response_headers,
+ const std::string& response_data,
+ bool auto_advance);
+
+ // The three canned URLs this handler will respond to without having been
+ // explicitly initialized with response headers and data.
+ // FIXME(brettw): we should probably also have a redirect one
+ static GURL test_url_1();
+ static GURL test_url_2();
+ static GURL test_url_3();
+ static GURL test_url_error();
+
+ // The data that corresponds to each of the URLs above
+ static std::string test_data_1();
+ static std::string test_data_2();
+ static std::string test_data_3();
+
+ // The headers that correspond to each of the URLs above
+ static std::string test_headers();
+
+ // The headers for a redirect response
+ static std::string test_redirect_headers();
+
+ // The headers for a server error response
+ static std::string test_error_headers();
+
+ // Processes one pending message from the stack, returning true if any
+ // message was processed, or false if there are no more pending request
+ // notifications to send. This is not applicable when using auto_advance.
+ static bool ProcessOnePendingMessage();
+
+ // With auto advance enabled, the job will advance thru the stages without
+ // the caller having to call ProcessOnePendingMessage. Auto advance depends
+ // on having a message loop running. The default is to not auto advance.
+ // Should not be altered after the job has started.
+ bool auto_advance() { return auto_advance_; }
+ void set_auto_advance(bool auto_advance) { auto_advance_ = auto_advance; }
+
+ // Factory method for protocol factory registration if callers don't subclass
+ static URLRequest::ProtocolFactory Factory;
+
+ // Job functions
+ virtual void Start() OVERRIDE;
+ virtual bool ReadRawData(IOBuffer* buf,
+ int buf_size,
+ int *bytes_read) OVERRIDE;
+ virtual void Kill() OVERRIDE;
+ virtual bool GetMimeType(std::string* mime_type) const OVERRIDE;
+ virtual void GetResponseInfo(HttpResponseInfo* info) OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+ virtual bool IsRedirectResponse(GURL* location,
+ int* http_status_code) OVERRIDE;
+
+ protected:
+ // Override to specify whether the next read done from this job will
+ // return IO pending. This controls whether or not the WAITING state will
+ // transition back to WAITING or to DATA_AVAILABLE after an asynchronous
+ // read is processed.
+ virtual bool NextReadAsync();
+
+ // This is what operation we are going to do next when this job is handled.
+ // When the stage is DONE, this job will not be put on the queue.
+ enum Stage { WAITING, DATA_AVAILABLE, ALL_DATA, DONE };
+
+ virtual ~URLRequestTestJob();
+
+ // Call to process the next opeation, usually sending a notification, and
+ // advancing the stage if necessary. THIS MAY DELETE THE OBJECT.
+ void ProcessNextOperation();
+
+ // Call to move the job along to the next operation.
+ void AdvanceJob();
+
+ // Called via InvokeLater to cause callbacks to occur after Start() returns.
+ virtual void StartAsync();
+
+ bool auto_advance_;
+
+ Stage stage_;
+
+ // The headers the job should return, will be set in Start() if not provided
+ // in the explicit ctor.
+ scoped_refptr<HttpResponseHeaders> response_headers_;
+
+ // The data to send, will be set in Start() if not provided in the explicit
+ // ctor.
+ std::string response_data_;
+
+ // current offset within response_data_
+ int offset_;
+
+ // Holds the buffer for an asynchronous ReadRawData call
+ IOBuffer* async_buf_;
+ int async_buf_size_;
+
+ base::WeakPtrFactory<URLRequestTestJob> weak_factory_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_TEST_JOB_H_
diff --git a/src/net/url_request/url_request_test_util.cc b/src/net/url_request/url_request_test_util.cc
new file mode 100644
index 0000000..93c707d
--- /dev/null
+++ b/src/net/url_request/url_request_test_util.cc
@@ -0,0 +1,576 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_test_util.h"
+
+#include "base/compiler_specific.h"
+#include "base/logging.h"
+#include "base/message_loop.h"
+#include "base/threading/thread.h"
+#include "base/threading/worker_pool.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/default_server_bound_cert_store.h"
+#include "net/base/host_port_pair.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/server_bound_cert_service.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_server_properties_impl.h"
+#include "net/url_request/static_http_user_agent_settings.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// These constants put the NetworkDelegate events of TestNetworkDelegate
+// into an order. They are used in conjunction with
+// |TestNetworkDelegate::next_states_| to check that we do not send
+// events in the wrong order.
+const int kStageBeforeURLRequest = 1 << 0;
+const int kStageBeforeSendHeaders = 1 << 1;
+const int kStageSendHeaders = 1 << 2;
+const int kStageHeadersReceived = 1 << 3;
+const int kStageAuthRequired = 1 << 4;
+const int kStageBeforeRedirect = 1 << 5;
+const int kStageResponseStarted = 1 << 6;
+const int kStageCompletedSuccess = 1 << 7;
+const int kStageCompletedError = 1 << 8;
+const int kStageURLRequestDestroyed = 1 << 9;
+const int kStageDestruction = 1 << 10;
+
+} // namespace
+
+TestURLRequestContext::TestURLRequestContext()
+ : initialized_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(context_storage_(this)) {
+ Init();
+}
+
+TestURLRequestContext::TestURLRequestContext(bool delay_initialization)
+ : initialized_(false),
+ ALLOW_THIS_IN_INITIALIZER_LIST(context_storage_(this)) {
+ if (!delay_initialization)
+ Init();
+}
+
+TestURLRequestContext::~TestURLRequestContext() {
+ DCHECK(initialized_);
+}
+
+void TestURLRequestContext::Init() {
+ DCHECK(!initialized_);
+ initialized_ = true;
+
+#if !__LB_ENABLE_NATIVE_HTTP_STACK__
+ if (!host_resolver())
+ context_storage_.set_host_resolver(
+ scoped_ptr<HostResolver>(new MockCachingHostResolver()));
+ if (!proxy_service())
+ context_storage_.set_proxy_service(ProxyService::CreateDirect());
+ if (!cert_verifier())
+ context_storage_.set_cert_verifier(CertVerifier::CreateDefault());
+ if (!ftp_transaction_factory()) {
+#if !defined(DISABLE_FTP_SUPPORT)
+ context_storage_.set_ftp_transaction_factory(
+ new FtpNetworkLayer(host_resolver()));
+#else
+ context_storage_.set_ftp_transaction_factory(NULL);
+#endif // !defined(DISABLE_FTP_SUPPORT)
+ }
+ if (!ssl_config_service())
+ context_storage_.set_ssl_config_service(new SSLConfigServiceDefaults);
+ if (!http_auth_handler_factory()) {
+ context_storage_.set_http_auth_handler_factory(
+ HttpAuthHandlerFactory::CreateDefault(host_resolver()));
+ }
+ if (!http_server_properties()) {
+ context_storage_.set_http_server_properties(
+ new HttpServerPropertiesImpl);
+ }
+ if (!transport_security_state()) {
+ context_storage_.set_transport_security_state(
+ new TransportSecurityState());
+ }
+#endif
+ HttpNetworkSession::Params params;
+ params.host_resolver = host_resolver();
+ params.cert_verifier = cert_verifier();
+ params.proxy_service = proxy_service();
+ params.ssl_config_service = ssl_config_service();
+ params.http_auth_handler_factory = http_auth_handler_factory();
+ params.network_delegate = network_delegate();
+ params.http_server_properties = http_server_properties();
+ params.net_log = net_log();
+
+#if !__LB_ENABLE_NATIVE_HTTP_STACK__
+ if (!http_transaction_factory()) {
+ context_storage_.set_http_transaction_factory(new HttpCache(
+ new HttpNetworkSession(params),
+ HttpCache::DefaultBackend::InMemory(0)));
+ }
+#endif
+ // In-memory cookie store.
+ if (!cookie_store())
+ context_storage_.set_cookie_store(new CookieMonster(NULL, NULL));
+ // In-memory origin bound cert service.
+ if (!server_bound_cert_service()) {
+ context_storage_.set_server_bound_cert_service(
+ new ServerBoundCertService(
+ new DefaultServerBoundCertStore(NULL),
+ base::WorkerPool::GetTaskRunner(true)));
+ }
+ if (!http_user_agent_settings()) {
+ context_storage_.set_http_user_agent_settings(
+ new StaticHttpUserAgentSettings(
+ "en-us,fr", "iso-8859-1,*,utf-8", EmptyString()));
+ }
+ if (!job_factory())
+ context_storage_.set_job_factory(new URLRequestJobFactoryImpl);
+}
+
+TestURLRequest::TestURLRequest(const GURL& url,
+ Delegate* delegate,
+ TestURLRequestContext* context)
+ : URLRequest(url, delegate, context) {
+}
+
+TestURLRequest::~TestURLRequest() {
+}
+
+TestURLRequestContextGetter::TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner)
+ : network_task_runner_(network_task_runner) {
+ DCHECK(network_task_runner_);
+}
+
+TestURLRequestContextGetter::TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner,
+ scoped_ptr<TestURLRequestContext> context)
+ : network_task_runner_(network_task_runner), context_(context.Pass()) {
+ DCHECK(network_task_runner_);
+}
+
+TestURLRequestContextGetter::~TestURLRequestContextGetter() {}
+
+TestURLRequestContext* TestURLRequestContextGetter::GetURLRequestContext() {
+ if (!context_.get())
+ context_.reset(new TestURLRequestContext);
+ return context_.get();
+}
+
+scoped_refptr<base::SingleThreadTaskRunner>
+TestURLRequestContextGetter::GetNetworkTaskRunner() const {
+ return network_task_runner_;
+}
+
+TestDelegate::TestDelegate()
+ : cancel_in_rr_(false),
+ cancel_in_rs_(false),
+ cancel_in_rd_(false),
+ cancel_in_rd_pending_(false),
+ quit_on_complete_(true),
+ quit_on_redirect_(false),
+ allow_certificate_errors_(false),
+ response_started_count_(0),
+ received_bytes_count_(0),
+ received_redirect_count_(0),
+ received_data_before_response_(false),
+ request_failed_(false),
+ have_certificate_errors_(false),
+ certificate_errors_are_fatal_(false),
+ auth_required_(false),
+ buf_(new IOBuffer(kBufferSize)) {
+}
+
+TestDelegate::~TestDelegate() {}
+
+void TestDelegate::OnReceivedRedirect(URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ EXPECT_TRUE(request->is_redirecting());
+ received_redirect_count_++;
+ if (quit_on_redirect_) {
+ *defer_redirect = true;
+ MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ } else if (cancel_in_rr_) {
+ request->Cancel();
+ }
+}
+
+void TestDelegate::OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) {
+ auth_required_ = true;
+ if (!credentials_.Empty()) {
+ request->SetAuth(credentials_);
+ } else {
+ request->CancelAuth();
+ }
+}
+
+void TestDelegate::OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal) {
+ // The caller can control whether it needs all SSL requests to go through,
+ // independent of any possible errors, or whether it wants SSL errors to
+ // cancel the request.
+ have_certificate_errors_ = true;
+ certificate_errors_are_fatal_ = fatal;
+ if (allow_certificate_errors_)
+ request->ContinueDespiteLastError();
+ else
+ request->Cancel();
+}
+
+void TestDelegate::OnResponseStarted(URLRequest* request) {
+ // It doesn't make sense for the request to have IO pending at this point.
+ DCHECK(!request->status().is_io_pending());
+ EXPECT_FALSE(request->is_redirecting());
+
+ response_started_count_++;
+ if (cancel_in_rs_) {
+ request->Cancel();
+ OnResponseCompleted(request);
+ } else if (!request->status().is_success()) {
+ DCHECK(request->status().status() == URLRequestStatus::FAILED ||
+ request->status().status() == URLRequestStatus::CANCELED);
+ request_failed_ = true;
+ OnResponseCompleted(request);
+ } else {
+ // Initiate the first read.
+ int bytes_read = 0;
+ if (request->Read(buf_, kBufferSize, &bytes_read))
+ OnReadCompleted(request, bytes_read);
+ else if (!request->status().is_io_pending())
+ OnResponseCompleted(request);
+ }
+}
+
+void TestDelegate::OnReadCompleted(URLRequest* request, int bytes_read) {
+ // It doesn't make sense for the request to have IO pending at this point.
+ DCHECK(!request->status().is_io_pending());
+
+ if (response_started_count_ == 0)
+ received_data_before_response_ = true;
+
+ if (cancel_in_rd_)
+ request->Cancel();
+
+ if (bytes_read >= 0) {
+ // There is data to read.
+ received_bytes_count_ += bytes_read;
+
+ // consume the data
+ data_received_.append(buf_->data(), bytes_read);
+ }
+
+ // If it was not end of stream, request to read more.
+ if (request->status().is_success() && bytes_read > 0) {
+ bytes_read = 0;
+ while (request->Read(buf_, kBufferSize, &bytes_read)) {
+ if (bytes_read > 0) {
+ data_received_.append(buf_->data(), bytes_read);
+ received_bytes_count_ += bytes_read;
+ } else {
+ break;
+ }
+ }
+ }
+ if (!request->status().is_io_pending())
+ OnResponseCompleted(request);
+ else if (cancel_in_rd_pending_)
+ request->Cancel();
+}
+
+void TestDelegate::OnResponseCompleted(URLRequest* request) {
+ if (quit_on_complete_)
+ MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+}
+
+TestNetworkDelegate::TestNetworkDelegate()
+ : last_error_(0),
+ error_count_(0),
+ created_requests_(0),
+ destroyed_requests_(0),
+ completed_requests_(0),
+ cookie_options_bit_mask_(0),
+ blocked_get_cookies_count_(0),
+ blocked_set_cookie_count_(0),
+ set_cookie_count_(0) {
+}
+
+TestNetworkDelegate::~TestNetworkDelegate() {
+ for (std::map<int, int>::iterator i = next_states_.begin();
+ i != next_states_.end(); ++i) {
+ event_order_[i->first] += "~TestNetworkDelegate\n";
+ EXPECT_TRUE(i->second & kStageDestruction) << event_order_[i->first];
+ }
+}
+
+void TestNetworkDelegate::InitRequestStatesIfNew(int request_id) {
+ if (next_states_.find(request_id) == next_states_.end()) {
+ next_states_[request_id] = kStageBeforeURLRequest;
+ event_order_[request_id] = "";
+ }
+}
+
+int TestNetworkDelegate::OnBeforeURLRequest(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url ) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnBeforeURLRequest\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageBeforeURLRequest) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageBeforeSendHeaders |
+ kStageResponseStarted | // data: URLs do not trigger sending headers
+ kStageBeforeRedirect | // a delegate can trigger a redirection
+ kStageCompletedError | // request canceled by delegate
+ kStageAuthRequired; // Auth can come next for FTP requests
+ created_requests_++;
+ return OK;
+}
+
+int TestNetworkDelegate::OnBeforeSendHeaders(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnBeforeSendHeaders\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageBeforeSendHeaders) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageSendHeaders |
+ kStageCompletedError; // request canceled by delegate
+
+ return OK;
+}
+
+void TestNetworkDelegate::OnSendHeaders(
+ URLRequest* request,
+ const HttpRequestHeaders& headers) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnSendHeaders\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageSendHeaders) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageHeadersReceived |
+ kStageCompletedError;
+}
+
+int TestNetworkDelegate::OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) {
+ int req_id = request->identifier();
+ event_order_[req_id] += "OnHeadersReceived\n";
+ InitRequestStatesIfNew(req_id);
+ EXPECT_TRUE(next_states_[req_id] & kStageHeadersReceived) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageBeforeRedirect |
+ kStageResponseStarted |
+ kStageAuthRequired |
+ kStageCompletedError; // e.g. proxy resolution problem
+
+ // Basic authentication sends a second request from the URLRequestHttpJob
+ // layer before the URLRequest reports that a response has started.
+ next_states_[req_id] |= kStageBeforeSendHeaders;
+
+ return OK;
+}
+
+void TestNetworkDelegate::OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnBeforeRedirect\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageBeforeRedirect) <<
+ event_order_[req_id];
+ next_states_[req_id] =
+ kStageBeforeURLRequest | // HTTP redirects trigger this.
+ kStageBeforeSendHeaders | // Redirects from the network delegate do not
+ // trigger onBeforeURLRequest.
+ kStageCompletedError;
+
+ // A redirect can lead to a file or a data URL. In this case, we do not send
+ // headers.
+ next_states_[req_id] |= kStageResponseStarted;
+}
+
+void TestNetworkDelegate::OnResponseStarted(URLRequest* request) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnResponseStarted\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageResponseStarted) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageCompletedSuccess | kStageCompletedError;
+ if (request->status().status() == URLRequestStatus::FAILED) {
+ error_count_++;
+ last_error_ = request->status().error();
+ }
+}
+
+void TestNetworkDelegate::OnRawBytesRead(const URLRequest& request,
+ int bytes_read) {
+}
+
+void TestNetworkDelegate::OnCompleted(URLRequest* request, bool started) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnCompleted\n";
+ // Expect "Success -> (next_states_ & kStageCompletedSuccess)"
+ // is logically identical to
+ // Expect "!(Success) || (next_states_ & kStageCompletedSuccess)"
+ EXPECT_TRUE(!request->status().is_success() ||
+ (next_states_[req_id] & kStageCompletedSuccess)) <<
+ event_order_[req_id];
+ EXPECT_TRUE(request->status().is_success() ||
+ (next_states_[req_id] & kStageCompletedError)) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageURLRequestDestroyed;
+ completed_requests_++;
+ if (request->status().status() == URLRequestStatus::FAILED) {
+ error_count_++;
+ last_error_ = request->status().error();
+ }
+}
+
+void TestNetworkDelegate::OnURLRequestDestroyed(URLRequest* request) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnURLRequestDestroyed\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageURLRequestDestroyed) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageDestruction;
+ destroyed_requests_++;
+}
+
+void TestNetworkDelegate::OnPACScriptError(int line_number,
+ const string16& error) {
+}
+
+NetworkDelegate::AuthRequiredResponse TestNetworkDelegate::OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) {
+ int req_id = request->identifier();
+ InitRequestStatesIfNew(req_id);
+ event_order_[req_id] += "OnAuthRequired\n";
+ EXPECT_TRUE(next_states_[req_id] & kStageAuthRequired) <<
+ event_order_[req_id];
+ next_states_[req_id] = kStageBeforeSendHeaders |
+ kStageHeadersReceived | // Request canceled by delegate simulates empty
+ // response.
+ kStageResponseStarted | // data: URLs do not trigger sending headers
+ kStageBeforeRedirect | // a delegate can trigger a redirection
+ kStageCompletedError; // request cancelled before callback
+ return NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION;
+}
+
+bool TestNetworkDelegate::OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) {
+ bool allow = true;
+ if (cookie_options_bit_mask_ & NO_GET_COOKIES)
+ allow = false;
+
+ if (!allow) {
+ blocked_get_cookies_count_++;
+ }
+
+ return allow;
+}
+
+bool TestNetworkDelegate::OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) {
+ bool allow = true;
+ if (cookie_options_bit_mask_ & NO_SET_COOKIE)
+ allow = false;
+
+ if (!allow) {
+ blocked_set_cookie_count_++;
+ } else {
+ set_cookie_count_++;
+ }
+
+ return allow;
+}
+
+bool TestNetworkDelegate::OnCanAccessFile(const URLRequest& request,
+ const FilePath& path) const {
+ return true;
+}
+
+bool TestNetworkDelegate::OnCanThrottleRequest(
+ const URLRequest& request) const {
+ return true;
+}
+
+int TestNetworkDelegate::OnBeforeSocketStreamConnect(
+ SocketStream* socket,
+ const CompletionCallback& callback) {
+ return OK;
+}
+
+void TestNetworkDelegate::OnRequestWaitStateChange(
+ const URLRequest& request,
+ RequestWaitState state) {
+}
+
+// static
+std::string ScopedCustomUrlRequestTestHttpHost::value_("127.0.0.1");
+
+ScopedCustomUrlRequestTestHttpHost::ScopedCustomUrlRequestTestHttpHost(
+ const std::string& new_value)
+ : old_value_(value_),
+ new_value_(new_value) {
+ value_ = new_value_;
+}
+
+ScopedCustomUrlRequestTestHttpHost::~ScopedCustomUrlRequestTestHttpHost() {
+ DCHECK_EQ(value_, new_value_);
+ value_ = old_value_;
+}
+
+// static
+const std::string& ScopedCustomUrlRequestTestHttpHost::value() {
+ return value_;
+}
+
+TestJobInterceptor::TestJobInterceptor() : main_intercept_job_(NULL) {
+}
+
+URLRequestJob* TestJobInterceptor::MaybeIntercept(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ URLRequestJob* job = main_intercept_job_;
+ main_intercept_job_ = NULL;
+ return job;
+}
+
+URLRequestJob* TestJobInterceptor::MaybeInterceptRedirect(
+ const GURL& location,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ return NULL;
+}
+
+URLRequestJob* TestJobInterceptor::MaybeInterceptResponse(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const {
+ return NULL;
+}
+
+void TestJobInterceptor::set_main_intercept_job(URLRequestJob* job) {
+ main_intercept_job_ = job;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_test_util.h b/src/net/url_request/url_request_test_util.h
new file mode 100644
index 0000000..323efb2
--- /dev/null
+++ b/src/net/url_request/url_request_test_util.h
@@ -0,0 +1,326 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
+#define NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
+
+#include <stdlib.h>
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "base/message_loop_proxy.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/string16.h"
+#include "base/string_util.h"
+#include "base/time.h"
+#include "base/utf_string_conversions.h"
+#include "googleurl/src/url_util.h"
+#include "net/base/cert_verifier.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/base/network_delegate.h"
+#include "net/base/ssl_config_service_defaults.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_auth_handler_factory.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/proxy/proxy_service.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_context_storage.h"
+#include "net/url_request/url_request_job_factory.h"
+
+using base::TimeDelta;
+
+namespace net {
+
+//-----------------------------------------------------------------------------
+
+class TestURLRequestContext : public URLRequestContext {
+ public:
+ TestURLRequestContext();
+ // Default constructor like TestURLRequestContext() but does not call
+ // Init() in case |delay_initialization| is true. This allows modifying the
+ // URLRequestContext before it is constructed completely. If
+ // |delay_initialization| is true, Init() needs be be called manually.
+ explicit TestURLRequestContext(bool delay_initialization);
+ virtual ~TestURLRequestContext();
+
+ void Init();
+
+ private:
+ bool initialized_;
+
+ protected:
+ URLRequestContextStorage context_storage_;
+};
+
+//-----------------------------------------------------------------------------
+
+// Used to return a dummy context, which lives on the message loop
+// given in the constructor.
+class TestURLRequestContextGetter : public URLRequestContextGetter {
+ public:
+ // |network_task_runner| must not be NULL.
+ explicit TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner);
+
+ // Use to pass a pre-initialized |context|.
+ TestURLRequestContextGetter(
+ const scoped_refptr<base::SingleThreadTaskRunner>& network_task_runner,
+ scoped_ptr<TestURLRequestContext> context);
+
+ // URLRequestContextGetter implementation.
+ virtual TestURLRequestContext* GetURLRequestContext() OVERRIDE;
+ virtual scoped_refptr<base::SingleThreadTaskRunner>
+ GetNetworkTaskRunner() const OVERRIDE;
+
+ protected:
+ virtual ~TestURLRequestContextGetter();
+
+ private:
+ const scoped_refptr<base::SingleThreadTaskRunner> network_task_runner_;
+ scoped_ptr<TestURLRequestContext> context_;
+};
+
+//-----------------------------------------------------------------------------
+
+class TestURLRequest : public URLRequest {
+ public:
+ TestURLRequest(
+ const GURL& url, Delegate* delegate, TestURLRequestContext* context);
+ virtual ~TestURLRequest();
+};
+
+//-----------------------------------------------------------------------------
+
+class TestDelegate : public URLRequest::Delegate {
+ public:
+ TestDelegate();
+ virtual ~TestDelegate();
+
+ void set_cancel_in_received_redirect(bool val) { cancel_in_rr_ = val; }
+ void set_cancel_in_response_started(bool val) { cancel_in_rs_ = val; }
+ void set_cancel_in_received_data(bool val) { cancel_in_rd_ = val; }
+ void set_cancel_in_received_data_pending(bool val) {
+ cancel_in_rd_pending_ = val;
+ }
+ void set_quit_on_complete(bool val) { quit_on_complete_ = val; }
+ void set_quit_on_redirect(bool val) { quit_on_redirect_ = val; }
+ void set_allow_certificate_errors(bool val) {
+ allow_certificate_errors_ = val;
+ }
+ void set_credentials(const AuthCredentials& credentials) {
+ credentials_ = credentials;
+ }
+
+ // query state
+ const std::string& data_received() const { return data_received_; }
+ int bytes_received() const { return static_cast<int>(data_received_.size()); }
+ int response_started_count() const { return response_started_count_; }
+ int received_redirect_count() const { return received_redirect_count_; }
+ bool received_data_before_response() const {
+ return received_data_before_response_;
+ }
+ bool request_failed() const { return request_failed_; }
+ bool have_certificate_errors() const { return have_certificate_errors_; }
+ bool certificate_errors_are_fatal() const {
+ return certificate_errors_are_fatal_;
+ }
+ bool auth_required_called() const { return auth_required_; }
+
+ // URLRequest::Delegate:
+ virtual void OnReceivedRedirect(URLRequest* request, const GURL& new_url,
+ bool* defer_redirect) OVERRIDE;
+ virtual void OnAuthRequired(URLRequest* request,
+ AuthChallengeInfo* auth_info) OVERRIDE;
+ // NOTE: |fatal| causes |certificate_errors_are_fatal_| to be set to true.
+ // (Unit tests use this as a post-condition.) But for policy, this method
+ // consults |allow_certificate_errors_|.
+ virtual void OnSSLCertificateError(URLRequest* request,
+ const SSLInfo& ssl_info,
+ bool fatal) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnReadCompleted(URLRequest* request,
+ int bytes_read) OVERRIDE;
+
+ private:
+ static const int kBufferSize = 4096;
+
+ virtual void OnResponseCompleted(URLRequest* request);
+
+ // options for controlling behavior
+ bool cancel_in_rr_;
+ bool cancel_in_rs_;
+ bool cancel_in_rd_;
+ bool cancel_in_rd_pending_;
+ bool quit_on_complete_;
+ bool quit_on_redirect_;
+ bool allow_certificate_errors_;
+ AuthCredentials credentials_;
+
+ // tracks status of callbacks
+ int response_started_count_;
+ int received_bytes_count_;
+ int received_redirect_count_;
+ bool received_data_before_response_;
+ bool request_failed_;
+ bool have_certificate_errors_;
+ bool certificate_errors_are_fatal_;
+ bool auth_required_;
+ std::string data_received_;
+
+ // our read buffer
+ scoped_refptr<IOBuffer> buf_;
+};
+
+//-----------------------------------------------------------------------------
+
+class TestNetworkDelegate : public NetworkDelegate {
+ public:
+ enum Options {
+ NO_GET_COOKIES = 1 << 0,
+ NO_SET_COOKIE = 1 << 1,
+ };
+
+ TestNetworkDelegate();
+ virtual ~TestNetworkDelegate();
+
+ void set_cookie_options(int o) {cookie_options_bit_mask_ = o; }
+
+ int last_error() const { return last_error_; }
+ int error_count() const { return error_count_; }
+ int created_requests() const { return created_requests_; }
+ int destroyed_requests() const { return destroyed_requests_; }
+ int completed_requests() const { return completed_requests_; }
+ int blocked_get_cookies_count() const { return blocked_get_cookies_count_; }
+ int blocked_set_cookie_count() const { return blocked_set_cookie_count_; }
+ int set_cookie_count() const { return set_cookie_count_; }
+
+ protected:
+ // NetworkDelegate:
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE;
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE;
+ virtual void OnSendHeaders(URLRequest* request,
+ const HttpRequestHeaders& headers) OVERRIDE;
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE;
+ virtual void OnBeforeRedirect(URLRequest* request,
+ const GURL& new_location) OVERRIDE;
+ virtual void OnResponseStarted(URLRequest* request) OVERRIDE;
+ virtual void OnRawBytesRead(const URLRequest& request,
+ int bytes_read) OVERRIDE;
+ virtual void OnCompleted(URLRequest* request, bool started) OVERRIDE;
+ virtual void OnURLRequestDestroyed(URLRequest* request) OVERRIDE;
+ virtual void OnPACScriptError(int line_number,
+ const string16& error) OVERRIDE;
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE;
+ virtual bool OnCanGetCookies(const URLRequest& request,
+ const CookieList& cookie_list) OVERRIDE;
+ virtual bool OnCanSetCookie(const URLRequest& request,
+ const std::string& cookie_line,
+ CookieOptions* options) OVERRIDE;
+ virtual bool OnCanAccessFile(const URLRequest& request,
+ const FilePath& path) const OVERRIDE;
+ virtual bool OnCanThrottleRequest(
+ const URLRequest& request) const OVERRIDE;
+ virtual int OnBeforeSocketStreamConnect(
+ SocketStream* stream,
+ const CompletionCallback& callback) OVERRIDE;
+ virtual void OnRequestWaitStateChange(const URLRequest& request,
+ RequestWaitState state) OVERRIDE;
+
+ void InitRequestStatesIfNew(int request_id);
+
+ int last_error_;
+ int error_count_;
+ int created_requests_;
+ int destroyed_requests_;
+ int completed_requests_;
+ int cookie_options_bit_mask_;
+ int blocked_get_cookies_count_;
+ int blocked_set_cookie_count_;
+ int set_cookie_count_;
+
+ // NetworkDelegate callbacks happen in a particular order (e.g.
+ // OnBeforeURLRequest is always called before OnBeforeSendHeaders).
+ // This bit-set indicates for each request id (key) what events may be sent
+ // next.
+ std::map<int, int> next_states_;
+
+ // A log that records for each request id (key) the order in which On...
+ // functions were called.
+ std::map<int, std::string> event_order_;
+};
+
+// Overrides the host used by the LocalHttpTestServer in
+// url_request_unittest.cc . This is used by the chrome_frame_net_tests due to
+// a mysterious bug when tests execute over the loopback adapter. See
+// http://crbug.com/114369 .
+class ScopedCustomUrlRequestTestHttpHost {
+ public:
+ // Sets the host name to be used. The previous hostname will be stored and
+ // restored upon destruction. Note that if the lifetimes of two or more
+ // instances of this class overlap, they must be strictly nested.
+ explicit ScopedCustomUrlRequestTestHttpHost(const std::string& new_value);
+
+ ~ScopedCustomUrlRequestTestHttpHost();
+
+ // Returns the current value to be used by HTTP tests in
+ // url_request_unittest.cc .
+ static const std::string& value();
+
+ private:
+ static std::string value_;
+ const std::string old_value_;
+ const std::string new_value_;
+
+ DISALLOW_COPY_AND_ASSIGN(ScopedCustomUrlRequestTestHttpHost);
+};
+
+//-----------------------------------------------------------------------------
+
+// A simple Interceptor that returns a pre-built URLRequestJob only once.
+class TestJobInterceptor : public URLRequestJobFactory::Interceptor {
+ public:
+ TestJobInterceptor();
+
+ virtual URLRequestJob* MaybeIntercept(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ const GURL& location,
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request,
+ NetworkDelegate* network_delegate) const OVERRIDE;
+ void set_main_intercept_job(URLRequestJob* job);
+
+ private:
+ mutable URLRequestJob* main_intercept_job_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_TEST_UTIL_H_
diff --git a/src/net/url_request/url_request_throttler_entry.cc b/src/net/url_request/url_request_throttler_entry.cc
new file mode 100644
index 0000000..0899ed4
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_entry.cc
@@ -0,0 +1,321 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_throttler_entry.h"
+
+#include <cmath>
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/rand_util.h"
+#include "base/string_number_conversions.h"
+#include "base/values.h"
+#include "net/base/load_flags.h"
+#include "net/base/net_log.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+#include "net/url_request/url_request_throttler_manager.h"
+
+namespace net {
+
+const int URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs = 2000;
+const int URLRequestThrottlerEntry::kDefaultMaxSendThreshold = 20;
+
+// This set of back-off parameters will (at maximum values, i.e. without
+// the reduction caused by jitter) add 0-41% (distributed uniformly
+// in that range) to the "perceived downtime" of the remote server, once
+// exponential back-off kicks in and is throttling requests for more than
+// about a second at a time. Once the maximum back-off is reached, the added
+// perceived downtime decreases rapidly, percentage-wise.
+//
+// Another way to put it is that the maximum additional perceived downtime
+// with these numbers is a couple of seconds shy of 15 minutes, and such
+// a delay would not occur until the remote server has been actually
+// unavailable at the end of each back-off period for a total of about
+// 48 minutes.
+//
+// Ignoring the first couple of errors is just a conservative measure to
+// avoid false positives. It should help avoid back-off from kicking in e.g.
+// on flaky connections.
+const int URLRequestThrottlerEntry::kDefaultNumErrorsToIgnore = 2;
+const int URLRequestThrottlerEntry::kDefaultInitialDelayMs = 700;
+const double URLRequestThrottlerEntry::kDefaultMultiplyFactor = 1.4;
+const double URLRequestThrottlerEntry::kDefaultJitterFactor = 0.4;
+const int URLRequestThrottlerEntry::kDefaultMaximumBackoffMs = 15 * 60 * 1000;
+const int URLRequestThrottlerEntry::kDefaultEntryLifetimeMs = 2 * 60 * 1000;
+const char URLRequestThrottlerEntry::kExponentialThrottlingHeader[] =
+ "X-Chrome-Exponential-Throttling";
+const char URLRequestThrottlerEntry::kExponentialThrottlingDisableValue[] =
+ "disable";
+
+// Returns NetLog parameters when a request is rejected by throttling.
+Value* NetLogRejectedRequestCallback(const std::string* url_id,
+ int num_failures,
+ int release_after_ms,
+ NetLog::LogLevel /* log_level */) {
+ DictionaryValue* dict = new DictionaryValue();
+ dict->SetString("url", *url_id);
+ dict->SetInteger("num_failures", num_failures);
+ dict->SetInteger("release_after_ms", release_after_ms);
+ return dict;
+}
+
+URLRequestThrottlerEntry::URLRequestThrottlerEntry(
+ URLRequestThrottlerManager* manager,
+ const std::string& url_id)
+ : sliding_window_period_(
+ base::TimeDelta::FromMilliseconds(kDefaultSlidingWindowPeriodMs)),
+ max_send_threshold_(kDefaultMaxSendThreshold),
+ is_backoff_disabled_(false),
+ backoff_entry_(&backoff_policy_),
+ manager_(manager),
+ url_id_(url_id),
+ net_log_(BoundNetLog::Make(
+ manager->net_log(), NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING)) {
+ DCHECK(manager_);
+ Initialize();
+}
+
+URLRequestThrottlerEntry::URLRequestThrottlerEntry(
+ URLRequestThrottlerManager* manager,
+ const std::string& url_id,
+ int sliding_window_period_ms,
+ int max_send_threshold,
+ int initial_backoff_ms,
+ double multiply_factor,
+ double jitter_factor,
+ int maximum_backoff_ms)
+ : sliding_window_period_(
+ base::TimeDelta::FromMilliseconds(sliding_window_period_ms)),
+ max_send_threshold_(max_send_threshold),
+ is_backoff_disabled_(false),
+ backoff_entry_(&backoff_policy_),
+ manager_(manager),
+ url_id_(url_id) {
+ DCHECK_GT(sliding_window_period_ms, 0);
+ DCHECK_GT(max_send_threshold_, 0);
+ DCHECK_GE(initial_backoff_ms, 0);
+ DCHECK_GT(multiply_factor, 0);
+ DCHECK_GE(jitter_factor, 0.0);
+ DCHECK_LT(jitter_factor, 1.0);
+ DCHECK_GE(maximum_backoff_ms, 0);
+ DCHECK(manager_);
+
+ Initialize();
+ backoff_policy_.initial_delay_ms = initial_backoff_ms;
+ backoff_policy_.multiply_factor = multiply_factor;
+ backoff_policy_.jitter_factor = jitter_factor;
+ backoff_policy_.maximum_backoff_ms = maximum_backoff_ms;
+ backoff_policy_.entry_lifetime_ms = -1;
+ backoff_policy_.num_errors_to_ignore = 0;
+ backoff_policy_.always_use_initial_delay = false;
+}
+
+bool URLRequestThrottlerEntry::IsEntryOutdated() const {
+ // This function is called by the URLRequestThrottlerManager to determine
+ // whether entries should be discarded from its url_entries_ map. We
+ // want to ensure that it does not remove entries from the map while there
+ // are clients (objects other than the manager) holding references to
+ // the entry, otherwise separate clients could end up holding separate
+ // entries for a request to the same URL, which is undesirable. Therefore,
+ // if an entry has more than one reference (the map will always hold one),
+ // it should not be considered outdated.
+ //
+ // We considered whether to make URLRequestThrottlerEntry objects
+ // non-refcounted, but since any means of knowing whether they are
+ // currently in use by others than the manager would be more or less
+ // equivalent to a refcount, we kept them refcounted.
+ if (!HasOneRef())
+ return false;
+
+ // If there are send events in the sliding window period, we still need this
+ // entry.
+ if (!send_log_.empty() &&
+ send_log_.back() + sliding_window_period_ > ImplGetTimeNow()) {
+ return false;
+ }
+
+ return GetBackoffEntry()->CanDiscard();
+}
+
+void URLRequestThrottlerEntry::DisableBackoffThrottling() {
+ is_backoff_disabled_ = true;
+}
+
+void URLRequestThrottlerEntry::DetachManager() {
+ manager_ = NULL;
+}
+
+bool URLRequestThrottlerEntry::ShouldRejectRequest(
+ const URLRequest& request) const {
+ bool reject_request = false;
+ if (!is_backoff_disabled_ && !ExplicitUserRequest(request.load_flags()) &&
+ (!request.context()->network_delegate() ||
+ request.context()->network_delegate()->CanThrottleRequest(request)) &&
+ GetBackoffEntry()->ShouldRejectRequest()) {
+ int num_failures = GetBackoffEntry()->failure_count();
+ int release_after_ms =
+ GetBackoffEntry()->GetTimeUntilRelease().InMilliseconds();
+
+ net_log_.AddEvent(
+ NetLog::TYPE_THROTTLING_REJECTED_REQUEST,
+ base::Bind(&NetLogRejectedRequestCallback,
+ &url_id_, num_failures, release_after_ms));
+
+ reject_request = true;
+ }
+
+ int reject_count = reject_request ? 1 : 0;
+ UMA_HISTOGRAM_ENUMERATION(
+ "Throttling.RequestThrottled", reject_count, 2);
+
+ return reject_request;
+}
+
+int64 URLRequestThrottlerEntry::ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) {
+ base::TimeTicks now = ImplGetTimeNow();
+
+ // If a lot of requests were successfully made recently,
+ // sliding_window_release_time_ may be greater than
+ // exponential_backoff_release_time_.
+ base::TimeTicks recommended_sending_time =
+ std::max(std::max(now, earliest_time),
+ std::max(GetBackoffEntry()->GetReleaseTime(),
+ sliding_window_release_time_));
+
+ DCHECK(send_log_.empty() ||
+ recommended_sending_time >= send_log_.back());
+ // Log the new send event.
+ send_log_.push(recommended_sending_time);
+
+ sliding_window_release_time_ = recommended_sending_time;
+
+ // Drop the out-of-date events in the event list.
+ // We don't need to worry that the queue may become empty during this
+ // operation, since the last element is sliding_window_release_time_.
+ while ((send_log_.front() + sliding_window_period_ <=
+ sliding_window_release_time_) ||
+ send_log_.size() > static_cast<unsigned>(max_send_threshold_)) {
+ send_log_.pop();
+ }
+
+ // Check if there are too many send events in recent time.
+ if (send_log_.size() == static_cast<unsigned>(max_send_threshold_))
+ sliding_window_release_time_ = send_log_.front() + sliding_window_period_;
+
+ return (recommended_sending_time - now).InMillisecondsRoundedUp();
+}
+
+base::TimeTicks
+ URLRequestThrottlerEntry::GetExponentialBackoffReleaseTime() const {
+ // If a site opts out, it's likely because they have problems that trigger
+ // the back-off mechanism when it shouldn't be triggered, in which case
+ // returning the calculated back-off release time would probably be the
+ // wrong thing to do (i.e. it would likely be too long). Therefore, we
+ // return "now" so that retries are not delayed.
+ if (is_backoff_disabled_)
+ return ImplGetTimeNow();
+
+ return GetBackoffEntry()->GetReleaseTime();
+}
+
+void URLRequestThrottlerEntry::UpdateWithResponse(
+ const std::string& host,
+ const URLRequestThrottlerHeaderInterface* response) {
+ if (IsConsideredError(response->GetResponseCode())) {
+ GetBackoffEntry()->InformOfRequest(false);
+ } else {
+ GetBackoffEntry()->InformOfRequest(true);
+
+ std::string throttling_header = response->GetNormalizedValue(
+ kExponentialThrottlingHeader);
+ if (!throttling_header.empty())
+ HandleThrottlingHeader(throttling_header, host);
+ }
+}
+
+void URLRequestThrottlerEntry::ReceivedContentWasMalformed(int response_code) {
+ // A malformed body can only occur when the request to fetch a resource
+ // was successful. Therefore, in such a situation, we will receive one
+ // call to ReceivedContentWasMalformed() and one call to
+ // UpdateWithResponse() with a response categorized as "good". To end
+ // up counting one failure, we need to count two failures here against
+ // the one success in UpdateWithResponse().
+ //
+ // We do nothing for a response that is already being considered an error
+ // based on its status code (otherwise we would count 3 errors instead of 1).
+ if (!IsConsideredError(response_code)) {
+ GetBackoffEntry()->InformOfRequest(false);
+ GetBackoffEntry()->InformOfRequest(false);
+ }
+}
+
+URLRequestThrottlerEntry::~URLRequestThrottlerEntry() {
+}
+
+void URLRequestThrottlerEntry::Initialize() {
+ sliding_window_release_time_ = base::TimeTicks::Now();
+ backoff_policy_.num_errors_to_ignore = kDefaultNumErrorsToIgnore;
+ backoff_policy_.initial_delay_ms = kDefaultInitialDelayMs;
+ backoff_policy_.multiply_factor = kDefaultMultiplyFactor;
+ backoff_policy_.jitter_factor = kDefaultJitterFactor;
+ backoff_policy_.maximum_backoff_ms = kDefaultMaximumBackoffMs;
+ backoff_policy_.entry_lifetime_ms = kDefaultEntryLifetimeMs;
+ backoff_policy_.always_use_initial_delay = false;
+}
+
+bool URLRequestThrottlerEntry::IsConsideredError(int response_code) {
+ // We throttle only for the status codes most likely to indicate the server
+ // is failing because it is too busy or otherwise are likely to be
+ // because of DDoS.
+ //
+ // 500 is the generic error when no better message is suitable, and
+ // as such does not necessarily indicate a temporary state, but
+ // other status codes cover most of the permanent error states.
+ // 503 is explicitly documented as a temporary state where the server
+ // is either overloaded or down for maintenance.
+ // 509 is the (non-standard but widely implemented) Bandwidth Limit Exceeded
+ // status code, which might indicate DDoS.
+ //
+ // We do not back off on 502 or 504, which are reported by gateways
+ // (proxies) on timeouts or failures, because in many cases these requests
+ // have not made it to the destination server and so we do not actually
+ // know that it is down or busy. One degenerate case could be a proxy on
+ // localhost, where you are not actually connected to the network.
+ return (response_code == 500 ||
+ response_code == 503 ||
+ response_code == 509);
+}
+
+base::TimeTicks URLRequestThrottlerEntry::ImplGetTimeNow() const {
+ return base::TimeTicks::Now();
+}
+
+void URLRequestThrottlerEntry::HandleThrottlingHeader(
+ const std::string& header_value,
+ const std::string& host) {
+ if (header_value == kExponentialThrottlingDisableValue) {
+ DisableBackoffThrottling();
+ if (manager_)
+ manager_->AddToOptOutList(host);
+ }
+}
+
+const BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() const {
+ return &backoff_entry_;
+}
+
+BackoffEntry* URLRequestThrottlerEntry::GetBackoffEntry() {
+ return &backoff_entry_;
+}
+
+// static
+bool URLRequestThrottlerEntry::ExplicitUserRequest(const int load_flags) {
+ return (load_flags & LOAD_MAYBE_USER_GESTURE) != 0;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_throttler_entry.h b/src/net/url_request/url_request_throttler_entry.h
new file mode 100644
index 0000000..66a8ce9
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_entry.h
@@ -0,0 +1,175 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
+
+#include <queue>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/base/net_log.h"
+#include "net/url_request/url_request_throttler_entry_interface.h"
+
+namespace net {
+
+class URLRequestThrottlerManager;
+
+// URLRequestThrottlerEntry represents an entry of URLRequestThrottlerManager.
+// It analyzes requests of a specific URL over some period of time, in order to
+// deduce the back-off time for every request.
+// The back-off algorithm consists of two parts. Firstly, exponential back-off
+// is used when receiving 5XX server errors or malformed response bodies.
+// The exponential back-off rule is enforced by URLRequestHttpJob. Any
+// request sent during the back-off period will be cancelled.
+// Secondly, a sliding window is used to count recent requests to a given
+// destination and provide guidance (to the application level only) on whether
+// too many requests have been sent and when a good time to send the next one
+// would be. This is never used to deny requests at the network level.
+class NET_EXPORT URLRequestThrottlerEntry
+ : public URLRequestThrottlerEntryInterface {
+ public:
+ // Sliding window period.
+ static const int kDefaultSlidingWindowPeriodMs;
+
+ // Maximum number of requests allowed in sliding window period.
+ static const int kDefaultMaxSendThreshold;
+
+ // Number of initial errors to ignore before starting exponential back-off.
+ static const int kDefaultNumErrorsToIgnore;
+
+ // Initial delay for exponential back-off.
+ static const int kDefaultInitialDelayMs;
+
+ // Factor by which the waiting time will be multiplied.
+ static const double kDefaultMultiplyFactor;
+
+ // Fuzzing percentage. ex: 10% will spread requests randomly
+ // between 90%-100% of the calculated time.
+ static const double kDefaultJitterFactor;
+
+ // Maximum amount of time we are willing to delay our request.
+ static const int kDefaultMaximumBackoffMs;
+
+ // Time after which the entry is considered outdated.
+ static const int kDefaultEntryLifetimeMs;
+
+ // Name of the header that sites can use to opt out of exponential back-off
+ // throttling.
+ static const char kExponentialThrottlingHeader[];
+
+ // Value for exponential throttling header that can be used to opt out of
+ // exponential back-off throttling.
+ static const char kExponentialThrottlingDisableValue[];
+
+ // The manager object's lifetime must enclose the lifetime of this object.
+ URLRequestThrottlerEntry(URLRequestThrottlerManager* manager,
+ const std::string& url_id);
+
+ // The life span of instances created with this constructor is set to
+ // infinite, and the number of initial errors to ignore is set to 0.
+ // It is only used by unit tests.
+ URLRequestThrottlerEntry(URLRequestThrottlerManager* manager,
+ const std::string& url_id,
+ int sliding_window_period_ms,
+ int max_send_threshold,
+ int initial_backoff_ms,
+ double multiply_factor,
+ double jitter_factor,
+ int maximum_backoff_ms);
+
+ // Used by the manager, returns true if the entry needs to be garbage
+ // collected.
+ bool IsEntryOutdated() const;
+
+ // Causes this entry to never reject requests due to back-off.
+ void DisableBackoffThrottling();
+
+ // Causes this entry to NULL its manager pointer.
+ void DetachManager();
+
+ // Implementation of URLRequestThrottlerEntryInterface.
+ virtual bool ShouldRejectRequest(const URLRequest& request) const OVERRIDE;
+ virtual int64 ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) OVERRIDE;
+ virtual base::TimeTicks GetExponentialBackoffReleaseTime() const OVERRIDE;
+ virtual void UpdateWithResponse(
+ const std::string& host,
+ const URLRequestThrottlerHeaderInterface* response) OVERRIDE;
+ virtual void ReceivedContentWasMalformed(int response_code) OVERRIDE;
+
+ protected:
+ virtual ~URLRequestThrottlerEntry();
+
+ void Initialize();
+
+ // Returns true if the given response code is considered an error for
+ // throttling purposes.
+ bool IsConsideredError(int response_code);
+
+ // Equivalent to TimeTicks::Now(), virtual to be mockable for testing purpose.
+ virtual base::TimeTicks ImplGetTimeNow() const;
+
+ // Used internally to handle the opt-out header.
+ void HandleThrottlingHeader(const std::string& header_value,
+ const std::string& host);
+
+ // Retrieves the back-off entry object we're using. Used to enable a
+ // unit testing seam for dependency injection in tests.
+ virtual const BackoffEntry* GetBackoffEntry() const;
+ virtual BackoffEntry* GetBackoffEntry();
+
+ // Returns true if |load_flags| contains a flag that indicates an
+ // explicit request by the user to load the resource. We never
+ // throttle requests with such load flags.
+ static bool ExplicitUserRequest(const int load_flags);
+
+ // Used by tests.
+ base::TimeTicks sliding_window_release_time() const {
+ return sliding_window_release_time_;
+ }
+
+ // Used by tests.
+ void set_sliding_window_release_time(const base::TimeTicks& release_time) {
+ sliding_window_release_time_ = release_time;
+ }
+
+ // Valid and immutable after construction time.
+ BackoffEntry::Policy backoff_policy_;
+
+ private:
+ // Timestamp calculated by the sliding window algorithm for when we advise
+ // clients the next request should be made, at the earliest. Advisory only,
+ // not used to deny requests.
+ base::TimeTicks sliding_window_release_time_;
+
+ // A list of the recent send events. We use them to decide whether there are
+ // too many requests sent in sliding window.
+ std::queue<base::TimeTicks> send_log_;
+
+ const base::TimeDelta sliding_window_period_;
+ const int max_send_threshold_;
+
+ // True if DisableBackoffThrottling() has been called on this object.
+ bool is_backoff_disabled_;
+
+ // Access it through GetBackoffEntry() to allow a unit test seam.
+ BackoffEntry backoff_entry_;
+
+ // Weak back-reference to the manager object managing us.
+ URLRequestThrottlerManager* manager_;
+
+ // Canonicalized URL string that this entry is for; used for logging only.
+ std::string url_id_;
+
+ BoundNetLog net_log_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerEntry);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_H_
diff --git a/src/net/url_request/url_request_throttler_entry_interface.h b/src/net/url_request/url_request_throttler_entry_interface.h
new file mode 100644
index 0000000..6d975f4
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_entry_interface.h
@@ -0,0 +1,73 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/time.h"
+#include "net/base/net_export.h"
+
+namespace net {
+
+class URLRequest;
+class URLRequestThrottlerHeaderInterface;
+
+// Interface provided on entries of the URL request throttler manager.
+class NET_EXPORT URLRequestThrottlerEntryInterface
+ : public base::RefCountedThreadSafe<URLRequestThrottlerEntryInterface> {
+ public:
+ URLRequestThrottlerEntryInterface() {}
+
+ // Returns true when we have encountered server errors and are doing
+ // exponential back-off, unless the request has load flags that mean
+ // it is likely to be user-initiated, or the NetworkDelegate returns
+ // false for |CanThrottleRequest(request)|.
+ //
+ // URLRequestHttpJob checks this method prior to every request; it
+ // cancels requests if this method returns true.
+ virtual bool ShouldRejectRequest(const URLRequest& request) const = 0;
+
+ // Calculates a recommended sending time for the next request and reserves it.
+ // The sending time is not earlier than the current exponential back-off
+ // release time or |earliest_time|. Moreover, the previous results of
+ // the method are taken into account, in order to make sure they are spread
+ // properly over time.
+ // Returns the recommended delay before sending the next request, in
+ // milliseconds. The return value is always positive or 0.
+ // Although it is not mandatory, respecting the value returned by this method
+ // is helpful to avoid traffic overload.
+ virtual int64 ReserveSendingTimeForNextRequest(
+ const base::TimeTicks& earliest_time) = 0;
+
+ // Returns the time after which requests are allowed.
+ virtual base::TimeTicks GetExponentialBackoffReleaseTime() const = 0;
+
+ // This method needs to be called each time a response is received.
+ virtual void UpdateWithResponse(
+ const std::string& host,
+ const URLRequestThrottlerHeaderInterface* response) = 0;
+
+ // Lets higher-level modules, that know how to parse particular response
+ // bodies, notify of receiving malformed content for the given URL. This will
+ // be handled by the throttler as if an HTTP 503 response had been received to
+ // the request, i.e. it will count as a failure, unless the HTTP response code
+ // indicated is already one of those that will be counted as an error.
+ virtual void ReceivedContentWasMalformed(int response_code) = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<URLRequestThrottlerEntryInterface>;
+ virtual ~URLRequestThrottlerEntryInterface() {}
+
+ private:
+ friend class base::RefCounted<URLRequestThrottlerEntryInterface>;
+ DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerEntryInterface);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_ENTRY_INTERFACE_H_
diff --git a/src/net/url_request/url_request_throttler_header_adapter.cc b/src/net/url_request/url_request_throttler_header_adapter.cc
new file mode 100644
index 0000000..51bbc74
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_header_adapter.cc
@@ -0,0 +1,29 @@
+// Copyright (c) 2011 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 "net/url_request/url_request_throttler_header_adapter.h"
+
+#include "net/http/http_response_headers.h"
+
+namespace net {
+
+URLRequestThrottlerHeaderAdapter::URLRequestThrottlerHeaderAdapter(
+ HttpResponseHeaders* headers)
+ : response_header_(headers) {
+}
+
+URLRequestThrottlerHeaderAdapter::~URLRequestThrottlerHeaderAdapter() {}
+
+std::string URLRequestThrottlerHeaderAdapter::GetNormalizedValue(
+ const std::string& key) const {
+ std::string return_value;
+ response_header_->GetNormalizedHeader(key, &return_value);
+ return return_value;
+}
+
+int URLRequestThrottlerHeaderAdapter::GetResponseCode() const {
+ return response_header_->response_code();
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_throttler_header_adapter.h b/src/net/url_request/url_request_throttler_header_adapter.h
new file mode 100644
index 0000000..17a13a1
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_header_adapter.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
+
+#include <string>
+
+#include "base/compiler_specific.h"
+#include "base/memory/ref_counted.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+
+namespace net {
+
+class HttpResponseHeaders;
+
+// Adapter for the HTTP header interface of the URL request throttler component.
+class URLRequestThrottlerHeaderAdapter
+ : public URLRequestThrottlerHeaderInterface {
+ public:
+ explicit URLRequestThrottlerHeaderAdapter(HttpResponseHeaders* headers);
+ virtual ~URLRequestThrottlerHeaderAdapter();
+
+ // Implementation of URLRequestThrottlerHeaderInterface
+ virtual std::string GetNormalizedValue(const std::string& key) const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+
+ private:
+ const scoped_refptr<HttpResponseHeaders> response_header_;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_ADAPTER_H_
diff --git a/src/net/url_request/url_request_throttler_header_interface.h b/src/net/url_request/url_request_throttler_header_interface.h
new file mode 100644
index 0000000..c69d185
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_header_interface.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2010 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_INTERFACE_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_INTERFACE_H_
+
+#include <string>
+
+namespace net {
+
+// Interface to an HTTP header to enforce we have the methods we need.
+class URLRequestThrottlerHeaderInterface {
+ public:
+ virtual ~URLRequestThrottlerHeaderInterface() {}
+
+ // Method that enables us to fetch the header value by its key.
+ // ex: location: www.example.com -> key = "location" value = "www.example.com"
+ // If the key does not exist, it returns an empty string.
+ virtual std::string GetNormalizedValue(const std::string& key) const = 0;
+
+ // Returns the HTTP response code associated with the request.
+ virtual int GetResponseCode() const = 0;
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_HEADER_INTERFACE_H_
diff --git a/src/net/url_request/url_request_throttler_manager.cc b/src/net/url_request/url_request_throttler_manager.cc
new file mode 100644
index 0000000..fa04a00
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_manager.cc
@@ -0,0 +1,203 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_throttler_manager.h"
+
+#include "base/logging.h"
+#include "base/metrics/field_trial.h"
+#include "base/metrics/histogram.h"
+#include "base/string_util.h"
+#include "net/base/net_log.h"
+#include "net/base/net_util.h"
+
+namespace net {
+
+const unsigned int URLRequestThrottlerManager::kMaximumNumberOfEntries = 1500;
+const unsigned int URLRequestThrottlerManager::kRequestsBetweenCollecting = 200;
+
+URLRequestThrottlerManager::URLRequestThrottlerManager()
+ : requests_since_last_gc_(0),
+ enable_thread_checks_(false),
+ logged_for_localhost_disabled_(false),
+ registered_from_thread_(base::kInvalidThreadId) {
+ url_id_replacements_.ClearPassword();
+ url_id_replacements_.ClearUsername();
+ url_id_replacements_.ClearQuery();
+ url_id_replacements_.ClearRef();
+
+ NetworkChangeNotifier::AddIPAddressObserver(this);
+ NetworkChangeNotifier::AddConnectionTypeObserver(this);
+}
+
+URLRequestThrottlerManager::~URLRequestThrottlerManager() {
+ NetworkChangeNotifier::RemoveIPAddressObserver(this);
+ NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
+
+ // Since the manager object might conceivably go away before the
+ // entries, detach the entries' back-pointer to the manager.
+ UrlEntryMap::iterator i = url_entries_.begin();
+ while (i != url_entries_.end()) {
+ if (i->second != NULL) {
+ i->second->DetachManager();
+ }
+ ++i;
+ }
+
+ // Delete all entries.
+ url_entries_.clear();
+}
+
+scoped_refptr<URLRequestThrottlerEntryInterface>
+ URLRequestThrottlerManager::RegisterRequestUrl(const GURL &url) {
+ DCHECK(!enable_thread_checks_ || CalledOnValidThread());
+
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+
+ // Periodically garbage collect old entries.
+ GarbageCollectEntriesIfNecessary();
+
+ // Find the entry in the map or create a new NULL entry.
+ scoped_refptr<URLRequestThrottlerEntry>& entry = url_entries_[url_id];
+
+ // If the entry exists but could be garbage collected at this point, we
+ // start with a fresh entry so that we possibly back off a bit less
+ // aggressively (i.e. this resets the error count when the entry's URL
+ // hasn't been requested in long enough).
+ if (entry.get() && entry->IsEntryOutdated()) {
+ entry = NULL;
+ }
+
+ // Create the entry if needed.
+ if (entry.get() == NULL) {
+ entry = new URLRequestThrottlerEntry(this, url_id);
+
+ // We only disable back-off throttling on an entry that we have
+ // just constructed. This is to allow unit tests to explicitly override
+ // the entry for localhost URLs. Given that we do not attempt to
+ // disable throttling for entries already handed out (see comment
+ // in AddToOptOutList), this is not a problem.
+ std::string host = url.host();
+ if (opt_out_hosts_.find(host) != opt_out_hosts_.end() ||
+ IsLocalhost(host)) {
+ if (!logged_for_localhost_disabled_ && IsLocalhost(host)) {
+ logged_for_localhost_disabled_ = true;
+ net_log_.AddEvent(NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST,
+ NetLog::StringCallback("host", &host));
+ }
+
+ // TODO(joi): Once sliding window is separate from back-off throttling,
+ // we can simply return a dummy implementation of
+ // URLRequestThrottlerEntryInterface here that never blocks anything (and
+ // not keep entries in url_entries_ for opted-out sites).
+ entry->DisableBackoffThrottling();
+ }
+ }
+
+ return entry;
+}
+
+void URLRequestThrottlerManager::AddToOptOutList(const std::string& host) {
+ // There is an edge case here that we are not handling, to keep things
+ // simple. If a host starts adding the opt-out header to its responses
+ // after there are already one or more entries in url_entries_ for that
+ // host, the pre-existing entries may still perform back-off throttling.
+ // In practice, this would almost never occur.
+ if (opt_out_hosts_.find(host) == opt_out_hosts_.end()) {
+ UMA_HISTOGRAM_COUNTS("Throttling.SiteOptedOut", 1);
+
+ net_log_.EndEvent(NetLog::TYPE_THROTTLING_DISABLED_FOR_HOST,
+ NetLog::StringCallback("host", &host));
+ opt_out_hosts_.insert(host);
+ }
+}
+
+void URLRequestThrottlerManager::OverrideEntryForTests(
+ const GURL& url,
+ URLRequestThrottlerEntry* entry) {
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+
+ // Periodically garbage collect old entries.
+ GarbageCollectEntriesIfNecessary();
+
+ url_entries_[url_id] = entry;
+}
+
+void URLRequestThrottlerManager::EraseEntryForTests(const GURL& url) {
+ // Normalize the url.
+ std::string url_id = GetIdFromUrl(url);
+ url_entries_.erase(url_id);
+}
+
+void URLRequestThrottlerManager::set_enable_thread_checks(bool enable) {
+ enable_thread_checks_ = enable;
+}
+
+bool URLRequestThrottlerManager::enable_thread_checks() const {
+ return enable_thread_checks_;
+}
+
+void URLRequestThrottlerManager::set_net_log(NetLog* net_log) {
+ DCHECK(net_log);
+ net_log_ = BoundNetLog::Make(net_log,
+ NetLog::SOURCE_EXPONENTIAL_BACKOFF_THROTTLING);
+}
+
+NetLog* URLRequestThrottlerManager::net_log() const {
+ return net_log_.net_log();
+}
+
+void URLRequestThrottlerManager::OnIPAddressChanged() {
+ OnNetworkChange();
+}
+
+void URLRequestThrottlerManager::OnConnectionTypeChanged(
+ NetworkChangeNotifier::ConnectionType type) {
+ OnNetworkChange();
+}
+
+std::string URLRequestThrottlerManager::GetIdFromUrl(const GURL& url) const {
+ if (!url.is_valid())
+ return url.possibly_invalid_spec();
+
+ GURL id = url.ReplaceComponents(url_id_replacements_);
+ return StringToLowerASCII(id.spec()).c_str();
+}
+
+void URLRequestThrottlerManager::GarbageCollectEntriesIfNecessary() {
+ requests_since_last_gc_++;
+ if (requests_since_last_gc_ < kRequestsBetweenCollecting)
+ return;
+ requests_since_last_gc_ = 0;
+
+ GarbageCollectEntries();
+}
+
+void URLRequestThrottlerManager::GarbageCollectEntries() {
+ UrlEntryMap::iterator i = url_entries_.begin();
+ while (i != url_entries_.end()) {
+ if ((i->second)->IsEntryOutdated()) {
+ url_entries_.erase(i++);
+ } else {
+ ++i;
+ }
+ }
+
+ // In case something broke we want to make sure not to grow indefinitely.
+ while (url_entries_.size() > kMaximumNumberOfEntries) {
+ url_entries_.erase(url_entries_.begin());
+ }
+}
+
+void URLRequestThrottlerManager::OnNetworkChange() {
+ // Remove all entries. Any entries that in-flight requests have a reference
+ // to will live until those requests end, and these entries may be
+ // inconsistent with new entries for the same URLs, but since what we
+ // want is a clean slate for the new connection type, this is OK.
+ url_entries_.clear();
+ requests_since_last_gc_ = 0;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_throttler_manager.h b/src/net/url_request/url_request_throttler_manager.h
new file mode 100644
index 0000000..9623834
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_manager.h
@@ -0,0 +1,166 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
+
+#include <map>
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/non_thread_safe.h"
+#include "base/threading/platform_thread.h"
+#include "googleurl/src/gurl.h"
+#include "net/base/net_export.h"
+#include "net/base/network_change_notifier.h"
+#include "net/url_request/url_request_throttler_entry.h"
+
+namespace net {
+
+class BoundNetLog;
+class NetLog;
+
+// Class that registers URL request throttler entries for URLs being accessed
+// in order to supervise traffic. URL requests for HTTP contents should
+// register their URLs in this manager on each request.
+//
+// URLRequestThrottlerManager maintains a map of URL IDs to URL request
+// throttler entries. It creates URL request throttler entries when new URLs
+// are registered, and does garbage collection from time to time in order to
+// clean out outdated entries. URL ID consists of lowercased scheme, host, port
+// and path. All URLs converted to the same ID will share the same entry.
+class NET_EXPORT URLRequestThrottlerManager
+ : NON_EXPORTED_BASE(public base::NonThreadSafe),
+ public NetworkChangeNotifier::IPAddressObserver,
+ public NetworkChangeNotifier::ConnectionTypeObserver {
+ public:
+ URLRequestThrottlerManager();
+ virtual ~URLRequestThrottlerManager();
+
+ // Must be called for every request, returns the URL request throttler entry
+ // associated with the URL. The caller must inform this entry of some events.
+ // Please refer to url_request_throttler_entry_interface.h for further
+ // informations.
+ scoped_refptr<URLRequestThrottlerEntryInterface> RegisterRequestUrl(
+ const GURL& url);
+
+ // Adds the given host to a list of sites for which exponential back-off
+ // throttling will be disabled. Subdomains are not included, so they
+ // must be added separately.
+ void AddToOptOutList(const std::string& host);
+
+ // Registers a new entry in this service and overrides the existing entry (if
+ // any) for the URL. The service will hold a reference to the entry.
+ // It is only used by unit tests.
+ void OverrideEntryForTests(const GURL& url, URLRequestThrottlerEntry* entry);
+
+ // Explicitly erases an entry.
+ // This is useful to remove those entries which have got infinite lifetime and
+ // thus won't be garbage collected.
+ // It is only used by unit tests.
+ void EraseEntryForTests(const GURL& url);
+
+ // Turns threading model verification on or off. Any code that correctly
+ // uses the network stack should preferably call this function to enable
+ // verification of correct adherence to the network stack threading model.
+ void set_enable_thread_checks(bool enable);
+ bool enable_thread_checks() const;
+
+ // Whether throttling is enabled or not.
+ void set_enforce_throttling(bool enforce);
+ bool enforce_throttling();
+
+ // Sets the NetLog instance to use.
+ void set_net_log(NetLog* net_log);
+ NetLog* net_log() const;
+
+ // IPAddressObserver interface.
+ virtual void OnIPAddressChanged() OVERRIDE;
+
+ // ConnectionTypeObserver interface.
+ virtual void OnConnectionTypeChanged(
+ NetworkChangeNotifier::ConnectionType type) OVERRIDE;
+
+ // Method that allows us to transform a URL into an ID that can be used in our
+ // map. Resulting IDs will be lowercase and consist of the scheme, host, port
+ // and path (without query string, fragment, etc.).
+ // If the URL is invalid, the invalid spec will be returned, without any
+ // transformation.
+ std::string GetIdFromUrl(const GURL& url) const;
+
+ // Method that ensures the map gets cleaned from time to time. The period at
+ // which garbage collecting happens is adjustable with the
+ // kRequestBetweenCollecting constant.
+ void GarbageCollectEntriesIfNecessary();
+
+ // Method that does the actual work of garbage collecting.
+ void GarbageCollectEntries();
+
+ // When we switch from online to offline or change IP addresses, we
+ // clear all back-off history. This is a precaution in case the change in
+ // online state now lets us communicate without error with servers that
+ // we were previously getting 500 or 503 responses from (perhaps the
+ // responses are from a badly-written proxy that should have returned a
+ // 502 or 504 because it's upstream connection was down or it had no route
+ // to the server).
+ void OnNetworkChange();
+
+ // Used by tests.
+ int GetNumberOfEntriesForTests() const { return url_entries_.size(); }
+
+ private:
+ // From each URL we generate an ID composed of the scheme, host, port and path
+ // that allows us to uniquely map an entry to it.
+ typedef std::map<std::string, scoped_refptr<URLRequestThrottlerEntry> >
+ UrlEntryMap;
+
+ // We maintain a set of hosts that have opted out of exponential
+ // back-off throttling.
+ typedef std::set<std::string> OptOutHosts;
+
+ // Maximum number of entries that we are willing to collect in our map.
+ static const unsigned int kMaximumNumberOfEntries;
+ // Number of requests that will be made between garbage collection.
+ static const unsigned int kRequestsBetweenCollecting;
+
+ // Map that contains a list of URL ID and their matching
+ // URLRequestThrottlerEntry.
+ UrlEntryMap url_entries_;
+
+ // Set of hosts that have opted out.
+ OptOutHosts opt_out_hosts_;
+
+ // This keeps track of how many requests have been made. Used with
+ // GarbageCollectEntries.
+ unsigned int requests_since_last_gc_;
+
+ // Valid after construction.
+ GURL::Replacements url_id_replacements_;
+
+ // Certain tests do not obey the net component's threading policy, so we
+ // keep track of whether we're being used by tests, and turn off certain
+ // checks.
+ //
+ // TODO(joi): See if we can fix the offending unit tests and remove this
+ // workaround.
+ bool enable_thread_checks_;
+
+ // Initially false, switches to true once we have logged because of back-off
+ // being disabled for localhost.
+ bool logged_for_localhost_disabled_;
+
+ // NetLog to use, if configured.
+ BoundNetLog net_log_;
+
+ // Valid once we've registered for network notifications.
+ base::PlatformThreadId registered_from_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLRequestThrottlerManager);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_MANAGER_H_
diff --git a/src/net/url_request/url_request_throttler_simulation_unittest.cc b/src/net/url_request/url_request_throttler_simulation_unittest.cc
new file mode 100644
index 0000000..48b0319
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_simulation_unittest.cc
@@ -0,0 +1,757 @@
+// Copyright (c) 2012 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.
+
+// The tests in this file attempt to verify the following through simulation:
+// a) That a server experiencing overload will actually benefit from the
+// anti-DDoS throttling logic, i.e. that its traffic spike will subside
+// and be distributed over a longer period of time;
+// b) That "well-behaved" clients of a server under DDoS attack actually
+// benefit from the anti-DDoS throttling logic; and
+// c) That the approximate increase in "perceived downtime" introduced by
+// anti-DDoS throttling for various different actual downtimes is what
+// we expect it to be.
+
+#include <cmath>
+#include <limits>
+#include <vector>
+
+#include "base/environment.h"
+#include "base/memory/scoped_vector.h"
+#include "base/rand_util.h"
+#include "base/time.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/url_request/url_request_throttler_manager.h"
+#include "net/url_request/url_request_throttler_test_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+namespace {
+
+// Set this variable in your environment if you want to see verbose results
+// of the simulation tests.
+const char kShowSimulationVariableName[] = "SHOW_SIMULATION_RESULTS";
+
+// Prints output only if a given environment variable is set. We use this
+// to not print any output for human evaluation when the test is run without
+// supervision.
+void VerboseOut(const char* format, ...) {
+ static bool have_checked_environment = false;
+ static bool should_print = false;
+ if (!have_checked_environment) {
+ have_checked_environment = true;
+ scoped_ptr<base::Environment> env(base::Environment::Create());
+ if (env->HasVar(kShowSimulationVariableName))
+ should_print = true;
+ }
+
+ if (should_print) {
+ va_list arglist;
+ va_start(arglist, format);
+ vprintf(format, arglist);
+ va_end(arglist);
+ }
+}
+
+// A simple two-phase discrete time simulation. Actors are added in the order
+// they should take action at every tick of the clock. Ticks of the clock
+// are two-phase:
+// - Phase 1 advances every actor's time to a new absolute time.
+// - Phase 2 asks each actor to perform their action.
+class DiscreteTimeSimulation {
+ public:
+ class Actor {
+ public:
+ virtual ~Actor() {}
+ virtual void AdvanceTime(const TimeTicks& absolute_time) = 0;
+ virtual void PerformAction() = 0;
+ };
+
+ DiscreteTimeSimulation() {}
+
+ // Adds an |actor| to the simulation. The client of the simulation maintains
+ // ownership of |actor| and must ensure its lifetime exceeds that of the
+ // simulation. Actors should be added in the order you wish for them to
+ // act at each tick of the simulation.
+ void AddActor(Actor* actor) {
+ actors_.push_back(actor);
+ }
+
+ // Runs the simulation for, pretending |time_between_ticks| passes from one
+ // tick to the next. The start time will be the current real time. The
+ // simulation will stop when the simulated duration is equal to or greater
+ // than |maximum_simulated_duration|.
+ void RunSimulation(const TimeDelta& maximum_simulated_duration,
+ const TimeDelta& time_between_ticks) {
+ TimeTicks start_time = TimeTicks();
+ TimeTicks now = start_time;
+ while ((now - start_time) <= maximum_simulated_duration) {
+ for (std::vector<Actor*>::iterator it = actors_.begin();
+ it != actors_.end();
+ ++it) {
+ (*it)->AdvanceTime(now);
+ }
+
+ for (std::vector<Actor*>::iterator it = actors_.begin();
+ it != actors_.end();
+ ++it) {
+ (*it)->PerformAction();
+ }
+
+ now += time_between_ticks;
+ }
+ }
+
+ private:
+ std::vector<Actor*> actors_;
+
+ DISALLOW_COPY_AND_ASSIGN(DiscreteTimeSimulation);
+};
+
+// Represents a web server in a simulation of a server under attack by
+// a lot of clients. Must be added to the simulation's list of actors
+// after all |Requester| objects.
+class Server : public DiscreteTimeSimulation::Actor {
+ public:
+ Server(int max_queries_per_tick,
+ double request_drop_ratio)
+ : max_queries_per_tick_(max_queries_per_tick),
+ request_drop_ratio_(request_drop_ratio),
+ num_overloaded_ticks_remaining_(0),
+ num_current_tick_queries_(0),
+ num_overloaded_ticks_(0),
+ max_experienced_queries_per_tick_(0),
+ mock_request_(GURL(), NULL, &context_) {
+ }
+
+ void SetDowntime(const TimeTicks& start_time, const TimeDelta& duration) {
+ start_downtime_ = start_time;
+ end_downtime_ = start_time + duration;
+ }
+
+ virtual void AdvanceTime(const TimeTicks& absolute_time) OVERRIDE {
+ now_ = absolute_time;
+ }
+
+ virtual void PerformAction() OVERRIDE {
+ // We are inserted at the end of the actor's list, so all Requester
+ // instances have already done their bit.
+ if (num_current_tick_queries_ > max_experienced_queries_per_tick_)
+ max_experienced_queries_per_tick_ = num_current_tick_queries_;
+
+ if (num_current_tick_queries_ > max_queries_per_tick_) {
+ // We pretend the server fails for the next several ticks after it
+ // gets overloaded.
+ num_overloaded_ticks_remaining_ = 5;
+ ++num_overloaded_ticks_;
+ } else if (num_overloaded_ticks_remaining_ > 0) {
+ --num_overloaded_ticks_remaining_;
+ }
+
+ requests_per_tick_.push_back(num_current_tick_queries_);
+ num_current_tick_queries_ = 0;
+ }
+
+ // This is called by Requester. It returns the response code from
+ // the server.
+ int HandleRequest() {
+ ++num_current_tick_queries_;
+ if (!start_downtime_.is_null() &&
+ start_downtime_ < now_ && now_ < end_downtime_) {
+ // For the simulation measuring the increase in perceived
+ // downtime, it might be interesting to count separately the
+ // queries seen by the server (assuming a front-end reverse proxy
+ // is what actually serves up the 503s in this case) so that we could
+ // visualize the traffic spike seen by the server when it comes up,
+ // which would in many situations be ameliorated by the anti-DDoS
+ // throttling.
+ return 503;
+ }
+
+ if ((num_overloaded_ticks_remaining_ > 0 ||
+ num_current_tick_queries_ > max_queries_per_tick_) &&
+ base::RandDouble() < request_drop_ratio_) {
+ return 503;
+ }
+
+ return 200;
+ }
+
+ int num_overloaded_ticks() const {
+ return num_overloaded_ticks_;
+ }
+
+ int max_experienced_queries_per_tick() const {
+ return max_experienced_queries_per_tick_;
+ }
+
+ const URLRequest& mock_request() const {
+ return mock_request_;
+ }
+
+ std::string VisualizeASCII(int terminal_width) {
+ // Account for | characters we place at left of graph.
+ terminal_width -= 1;
+
+ VerboseOut("Overloaded for %d of %d ticks.\n",
+ num_overloaded_ticks_, requests_per_tick_.size());
+ VerboseOut("Got maximum of %d requests in a tick.\n\n",
+ max_experienced_queries_per_tick_);
+
+ VerboseOut("Traffic graph:\n\n");
+
+ // Printing the graph like this is a bit overkill, but was very useful
+ // while developing the various simulations to see if they were testing
+ // the corner cases we want to simulate.
+
+ // Find the smallest number of whole ticks we need to group into a
+ // column that will let all ticks fit into the column width we have.
+ int num_ticks = requests_per_tick_.size();
+ double ticks_per_column_exact =
+ static_cast<double>(num_ticks) / static_cast<double>(terminal_width);
+ int ticks_per_column = std::ceil(ticks_per_column_exact);
+ DCHECK_GE(ticks_per_column * terminal_width, num_ticks);
+
+ // Sum up the column values.
+ int num_columns = num_ticks / ticks_per_column;
+ if (num_ticks % ticks_per_column)
+ ++num_columns;
+ DCHECK_LE(num_columns, terminal_width);
+ scoped_array<int> columns(new int[num_columns]);
+ for (int tx = 0; tx < num_ticks; ++tx) {
+ int cx = tx / ticks_per_column;
+ if (tx % ticks_per_column == 0)
+ columns[cx] = 0;
+ columns[cx] += requests_per_tick_[tx];
+ }
+
+ // Find the lowest integer divisor that will let the column values
+ // be represented in a graph of maximum height 50.
+ int max_value = 0;
+ for (int cx = 0; cx < num_columns; ++cx)
+ max_value = std::max(max_value, columns[cx]);
+ const int kNumRows = 50;
+ double row_divisor_exact = max_value / static_cast<double>(kNumRows);
+ int row_divisor = std::ceil(row_divisor_exact);
+ DCHECK_GE(row_divisor * kNumRows, max_value);
+
+ // To show the overload line, we calculate the appropriate value.
+ int overload_value = max_queries_per_tick_ * ticks_per_column;
+
+ // When num_ticks is not a whole multiple of ticks_per_column, the last
+ // column includes fewer ticks than the others. In this case, don't
+ // print it so that we don't show an inconsistent value.
+ int num_printed_columns = num_columns;
+ if (num_ticks % ticks_per_column)
+ --num_printed_columns;
+
+ // This is a top-to-bottom traversal of rows, left-to-right per row.
+ std::string output;
+ for (int rx = 0; rx < kNumRows; ++rx) {
+ int range_min = (kNumRows - rx) * row_divisor;
+ int range_max = range_min + row_divisor;
+ if (range_min == 0)
+ range_min = -1; // Make 0 values fit in the bottom range.
+ output.append("|");
+ for (int cx = 0; cx < num_printed_columns; ++cx) {
+ char block = ' ';
+ // Show the overload line.
+ if (range_min < overload_value && overload_value <= range_max)
+ block = '-';
+
+ // Preferentially, show the graph line.
+ if (range_min < columns[cx] && columns[cx] <= range_max)
+ block = '#';
+
+ output.append(1, block);
+ }
+ output.append("\n");
+ }
+ output.append("|");
+ output.append(num_printed_columns, '=');
+
+ return output;
+ }
+
+ private:
+ TimeTicks now_;
+ TimeTicks start_downtime_; // Can be 0 to say "no downtime".
+ TimeTicks end_downtime_;
+ const int max_queries_per_tick_;
+ const double request_drop_ratio_; // Ratio of requests to 503 when failing.
+ int num_overloaded_ticks_remaining_;
+ int num_current_tick_queries_;
+ int num_overloaded_ticks_;
+ int max_experienced_queries_per_tick_;
+ std::vector<int> requests_per_tick_;
+
+ TestURLRequestContext context_;
+ TestURLRequest mock_request_;
+
+ DISALLOW_COPY_AND_ASSIGN(Server);
+};
+
+// Mock throttler entry used by Requester class.
+class MockURLRequestThrottlerEntry : public URLRequestThrottlerEntry {
+ public:
+ explicit MockURLRequestThrottlerEntry(
+ URLRequestThrottlerManager* manager)
+ : URLRequestThrottlerEntry(manager, ""),
+ mock_backoff_entry_(&backoff_policy_) {
+ }
+
+ virtual const BackoffEntry* GetBackoffEntry() const OVERRIDE {
+ return &mock_backoff_entry_;
+ }
+
+ virtual BackoffEntry* GetBackoffEntry() OVERRIDE {
+ return &mock_backoff_entry_;
+ }
+
+ virtual TimeTicks ImplGetTimeNow() const OVERRIDE {
+ return fake_now_;
+ }
+
+ void SetFakeNow(const TimeTicks& fake_time) {
+ fake_now_ = fake_time;
+ mock_backoff_entry_.set_fake_now(fake_time);
+ }
+
+ TimeTicks fake_now() const {
+ return fake_now_;
+ }
+
+ protected:
+ virtual ~MockURLRequestThrottlerEntry() {}
+
+ private:
+ TimeTicks fake_now_;
+ MockBackoffEntry mock_backoff_entry_;
+};
+
+// Registry of results for a class of |Requester| objects (e.g. attackers vs.
+// regular clients).
+class RequesterResults {
+public:
+ RequesterResults()
+ : num_attempts_(0), num_successful_(0), num_failed_(0), num_blocked_(0) {
+ }
+
+ void AddSuccess() {
+ ++num_attempts_;
+ ++num_successful_;
+ }
+
+ void AddFailure() {
+ ++num_attempts_;
+ ++num_failed_;
+ }
+
+ void AddBlocked() {
+ ++num_attempts_;
+ ++num_blocked_;
+ }
+
+ int num_attempts() const { return num_attempts_; }
+ int num_successful() const { return num_successful_; }
+ int num_failed() const { return num_failed_; }
+ int num_blocked() const { return num_blocked_; }
+
+ double GetBlockedRatio() {
+ DCHECK(num_attempts_);
+ return static_cast<double>(num_blocked_) /
+ static_cast<double>(num_attempts_);
+ }
+
+ double GetSuccessRatio() {
+ DCHECK(num_attempts_);
+ return static_cast<double>(num_successful_) /
+ static_cast<double>(num_attempts_);
+ }
+
+ void PrintResults(const char* class_description) {
+ if (num_attempts_ == 0) {
+ VerboseOut("No data for %s\n", class_description);
+ return;
+ }
+
+ VerboseOut("Requester results for %s\n", class_description);
+ VerboseOut(" %d attempts\n", num_attempts_);
+ VerboseOut(" %d successes\n", num_successful_);
+ VerboseOut(" %d 5xx responses\n", num_failed_);
+ VerboseOut(" %d requests blocked\n", num_blocked_);
+ VerboseOut(" %.2f success ratio\n", GetSuccessRatio());
+ VerboseOut(" %.2f blocked ratio\n", GetBlockedRatio());
+ VerboseOut("\n");
+ }
+
+ private:
+ int num_attempts_;
+ int num_successful_;
+ int num_failed_;
+ int num_blocked_;
+};
+
+// Represents an Requester in a simulated DDoS situation, that periodically
+// requests a specific resource.
+class Requester : public DiscreteTimeSimulation::Actor {
+ public:
+ Requester(MockURLRequestThrottlerEntry* throttler_entry,
+ const TimeDelta& time_between_requests,
+ Server* server,
+ RequesterResults* results)
+ : throttler_entry_(throttler_entry),
+ time_between_requests_(time_between_requests),
+ last_attempt_was_failure_(false),
+ server_(server),
+ results_(results) {
+ DCHECK(server_);
+ }
+
+ void AdvanceTime(const TimeTicks& absolute_time) OVERRIDE {
+ if (time_of_last_success_.is_null())
+ time_of_last_success_ = absolute_time;
+
+ throttler_entry_->SetFakeNow(absolute_time);
+ }
+
+ void PerformAction() OVERRIDE {
+ TimeDelta effective_delay = time_between_requests_;
+ TimeDelta current_jitter = TimeDelta::FromMilliseconds(
+ request_jitter_.InMilliseconds() * base::RandDouble());
+ if (base::RandInt(0, 1)) {
+ effective_delay -= current_jitter;
+ } else {
+ effective_delay += current_jitter;
+ }
+
+ if (throttler_entry_->fake_now() - time_of_last_attempt_ >
+ effective_delay) {
+ if (!throttler_entry_->ShouldRejectRequest(server_->mock_request())) {
+ int status_code = server_->HandleRequest();
+ MockURLRequestThrottlerHeaderAdapter response_headers(status_code);
+ throttler_entry_->UpdateWithResponse("", &response_headers);
+
+ if (status_code == 200) {
+ if (results_)
+ results_->AddSuccess();
+
+ if (last_attempt_was_failure_) {
+ last_downtime_duration_ =
+ throttler_entry_->fake_now() - time_of_last_success_;
+ }
+
+ time_of_last_success_ = throttler_entry_->fake_now();
+ last_attempt_was_failure_ = false;
+ } else {
+ if (results_)
+ results_->AddFailure();
+ last_attempt_was_failure_ = true;
+ }
+ } else {
+ if (results_)
+ results_->AddBlocked();
+ last_attempt_was_failure_ = true;
+ }
+
+ time_of_last_attempt_ = throttler_entry_->fake_now();
+ }
+ }
+
+ // Adds a delay until the first request, equal to a uniformly distributed
+ // value between now and now + max_delay.
+ void SetStartupJitter(const TimeDelta& max_delay) {
+ int delay_ms = base::RandInt(0, max_delay.InMilliseconds());
+ time_of_last_attempt_ = TimeTicks() +
+ TimeDelta::FromMilliseconds(delay_ms) - time_between_requests_;
+ }
+
+ void SetRequestJitter(const TimeDelta& request_jitter) {
+ request_jitter_ = request_jitter;
+ }
+
+ TimeDelta last_downtime_duration() const { return last_downtime_duration_; }
+
+ private:
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry_;
+ const TimeDelta time_between_requests_;
+ TimeDelta request_jitter_;
+ TimeTicks time_of_last_attempt_;
+ TimeTicks time_of_last_success_;
+ bool last_attempt_was_failure_;
+ TimeDelta last_downtime_duration_;
+ Server* const server_;
+ RequesterResults* const results_; // May be NULL.
+
+ DISALLOW_COPY_AND_ASSIGN(Requester);
+};
+
+void SimulateAttack(Server* server,
+ RequesterResults* attacker_results,
+ RequesterResults* client_results,
+ bool enable_throttling) {
+ const size_t kNumAttackers = 50;
+ const size_t kNumClients = 50;
+ DiscreteTimeSimulation simulation;
+ URLRequestThrottlerManager manager;
+ ScopedVector<Requester> requesters;
+ for (size_t i = 0; i < kNumAttackers; ++i) {
+ // Use a tiny time_between_requests so the attackers will ping the
+ // server at every tick of the simulation.
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry(
+ new MockURLRequestThrottlerEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester* attacker = new Requester(throttler_entry.get(),
+ TimeDelta::FromMilliseconds(1),
+ server,
+ attacker_results);
+ attacker->SetStartupJitter(TimeDelta::FromSeconds(120));
+ requesters.push_back(attacker);
+ simulation.AddActor(attacker);
+ }
+ for (size_t i = 0; i < kNumClients; ++i) {
+ // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry(
+ new MockURLRequestThrottlerEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester* client = new Requester(throttler_entry.get(),
+ TimeDelta::FromMinutes(2),
+ server,
+ client_results);
+ client->SetStartupJitter(TimeDelta::FromSeconds(120));
+ client->SetRequestJitter(TimeDelta::FromMinutes(1));
+ requesters.push_back(client);
+ simulation.AddActor(client);
+ }
+ simulation.AddActor(server);
+
+ simulation.RunSimulation(TimeDelta::FromMinutes(6),
+ TimeDelta::FromSeconds(1));
+}
+
+TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
+ Server unprotected_server(30, 1.0);
+ RequesterResults unprotected_attacker_results;
+ RequesterResults unprotected_client_results;
+ Server protected_server(30, 1.0);
+ RequesterResults protected_attacker_results;
+ RequesterResults protected_client_results;
+ SimulateAttack(&unprotected_server,
+ &unprotected_attacker_results,
+ &unprotected_client_results,
+ false);
+ SimulateAttack(&protected_server,
+ &protected_attacker_results,
+ &protected_client_results,
+ true);
+
+ // These assert that the DDoS protection actually benefits the
+ // server. Manual inspection of the traffic graphs will show this
+ // even more clearly.
+ EXPECT_GT(unprotected_server.num_overloaded_ticks(),
+ protected_server.num_overloaded_ticks());
+ EXPECT_GT(unprotected_server.max_experienced_queries_per_tick(),
+ protected_server.max_experienced_queries_per_tick());
+
+ // These assert that the DDoS protection actually benefits non-malicious
+ // (and non-degenerate/accidentally DDoSing) users.
+ EXPECT_LT(protected_client_results.GetBlockedRatio(),
+ protected_attacker_results.GetBlockedRatio());
+ EXPECT_GT(protected_client_results.GetSuccessRatio(),
+ unprotected_client_results.GetSuccessRatio());
+
+ // The rest is just for optional manual evaluation of the results;
+ // in particular the traffic pattern is interesting.
+
+ VerboseOut("\nUnprotected server's results:\n\n");
+ VerboseOut(unprotected_server.VisualizeASCII(132).c_str());
+ VerboseOut("\n\n");
+ VerboseOut("Protected server's results:\n\n");
+ VerboseOut(protected_server.VisualizeASCII(132).c_str());
+ VerboseOut("\n\n");
+
+ unprotected_attacker_results.PrintResults(
+ "attackers attacking unprotected server.");
+ unprotected_client_results.PrintResults(
+ "normal clients making requests to unprotected server.");
+ protected_attacker_results.PrintResults(
+ "attackers attacking protected server.");
+ protected_client_results.PrintResults(
+ "normal clients making requests to protected server.");
+}
+
+// Returns the downtime perceived by the client, as a ratio of the
+// actual downtime.
+double SimulateDowntime(const TimeDelta& duration,
+ const TimeDelta& average_client_interval,
+ bool enable_throttling) {
+ TimeDelta time_between_ticks = duration / 200;
+ TimeTicks start_downtime = TimeTicks() + (duration / 2);
+
+ // A server that never rejects requests, but will go down for maintenance.
+ Server server(std::numeric_limits<int>::max(), 1.0);
+ server.SetDowntime(start_downtime, duration);
+
+ URLRequestThrottlerManager manager;
+ scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry(
+ new MockURLRequestThrottlerEntry(&manager));
+ if (!enable_throttling)
+ throttler_entry->DisableBackoffThrottling();
+
+ Requester requester(
+ throttler_entry.get(), average_client_interval, &server, NULL);
+ requester.SetStartupJitter(duration / 3);
+ requester.SetRequestJitter(average_client_interval);
+
+ DiscreteTimeSimulation simulation;
+ simulation.AddActor(&requester);
+ simulation.AddActor(&server);
+
+ simulation.RunSimulation(duration * 2, time_between_ticks);
+
+ return static_cast<double>(
+ requester.last_downtime_duration().InMilliseconds()) /
+ static_cast<double>(duration.InMilliseconds());
+}
+
+TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
+ struct Stats {
+ // Expected interval that we expect the ratio of downtime when anti-DDoS
+ // is enabled and downtime when anti-DDoS is not enabled to fall within.
+ //
+ // The expected interval depends on two things: The exponential back-off
+ // policy encoded in URLRequestThrottlerEntry, and the test or set of
+ // tests that the Stats object is tracking (e.g. a test where the client
+ // retries very rapidly on a very long downtime will tend to increase the
+ // number).
+ //
+ // To determine an appropriate new interval when parameters have changed,
+ // run the test a few times (you may have to Ctrl-C out of it after a few
+ // seconds) and choose an interval that the test converges quickly and
+ // reliably to. Then set the new interval, and run the test e.g. 20 times
+ // in succession to make sure it never takes an obscenely long time to
+ // converge to this interval.
+ double expected_min_increase;
+ double expected_max_increase;
+
+ size_t num_runs;
+ double total_ratio_unprotected;
+ double total_ratio_protected;
+
+ bool DidConverge(double* increase_ratio_out) {
+ double unprotected_ratio = total_ratio_unprotected / num_runs;
+ double protected_ratio = total_ratio_protected / num_runs;
+ double increase_ratio = protected_ratio / unprotected_ratio;
+ if (increase_ratio_out)
+ *increase_ratio_out = increase_ratio;
+ return expected_min_increase <= increase_ratio &&
+ increase_ratio <= expected_max_increase;
+ }
+
+ void ReportTrialResult(double increase_ratio) {
+ VerboseOut(
+ " Perceived downtime with throttling is %.4f times without.\n",
+ increase_ratio);
+ VerboseOut(" Test result after %d trials.\n", num_runs);
+ }
+ };
+
+ Stats global_stats = { 1.08, 1.15 };
+
+ struct Trial {
+ TimeDelta duration;
+ TimeDelta average_client_interval;
+ Stats stats;
+
+ void PrintTrialDescription() {
+ double duration_minutes =
+ static_cast<double>(duration.InSeconds()) / 60.0;
+ double interval_minutes =
+ static_cast<double>(average_client_interval.InSeconds()) / 60.0;
+ VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
+ duration_minutes, interval_minutes);
+ }
+ };
+
+ // We don't set or check expected ratio intervals on individual
+ // experiments as this might make the test too fragile, but we
+ // print them out at the end for manual evaluation (we want to be
+ // able to make claims about the expected ratios depending on the
+ // type of behavior of the client and the downtime, e.g. the difference
+ // in behavior between a client making requests every few minutes vs.
+ // one that makes a request every 15 seconds).
+ Trial trials[] = {
+ { TimeDelta::FromSeconds(10), TimeDelta::FromSeconds(3) },
+ { TimeDelta::FromSeconds(30), TimeDelta::FromSeconds(7) },
+ { TimeDelta::FromMinutes(5), TimeDelta::FromSeconds(30) },
+ { TimeDelta::FromMinutes(10), TimeDelta::FromSeconds(20) },
+ { TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(15) },
+ { TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(50) },
+ { TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(2) },
+ { TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(5) },
+ { TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(7) },
+ { TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(2) },
+ { TimeDelta::FromMinutes(40), TimeDelta::FromSeconds(15) },
+ { TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(7) },
+ { TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(2) },
+ { TimeDelta::FromMinutes(60), TimeDelta::FromSeconds(15) },
+ { TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(20) },
+ { TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(3) },
+ { TimeDelta::FromMinutes(80), TimeDelta::FromSeconds(15) },
+
+ // Most brutal?
+ { TimeDelta::FromMinutes(45), TimeDelta::FromMilliseconds(500) },
+ };
+
+ // If things don't converge by the time we've done 100K trials, then
+ // clearly one or more of the expected intervals are wrong.
+ while (global_stats.num_runs < 100000) {
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(trials); ++i) {
+ ++global_stats.num_runs;
+ ++trials[i].stats.num_runs;
+ double ratio_unprotected = SimulateDowntime(
+ trials[i].duration, trials[i].average_client_interval, false);
+ double ratio_protected = SimulateDowntime(
+ trials[i].duration, trials[i].average_client_interval, true);
+ global_stats.total_ratio_unprotected += ratio_unprotected;
+ global_stats.total_ratio_protected += ratio_protected;
+ trials[i].stats.total_ratio_unprotected += ratio_unprotected;
+ trials[i].stats.total_ratio_protected += ratio_protected;
+ }
+
+ double increase_ratio;
+ if (global_stats.DidConverge(&increase_ratio))
+ break;
+
+ if (global_stats.num_runs > 200) {
+ VerboseOut("Test has not yet converged on expected interval.\n");
+ global_stats.ReportTrialResult(increase_ratio);
+ }
+ }
+
+ double average_increase_ratio;
+ EXPECT_TRUE(global_stats.DidConverge(&average_increase_ratio));
+
+ // Print individual trial results for optional manual evaluation.
+ double max_increase_ratio = 0.0;
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(trials); ++i) {
+ double increase_ratio;
+ trials[i].stats.DidConverge(&increase_ratio);
+ max_increase_ratio = std::max(max_increase_ratio, increase_ratio);
+ trials[i].PrintTrialDescription();
+ trials[i].stats.ReportTrialResult(increase_ratio);
+ }
+
+ VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio);
+ VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio);
+}
+
+} // namespace
+} // namespace net
diff --git a/src/net/url_request/url_request_throttler_test_support.cc b/src/net/url_request/url_request_throttler_test_support.cc
new file mode 100644
index 0000000..b6088ec
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_test_support.cc
@@ -0,0 +1,58 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_throttler_test_support.h"
+
+#include "net/url_request/url_request_throttler_entry.h"
+
+namespace net {
+
+MockBackoffEntry::MockBackoffEntry(const BackoffEntry::Policy* const policy)
+ : BackoffEntry(policy) {
+}
+
+MockBackoffEntry::~MockBackoffEntry() {
+}
+
+base::TimeTicks MockBackoffEntry::ImplGetTimeNow() const {
+ return fake_now_;
+}
+
+void MockBackoffEntry::set_fake_now(const base::TimeTicks& now) {
+ fake_now_ = now;
+}
+
+MockURLRequestThrottlerHeaderAdapter::MockURLRequestThrottlerHeaderAdapter(
+ int response_code)
+ : fake_response_code_(response_code) {
+}
+
+MockURLRequestThrottlerHeaderAdapter::MockURLRequestThrottlerHeaderAdapter(
+ const std::string& retry_value,
+ const std::string& opt_out_value,
+ int response_code)
+ : fake_retry_value_(retry_value),
+ fake_opt_out_value_(opt_out_value),
+ fake_response_code_(response_code) {
+}
+
+MockURLRequestThrottlerHeaderAdapter::~MockURLRequestThrottlerHeaderAdapter() {
+}
+
+std::string MockURLRequestThrottlerHeaderAdapter::GetNormalizedValue(
+ const std::string& key) const {
+ if (key ==
+ URLRequestThrottlerEntry::kExponentialThrottlingHeader &&
+ !fake_opt_out_value_.empty()) {
+ return fake_opt_out_value_;
+ }
+
+ return "";
+}
+
+int MockURLRequestThrottlerHeaderAdapter::GetResponseCode() const {
+ return fake_response_code_;
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_throttler_test_support.h b/src/net/url_request/url_request_throttler_test_support.h
new file mode 100644
index 0000000..cad041c
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_test_support.h
@@ -0,0 +1,61 @@
+// Copyright (c) 2011 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.
+
+#ifndef NET_URL_REQUEST_URL_REQUEST_THROTTLER_TEST_SUPPORT_H_
+#define NET_URL_REQUEST_URL_REQUEST_THROTTLER_TEST_SUPPORT_H_
+
+#include <string>
+
+#include "base/time.h"
+#include "net/base/backoff_entry.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+
+namespace net {
+
+class MockBackoffEntry : public BackoffEntry {
+ public:
+ explicit MockBackoffEntry(const BackoffEntry::Policy* const policy);
+ virtual ~MockBackoffEntry();
+
+ // BackoffEntry overrides.
+ virtual base::TimeTicks ImplGetTimeNow() const OVERRIDE;
+
+ void set_fake_now(const base::TimeTicks& now);
+
+ private:
+ base::TimeTicks fake_now_;
+};
+
+// Mocks the URLRequestThrottlerHeaderInterface, allowing testing code to
+// pass arbitrary fake headers to the throttling code.
+class MockURLRequestThrottlerHeaderAdapter
+ : public URLRequestThrottlerHeaderInterface {
+ public:
+ // Constructs mock response headers with the given |response_code| and no
+ // custom response header fields.
+ explicit MockURLRequestThrottlerHeaderAdapter(int response_code);
+
+ // Constructs mock response headers with the given |response_code| and
+ // with a custom-retry header value |retry_value| if it is non-empty, and
+ // a custom opt-out header value |opt_out_value| if it is non-empty.
+ MockURLRequestThrottlerHeaderAdapter(const std::string& retry_value,
+ const std::string& opt_out_value,
+ int response_code);
+ virtual ~MockURLRequestThrottlerHeaderAdapter();
+
+ // URLRequestThrottlerHeaderInterface overrides.
+ virtual std::string GetNormalizedValue(const std::string& key) const OVERRIDE;
+ virtual int GetResponseCode() const OVERRIDE;
+
+ private:
+ std::string fake_retry_value_;
+ std::string fake_opt_out_value_;
+ int fake_response_code_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockURLRequestThrottlerHeaderAdapter);
+};
+
+} // namespace net
+
+#endif // NET_URL_REQUEST_URL_REQUEST_THROTTLER_TEST_SUPPORT_H_
diff --git a/src/net/url_request/url_request_throttler_unittest.cc b/src/net/url_request/url_request_throttler_unittest.cc
new file mode 100644
index 0000000..221d0c8
--- /dev/null
+++ b/src/net/url_request/url_request_throttler_unittest.cc
@@ -0,0 +1,563 @@
+// Copyright (c) 2012 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 "net/url_request/url_request_throttler_manager.h"
+
+#include "base/memory/scoped_ptr.h"
+#include "base/metrics/histogram.h"
+#include "base/metrics/histogram_samples.h"
+#include "base/metrics/statistics_recorder.h"
+#include "base/pickle.h"
+#include "base/stl_util.h"
+#include "base/stringprintf.h"
+#include "base/string_number_conversions.h"
+#include "base/time.h"
+#include "net/base/load_flags.h"
+#include "net/base/test_completion_callback.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_test_util.h"
+#include "net/url_request/url_request_throttler_header_interface.h"
+#include "net/url_request/url_request_throttler_test_support.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::TimeDelta;
+using base::TimeTicks;
+
+namespace net {
+
+namespace {
+
+using base::Histogram;
+using base::HistogramSamples;
+using base::StatisticsRecorder;
+
+class MockURLRequestThrottlerEntry : public URLRequestThrottlerEntry {
+ public:
+ explicit MockURLRequestThrottlerEntry(
+ net::URLRequestThrottlerManager* manager)
+ : net::URLRequestThrottlerEntry(manager, ""),
+ mock_backoff_entry_(&backoff_policy_) {
+ InitPolicy();
+ }
+ MockURLRequestThrottlerEntry(
+ net::URLRequestThrottlerManager* manager,
+ const TimeTicks& exponential_backoff_release_time,
+ const TimeTicks& sliding_window_release_time,
+ const TimeTicks& fake_now)
+ : net::URLRequestThrottlerEntry(manager, ""),
+ fake_time_now_(fake_now),
+ mock_backoff_entry_(&backoff_policy_) {
+ InitPolicy();
+
+ mock_backoff_entry_.set_fake_now(fake_now);
+ set_exponential_backoff_release_time(exponential_backoff_release_time);
+ set_sliding_window_release_time(sliding_window_release_time);
+ }
+
+ void InitPolicy() {
+ // Some tests become flaky if we have jitter.
+ backoff_policy_.jitter_factor = 0.0;
+
+ // This lets us avoid having to make multiple failures initially (this
+ // logic is already tested in the BackoffEntry unit tests).
+ backoff_policy_.num_errors_to_ignore = 0;
+ }
+
+ const BackoffEntry* GetBackoffEntry() const {
+ return &mock_backoff_entry_;
+ }
+
+ BackoffEntry* GetBackoffEntry() {
+ return &mock_backoff_entry_;
+ }
+
+ static bool ExplicitUserRequest(int load_flags) {
+ return URLRequestThrottlerEntry::ExplicitUserRequest(load_flags);
+ }
+
+ void ResetToBlank(const TimeTicks& time_now) {
+ fake_time_now_ = time_now;
+ mock_backoff_entry_.set_fake_now(time_now);
+
+ GetBackoffEntry()->Reset();
+ GetBackoffEntry()->SetCustomReleaseTime(time_now);
+ set_sliding_window_release_time(time_now);
+ }
+
+ // Overridden for tests.
+ virtual TimeTicks ImplGetTimeNow() const OVERRIDE { return fake_time_now_; }
+
+ void set_exponential_backoff_release_time(
+ const base::TimeTicks& release_time) {
+ GetBackoffEntry()->SetCustomReleaseTime(release_time);
+ }
+
+ base::TimeTicks sliding_window_release_time() const {
+ return URLRequestThrottlerEntry::sliding_window_release_time();
+ }
+
+ void set_sliding_window_release_time(
+ const base::TimeTicks& release_time) {
+ URLRequestThrottlerEntry::set_sliding_window_release_time(
+ release_time);
+ }
+
+ TimeTicks fake_time_now_;
+ MockBackoffEntry mock_backoff_entry_;
+
+ protected:
+ virtual ~MockURLRequestThrottlerEntry() {}
+};
+
+class MockURLRequestThrottlerManager : public URLRequestThrottlerManager {
+ public:
+ MockURLRequestThrottlerManager() : create_entry_index_(0) {}
+
+ // Method to process the URL using URLRequestThrottlerManager protected
+ // method.
+ std::string DoGetUrlIdFromUrl(const GURL& url) { return GetIdFromUrl(url); }
+
+ // Method to use the garbage collecting method of URLRequestThrottlerManager.
+ void DoGarbageCollectEntries() { GarbageCollectEntries(); }
+
+ // Returns the number of entries in the map.
+ int GetNumberOfEntries() const { return GetNumberOfEntriesForTests(); }
+
+ void CreateEntry(bool is_outdated) {
+ TimeTicks time = TimeTicks::Now();
+ if (is_outdated) {
+ time -= TimeDelta::FromMilliseconds(
+ MockURLRequestThrottlerEntry::kDefaultEntryLifetimeMs + 1000);
+ }
+ std::string fake_url_string("http://www.fakeurl.com/");
+ fake_url_string.append(base::IntToString(create_entry_index_++));
+ GURL fake_url(fake_url_string);
+ OverrideEntryForTests(
+ fake_url,
+ new MockURLRequestThrottlerEntry(this, time, TimeTicks::Now(),
+ TimeTicks::Now()));
+ }
+
+ private:
+ int create_entry_index_;
+};
+
+struct TimeAndBool {
+ TimeAndBool(const TimeTicks& time_value, bool expected, int line_num) {
+ time = time_value;
+ result = expected;
+ line = line_num;
+ }
+ TimeTicks time;
+ bool result;
+ int line;
+};
+
+struct GurlAndString {
+ GurlAndString(const GURL& url_value,
+ const std::string& expected,
+ int line_num) {
+ url = url_value;
+ result = expected;
+ line = line_num;
+ }
+ GURL url;
+ std::string result;
+ int line;
+};
+
+} // namespace
+
+class URLRequestThrottlerEntryTest : public testing::Test {
+ protected:
+ URLRequestThrottlerEntryTest() : request_(GURL(), NULL, &context_) {
+ }
+
+ virtual void SetUp();
+ virtual void TearDown();
+
+ // After calling this function, histogram snapshots in |samples_| contain
+ // only the delta caused by the test case currently running.
+ void CalculateHistogramDeltas();
+
+ TimeTicks now_;
+ MockURLRequestThrottlerManager manager_; // Dummy object, not used.
+ scoped_refptr<MockURLRequestThrottlerEntry> entry_;
+
+ std::map<std::string, HistogramSamples*> original_samples_;
+ std::map<std::string, HistogramSamples*> samples_;
+
+ TestURLRequestContext context_;
+ TestURLRequest request_;
+};
+
+// List of all histograms we care about in these unit tests.
+const char* kHistogramNames[] = {
+ "Throttling.FailureCountAtSuccess",
+ "Throttling.PerceivedDowntime",
+ "Throttling.RequestThrottled",
+ "Throttling.SiteOptedOut",
+};
+
+void URLRequestThrottlerEntryTest::SetUp() {
+ request_.set_load_flags(0);
+
+ now_ = TimeTicks::Now();
+ entry_ = new MockURLRequestThrottlerEntry(&manager_);
+ entry_->ResetToBlank(now_);
+
+ for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
+ // Must retrieve original samples for each histogram for comparison
+ // as other tests may affect them.
+ const char* name = kHistogramNames[i];
+ Histogram* histogram = StatisticsRecorder::FindHistogram(name);
+ if (histogram) {
+ original_samples_[name] = histogram->SnapshotSamples().release();
+ } else {
+ original_samples_[name] = NULL;
+ }
+ }
+}
+
+void URLRequestThrottlerEntryTest::TearDown() {
+ STLDeleteValues(&original_samples_);
+ STLDeleteValues(&samples_);
+}
+
+void URLRequestThrottlerEntryTest::CalculateHistogramDeltas() {
+ for (size_t i = 0; i < arraysize(kHistogramNames); ++i) {
+ const char* name = kHistogramNames[i];
+ HistogramSamples* original = original_samples_[name];
+
+ Histogram* histogram = StatisticsRecorder::FindHistogram(name);
+ if (histogram) {
+ ASSERT_EQ(Histogram::kUmaTargetedHistogramFlag, histogram->flags());
+
+ scoped_ptr<HistogramSamples> samples(histogram->SnapshotSamples());
+ if (original)
+ samples->Subtract(*original);
+ samples_[name] = samples.release();
+ }
+ }
+
+ // Ensure we don't accidentally use the originals in our tests.
+ STLDeleteValues(&original_samples_);
+ original_samples_.clear();
+}
+
+std::ostream& operator<<(std::ostream& out, const base::TimeTicks& time) {
+ return out << time.ToInternalValue();
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceDuringExponentialBackoff) {
+ entry_->set_exponential_backoff_release_time(
+ entry_->fake_time_now_ + TimeDelta::FromMilliseconds(1));
+ EXPECT_TRUE(entry_->ShouldRejectRequest(request_));
+
+ // Also end-to-end test the load flags exceptions.
+ request_.set_load_flags(LOAD_MAYBE_USER_GESTURE);
+ EXPECT_FALSE(entry_->ShouldRejectRequest(request_));
+
+ CalculateHistogramDeltas();
+ ASSERT_EQ(1, samples_["Throttling.RequestThrottled"]->GetCount(0));
+ ASSERT_EQ(1, samples_["Throttling.RequestThrottled"]->GetCount(1));
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceNotDuringExponentialBackoff) {
+ entry_->set_exponential_backoff_release_time(entry_->fake_time_now_);
+ EXPECT_FALSE(entry_->ShouldRejectRequest(request_));
+ entry_->set_exponential_backoff_release_time(
+ entry_->fake_time_now_ - TimeDelta::FromMilliseconds(1));
+ EXPECT_FALSE(entry_->ShouldRejectRequest(request_));
+
+ CalculateHistogramDeltas();
+ ASSERT_EQ(2, samples_["Throttling.RequestThrottled"]->GetCount(0));
+ ASSERT_EQ(0, samples_["Throttling.RequestThrottled"]->GetCount(1));
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceUpdateFailure) {
+ MockURLRequestThrottlerHeaderAdapter failure_response(503);
+ entry_->UpdateWithResponse("", &failure_response);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), entry_->fake_time_now_)
+ << "A failure should increase the release_time";
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceUpdateSuccess) {
+ MockURLRequestThrottlerHeaderAdapter success_response(200);
+ entry_->UpdateWithResponse("", &success_response);
+ EXPECT_EQ(entry_->GetExponentialBackoffReleaseTime(), entry_->fake_time_now_)
+ << "A success should not add any delay";
+}
+
+TEST_F(URLRequestThrottlerEntryTest, InterfaceUpdateSuccessThenFailure) {
+ MockURLRequestThrottlerHeaderAdapter failure_response(503);
+ MockURLRequestThrottlerHeaderAdapter success_response(200);
+ entry_->UpdateWithResponse("", &success_response);
+ entry_->UpdateWithResponse("", &failure_response);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), entry_->fake_time_now_)
+ << "This scenario should add delay";
+ entry_->UpdateWithResponse("", &success_response);
+}
+
+TEST_F(URLRequestThrottlerEntryTest, IsEntryReallyOutdated) {
+ TimeDelta lifetime = TimeDelta::FromMilliseconds(
+ MockURLRequestThrottlerEntry::kDefaultEntryLifetimeMs);
+ const TimeDelta kFiveMs = TimeDelta::FromMilliseconds(5);
+
+ TimeAndBool test_values[] = {
+ TimeAndBool(now_, false, __LINE__),
+ TimeAndBool(now_ - kFiveMs, false, __LINE__),
+ TimeAndBool(now_ + kFiveMs, false, __LINE__),
+ TimeAndBool(now_ - (lifetime - kFiveMs), false, __LINE__),
+ TimeAndBool(now_ - lifetime, true, __LINE__),
+ TimeAndBool(now_ - (lifetime + kFiveMs), true, __LINE__)};
+
+ for (unsigned int i = 0; i < arraysize(test_values); ++i) {
+ entry_->set_exponential_backoff_release_time(test_values[i].time);
+ EXPECT_EQ(entry_->IsEntryOutdated(), test_values[i].result) <<
+ "Test case #" << i << " line " << test_values[i].line << " failed";
+ }
+}
+
+TEST_F(URLRequestThrottlerEntryTest, MaxAllowedBackoff) {
+ for (int i = 0; i < 30; ++i) {
+ MockURLRequestThrottlerHeaderAdapter response_adapter(503);
+ entry_->UpdateWithResponse("", &response_adapter);
+ }
+
+ TimeDelta delay = entry_->GetExponentialBackoffReleaseTime() - now_;
+ EXPECT_EQ(delay.InMilliseconds(),
+ MockURLRequestThrottlerEntry::kDefaultMaximumBackoffMs);
+}
+
+TEST_F(URLRequestThrottlerEntryTest, MalformedContent) {
+ MockURLRequestThrottlerHeaderAdapter response_adapter(503);
+ for (int i = 0; i < 5; ++i)
+ entry_->UpdateWithResponse("", &response_adapter);
+
+ TimeTicks release_after_failures = entry_->GetExponentialBackoffReleaseTime();
+
+ // Inform the entry that a response body was malformed, which is supposed to
+ // increase the back-off time. Note that we also submit a successful
+ // UpdateWithResponse to pair with ReceivedContentWasMalformed() since that
+ // is what happens in practice (if a body is received, then a non-500
+ // response must also have been received).
+ entry_->ReceivedContentWasMalformed(200);
+ MockURLRequestThrottlerHeaderAdapter success_adapter(200);
+ entry_->UpdateWithResponse("", &success_adapter);
+ EXPECT_GT(entry_->GetExponentialBackoffReleaseTime(), release_after_failures);
+}
+
+TEST_F(URLRequestThrottlerEntryTest, SlidingWindow) {
+ int max_send = URLRequestThrottlerEntry::kDefaultMaxSendThreshold;
+ int sliding_window =
+ URLRequestThrottlerEntry::kDefaultSlidingWindowPeriodMs;
+
+ TimeTicks time_1 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(sliding_window / 3);
+ TimeTicks time_2 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(2 * sliding_window / 3);
+ TimeTicks time_3 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(sliding_window);
+ TimeTicks time_4 = entry_->fake_time_now_ +
+ TimeDelta::FromMilliseconds(sliding_window + 2 * sliding_window / 3);
+
+ entry_->set_exponential_backoff_release_time(time_1);
+
+ for (int i = 0; i < max_send / 2; ++i) {
+ EXPECT_EQ(2 * sliding_window / 3,
+ entry_->ReserveSendingTimeForNextRequest(time_2));
+ }
+ EXPECT_EQ(time_2, entry_->sliding_window_release_time());
+
+ entry_->fake_time_now_ = time_3;
+
+ for (int i = 0; i < (max_send + 1) / 2; ++i)
+ EXPECT_EQ(0, entry_->ReserveSendingTimeForNextRequest(TimeTicks()));
+
+ EXPECT_EQ(time_4, entry_->sliding_window_release_time());
+}
+
+TEST_F(URLRequestThrottlerEntryTest, ExplicitUserRequest) {
+ ASSERT_FALSE(MockURLRequestThrottlerEntry::ExplicitUserRequest(0));
+ ASSERT_TRUE(MockURLRequestThrottlerEntry::ExplicitUserRequest(
+ LOAD_MAYBE_USER_GESTURE));
+ ASSERT_FALSE(MockURLRequestThrottlerEntry::ExplicitUserRequest(
+ ~LOAD_MAYBE_USER_GESTURE));
+}
+
+class URLRequestThrottlerManagerTest : public testing::Test {
+ protected:
+ URLRequestThrottlerManagerTest()
+ : request_(GURL(), NULL, &context_) {
+ }
+
+ virtual void SetUp() {
+ request_.set_load_flags(0);
+ }
+
+ // context_ must be declared before request_.
+ TestURLRequestContext context_;
+ TestURLRequest request_;
+};
+
+TEST_F(URLRequestThrottlerManagerTest, IsUrlStandardised) {
+ MockURLRequestThrottlerManager manager;
+ GurlAndString test_values[] = {
+ GurlAndString(GURL("http://www.example.com"),
+ std::string("http://www.example.com/"),
+ __LINE__),
+ GurlAndString(GURL("http://www.Example.com"),
+ std::string("http://www.example.com/"),
+ __LINE__),
+ GurlAndString(GURL("http://www.ex4mple.com/Pr4c71c41"),
+ std::string("http://www.ex4mple.com/pr4c71c41"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/0/token/false"),
+ std::string("http://www.example.com/0/token/false"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php?code=javascript"),
+ std::string("http://www.example.com/index.php"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php?code=1#superEntry"),
+ std::string("http://www.example.com/index.php"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com/index.php#superEntry"),
+ std::string("http://www.example.com/index.php"),
+ __LINE__),
+ GurlAndString(GURL("http://www.example.com:1234/"),
+ std::string("http://www.example.com:1234/"),
+ __LINE__)};
+
+ for (unsigned int i = 0; i < arraysize(test_values); ++i) {
+ std::string temp = manager.DoGetUrlIdFromUrl(test_values[i].url);
+ EXPECT_EQ(temp, test_values[i].result) <<
+ "Test case #" << i << " line " << test_values[i].line << " failed";
+ }
+}
+
+TEST_F(URLRequestThrottlerManagerTest, AreEntriesBeingCollected) {
+ MockURLRequestThrottlerManager manager;
+
+ manager.CreateEntry(true); // true = Entry is outdated.
+ manager.CreateEntry(true);
+ manager.CreateEntry(true);
+ manager.DoGarbageCollectEntries();
+ EXPECT_EQ(0, manager.GetNumberOfEntries());
+
+ manager.CreateEntry(false);
+ manager.CreateEntry(false);
+ manager.CreateEntry(false);
+ manager.CreateEntry(true);
+ manager.DoGarbageCollectEntries();
+ EXPECT_EQ(3, manager.GetNumberOfEntries());
+}
+
+TEST_F(URLRequestThrottlerManagerTest, IsHostBeingRegistered) {
+ MockURLRequestThrottlerManager manager;
+
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0?code=1"));
+ manager.RegisterRequestUrl(GURL("http://www.google.com/index/0#lolsaure"));
+
+ EXPECT_EQ(3, manager.GetNumberOfEntries());
+}
+
+void ExpectEntryAllowsAllOnErrorIfOptedOut(
+ net::URLRequestThrottlerEntryInterface* entry,
+ bool opted_out,
+ const URLRequest& request) {
+ EXPECT_FALSE(entry->ShouldRejectRequest(request));
+ MockURLRequestThrottlerHeaderAdapter failure_adapter(503);
+ for (int i = 0; i < 10; ++i) {
+ // Host doesn't really matter in this scenario so we skip it.
+ entry->UpdateWithResponse("", &failure_adapter);
+ }
+ EXPECT_NE(opted_out, entry->ShouldRejectRequest(request));
+
+ if (opted_out) {
+ // We're not mocking out GetTimeNow() in this scenario
+ // so add a 100 ms buffer to avoid flakiness (that should always
+ // give enough time to get from the TimeTicks::Now() call here
+ // to the TimeTicks::Now() call in the entry class).
+ EXPECT_GT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ entry->GetExponentialBackoffReleaseTime());
+ } else {
+ // As above, add 100 ms.
+ EXPECT_LT(TimeTicks::Now() + TimeDelta::FromMilliseconds(100),
+ entry->GetExponentialBackoffReleaseTime());
+ }
+}
+
+TEST_F(URLRequestThrottlerManagerTest, OptOutHeader) {
+ MockURLRequestThrottlerManager manager;
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> entry =
+ manager.RegisterRequestUrl(GURL("http://www.google.com/yodude"));
+
+ // Fake a response with the opt-out header.
+ MockURLRequestThrottlerHeaderAdapter response_adapter(
+ "",
+ MockURLRequestThrottlerEntry::kExponentialThrottlingDisableValue,
+ 200);
+ entry->UpdateWithResponse("www.google.com", &response_adapter);
+
+ // Ensure that the same entry on error always allows everything.
+ ExpectEntryAllowsAllOnErrorIfOptedOut(entry, true, request_);
+
+ // Ensure that a freshly created entry (for a different URL on an
+ // already opted-out host) also gets "always allow" behavior.
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> other_entry =
+ manager.RegisterRequestUrl(GURL("http://www.google.com/bingobob"));
+ ExpectEntryAllowsAllOnErrorIfOptedOut(other_entry, true, request_);
+
+ // Fake a response with the opt-out header incorrectly specified.
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> no_opt_out_entry =
+ manager.RegisterRequestUrl(GURL("http://www.nike.com/justdoit"));
+ MockURLRequestThrottlerHeaderAdapter wrong_adapter("", "yesplease", 200);
+ no_opt_out_entry->UpdateWithResponse("www.nike.com", &wrong_adapter);
+ ExpectEntryAllowsAllOnErrorIfOptedOut(no_opt_out_entry, false, request_);
+
+ // A localhost entry should always be opted out.
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> localhost_entry =
+ manager.RegisterRequestUrl(GURL("http://localhost/hello"));
+ ExpectEntryAllowsAllOnErrorIfOptedOut(localhost_entry, true, request_);
+}
+
+TEST_F(URLRequestThrottlerManagerTest, ClearOnNetworkChange) {
+ for (int i = 0; i < 3; ++i) {
+ MockURLRequestThrottlerManager manager;
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> entry_before =
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ MockURLRequestThrottlerHeaderAdapter failure_adapter(503);
+ for (int j = 0; j < 10; ++j) {
+ // Host doesn't really matter in this scenario so we skip it.
+ entry_before->UpdateWithResponse("", &failure_adapter);
+ }
+ EXPECT_TRUE(entry_before->ShouldRejectRequest(request_));
+
+ switch (i) {
+ case 0:
+ manager.OnIPAddressChanged();
+ break;
+ case 1:
+ manager.OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::CONNECTION_UNKNOWN);
+ break;
+ case 2:
+ manager.OnConnectionTypeChanged(
+ net::NetworkChangeNotifier::CONNECTION_NONE);
+ break;
+ default:
+ FAIL();
+ }
+
+ scoped_refptr<net::URLRequestThrottlerEntryInterface> entry_after =
+ manager.RegisterRequestUrl(GURL("http://www.example.com/"));
+ EXPECT_FALSE(entry_after->ShouldRejectRequest(request_));
+ }
+}
+
+} // namespace net
diff --git a/src/net/url_request/url_request_unittest.cc b/src/net/url_request/url_request_unittest.cc
new file mode 100644
index 0000000..fb17ec9
--- /dev/null
+++ b/src/net/url_request/url_request_unittest.cc
@@ -0,0 +1,5197 @@
+// Copyright (c) 2012 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 "build/build_config.h"
+
+#if defined(OS_WIN)
+#include <windows.h>
+#include <shlobj.h>
+#endif
+
+#include <algorithm>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/bind.h"
+#include "base/compiler_specific.h"
+#include "base/file_util.h"
+#include "base/format_macros.h"
+#include "base/memory/weak_ptr.h"
+#include "base/message_loop.h"
+#include "base/path_service.h"
+#include "base/process_util.h"
+#include "base/string_number_conversions.h"
+#include "base/string_piece.h"
+#include "base/string_split.h"
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/utf_string_conversions.h"
+#include "net/base/cert_test_util.h"
+#include "net/base/ev_root_ca_metadata.h"
+#include "net/base/load_flags.h"
+#include "net/base/mock_host_resolver.h"
+#include "net/base/net_errors.h"
+#include "net/base/net_log.h"
+#include "net/base/net_log_unittest.h"
+#include "net/base/net_module.h"
+#include "net/base/net_util.h"
+#include "net/base/ssl_connection_status_flags.h"
+#include "net/base/test_data_directory.h"
+#include "net/base/test_root_certs.h"
+#include "net/base/upload_bytes_element_reader.h"
+#include "net/base/upload_data_stream.h"
+#include "net/base/upload_file_element_reader.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store_test_helpers.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/ftp/ftp_network_layer.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_network_layer.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_response_headers.h"
+#include "net/ocsp/nss_ocsp.h"
+#include "net/proxy/proxy_service.h"
+#include "net/socket/ssl_client_socket.h"
+#include "net/test/test_server.h"
+#include "net/url_request/ftp_protocol_handler.h"
+#include "net/url_request/static_http_user_agent_settings.h"
+#include "net/url_request/url_request.h"
+#include "net/url_request/url_request_file_dir_job.h"
+#include "net/url_request/url_request_http_job.h"
+#include "net/url_request/url_request_job_factory_impl.h"
+#include "net/url_request/url_request_redirect_job.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"
+#include "testing/platform_test.h"
+
+#if defined(OS_WIN)
+#include "base/win/scoped_com_initializer.h"
+#include "base/win/windows_version.h"
+#endif
+
+using base::Time;
+
+namespace net {
+
+namespace {
+
+const string16 kChrome(ASCIIToUTF16("chrome"));
+const string16 kSecret(ASCIIToUTF16("secret"));
+const string16 kUser(ASCIIToUTF16("user"));
+
+base::StringPiece TestNetResourceProvider(int key) {
+ return "header";
+}
+
+// Do a case-insensitive search through |haystack| for |needle|.
+bool ContainsString(const std::string& haystack, const char* needle) {
+ std::string::const_iterator it =
+ std::search(haystack.begin(),
+ haystack.end(),
+ needle,
+ needle + strlen(needle),
+ base::CaseInsensitiveCompare<char>());
+ return it != haystack.end();
+}
+
+void FillBuffer(char* buffer, size_t len) {
+ static bool called = false;
+ if (!called) {
+ called = true;
+ int seed = static_cast<int>(Time::Now().ToInternalValue());
+ srand(seed);
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ buffer[i] = static_cast<char>(rand());
+ if (!buffer[i])
+ buffer[i] = 'g';
+ }
+}
+
+UploadDataStream* CreateSimpleUploadData(const char* data) {
+ scoped_ptr<UploadElementReader> reader(
+ new UploadBytesElementReader(data, strlen(data)));
+ return UploadDataStream::CreateWithReader(reader.Pass(), 0);
+}
+
+// Verify that the SSLInfo of a successful SSL connection has valid values.
+void CheckSSLInfo(const SSLInfo& ssl_info) {
+ // Allow ChromeFrame fake SSLInfo to get through.
+ if (ssl_info.cert.get() &&
+ ssl_info.cert.get()->issuer().GetDisplayName() == "Chrome Internal") {
+ // -1 means unknown.
+ EXPECT_EQ(ssl_info.security_bits, -1);
+ return;
+ }
+
+ // -1 means unknown. 0 means no encryption.
+ EXPECT_GT(ssl_info.security_bits, 0);
+
+ // The cipher suite TLS_NULL_WITH_NULL_NULL (0) must not be negotiated.
+ int cipher_suite = SSLConnectionStatusToCipherSuite(
+ ssl_info.connection_status);
+ EXPECT_NE(0, cipher_suite);
+}
+
+bool FingerprintsEqual(const HashValueVector& a, const HashValueVector& b) {
+ size_t size = a.size();
+
+ if (size != b.size())
+ return false;
+
+ for (size_t i = 0; i < size; ++i) {
+ if (!a[i].Equals(b[i]))
+ return false;
+ }
+
+ return true;
+}
+
+// A network delegate that allows the user to choose a subset of request stages
+// to block in. When blocking, the delegate can do one of the following:
+// * synchronously return a pre-specified error code, or
+// * asynchronously return that value via an automatically called callback,
+// or
+// * block and wait for the user to do a callback.
+// Additionally, the user may also specify a redirect URL -- then each request
+// with the current URL different from the redirect target will be redirected
+// to that target, in the on-before-URL-request stage, independent of whether
+// the delegate blocks in ON_BEFORE_URL_REQUEST or not.
+class BlockingNetworkDelegate : public TestNetworkDelegate {
+ public:
+ // Stages in which the delegate can block.
+ enum Stage {
+ NOT_BLOCKED = 0,
+ ON_BEFORE_URL_REQUEST = 1 << 0,
+ ON_BEFORE_SEND_HEADERS = 1 << 1,
+ ON_HEADERS_RECEIVED = 1 << 2,
+ ON_AUTH_REQUIRED = 1 << 3
+ };
+
+ // Behavior during blocked stages. During other stages, just
+ // returns net::OK or NetworkDelegate::AUTH_REQUIRED_RESPONSE_NO_ACTION.
+ enum BlockMode {
+ SYNCHRONOUS, // No callback, returns specified return values.
+ AUTO_CALLBACK, // |this| posts a task to run the callback using the
+ // specified return codes.
+ USER_CALLBACK, // User takes care of doing a callback. |retval_| and
+ // |auth_retval_| are ignored. In every blocking stage the
+ // message loop is quit.
+ };
+
+ // Creates a delegate which does not block at all.
+ explicit BlockingNetworkDelegate(BlockMode block_mode);
+
+ // For users to trigger a callback returning |response|.
+ // Side-effects: resets |stage_blocked_for_callback_| and stored callbacks.
+ // Only call if |block_mode_| == USER_CALLBACK.
+ void DoCallback(int response);
+ void DoAuthCallback(NetworkDelegate::AuthRequiredResponse response);
+
+ // Setters.
+ void set_retval(int retval) {
+ ASSERT_NE(USER_CALLBACK, block_mode_);
+ ASSERT_NE(ERR_IO_PENDING, retval);
+ ASSERT_NE(OK, retval);
+ retval_ = retval;
+ }
+
+ // If |auth_retval| == AUTH_REQUIRED_RESPONSE_SET_AUTH, then
+ // |auth_credentials_| will be passed with the response.
+ void set_auth_retval(AuthRequiredResponse auth_retval) {
+ ASSERT_NE(USER_CALLBACK, block_mode_);
+ ASSERT_NE(AUTH_REQUIRED_RESPONSE_IO_PENDING, auth_retval);
+ auth_retval_ = auth_retval;
+ }
+ void set_auth_credentials(const AuthCredentials& auth_credentials) {
+ auth_credentials_ = auth_credentials;
+ }
+
+ void set_redirect_url(const GURL& url) {
+ redirect_url_ = url;
+ }
+
+ void set_block_on(int block_on) {
+ block_on_ = block_on;
+ }
+
+ // Allows the user to check in which state did we block.
+ Stage stage_blocked_for_callback() const {
+ EXPECT_EQ(USER_CALLBACK, block_mode_);
+ return stage_blocked_for_callback_;
+ }
+
+ private:
+ void RunCallback(int response, const CompletionCallback& callback);
+ void RunAuthCallback(AuthRequiredResponse response,
+ const AuthCallback& callback);
+
+ // TestNetworkDelegate implementation.
+ virtual int OnBeforeURLRequest(URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) OVERRIDE;
+
+ virtual int OnBeforeSendHeaders(URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) OVERRIDE;
+
+ virtual int OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) OVERRIDE;
+
+ virtual NetworkDelegate::AuthRequiredResponse OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) OVERRIDE;
+
+ // Resets the callbacks and |stage_blocked_for_callback_|.
+ void Reset();
+
+ // Checks whether we should block in |stage|. If yes, returns an error code
+ // and optionally sets up callback based on |block_mode_|. If no, returns OK.
+ int MaybeBlockStage(Stage stage, const CompletionCallback& callback);
+
+ // Configuration parameters, can be adjusted by public methods:
+ const BlockMode block_mode_;
+
+ // Values returned on blocking stages when mode is SYNCHRONOUS or
+ // AUTO_CALLBACK. For USER_CALLBACK these are set automatically to IO_PENDING.
+ int retval_; // To be returned in non-auth stages.
+ AuthRequiredResponse auth_retval_;
+
+ GURL redirect_url_; // Used if non-empty.
+ int block_on_; // Bit mask: in which stages to block.
+
+ // |auth_credentials_| will be copied to |*target_auth_credential_| on
+ // callback.
+ AuthCredentials auth_credentials_;
+ AuthCredentials* target_auth_credentials_;
+
+ // Internal variables, not set by not the user:
+ // Last blocked stage waiting for user callback (unused if |block_mode_| !=
+ // USER_CALLBACK).
+ Stage stage_blocked_for_callback_;
+
+ // Callback objects stored during blocking stages.
+ CompletionCallback callback_;
+ AuthCallback auth_callback_;
+
+ base::WeakPtrFactory<BlockingNetworkDelegate> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(BlockingNetworkDelegate);
+};
+
+BlockingNetworkDelegate::BlockingNetworkDelegate(BlockMode block_mode)
+ : block_mode_(block_mode),
+ retval_(OK),
+ auth_retval_(AUTH_REQUIRED_RESPONSE_NO_ACTION),
+ block_on_(0),
+ target_auth_credentials_(NULL),
+ stage_blocked_for_callback_(NOT_BLOCKED),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+void BlockingNetworkDelegate::DoCallback(int response) {
+ ASSERT_EQ(USER_CALLBACK, block_mode_);
+ ASSERT_NE(NOT_BLOCKED, stage_blocked_for_callback_);
+ ASSERT_NE(ON_AUTH_REQUIRED, stage_blocked_for_callback_);
+ CompletionCallback callback = callback_;
+ Reset();
+ RunCallback(response, callback);
+}
+
+void BlockingNetworkDelegate::DoAuthCallback(
+ NetworkDelegate::AuthRequiredResponse response) {
+ ASSERT_EQ(USER_CALLBACK, block_mode_);
+ ASSERT_EQ(ON_AUTH_REQUIRED, stage_blocked_for_callback_);
+ AuthCallback auth_callback = auth_callback_;
+ Reset();
+ RunAuthCallback(response, auth_callback);
+}
+
+void BlockingNetworkDelegate::RunCallback(int response,
+ const CompletionCallback& callback) {
+ callback.Run(response);
+}
+
+void BlockingNetworkDelegate::RunAuthCallback(AuthRequiredResponse response,
+ const AuthCallback& callback) {
+ if (auth_retval_ == AUTH_REQUIRED_RESPONSE_SET_AUTH) {
+ ASSERT_TRUE(target_auth_credentials_ != NULL);
+ *target_auth_credentials_ = auth_credentials_;
+ }
+ callback.Run(response);
+}
+
+int BlockingNetworkDelegate::OnBeforeURLRequest(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ GURL* new_url) {
+ if (redirect_url_ == request->url())
+ return OK; // We've already seen this request and redirected elsewhere.
+
+ TestNetworkDelegate::OnBeforeURLRequest(request, callback, new_url);
+
+ if (!redirect_url_.is_empty())
+ *new_url = redirect_url_;
+
+ return MaybeBlockStage(ON_BEFORE_URL_REQUEST, callback);
+}
+
+int BlockingNetworkDelegate::OnBeforeSendHeaders(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ HttpRequestHeaders* headers) {
+ TestNetworkDelegate::OnBeforeSendHeaders(request, callback, headers);
+
+ return MaybeBlockStage(ON_BEFORE_SEND_HEADERS, callback);
+}
+
+int BlockingNetworkDelegate::OnHeadersReceived(
+ URLRequest* request,
+ const CompletionCallback& callback,
+ const HttpResponseHeaders* original_response_headers,
+ scoped_refptr<HttpResponseHeaders>* override_response_headers) {
+ TestNetworkDelegate::OnHeadersReceived(
+ request, callback, original_response_headers,
+ override_response_headers);
+
+ return MaybeBlockStage(ON_HEADERS_RECEIVED, callback);
+}
+
+NetworkDelegate::AuthRequiredResponse BlockingNetworkDelegate::OnAuthRequired(
+ URLRequest* request,
+ const AuthChallengeInfo& auth_info,
+ const AuthCallback& callback,
+ AuthCredentials* credentials) {
+ TestNetworkDelegate::OnAuthRequired(request, auth_info, callback,
+ credentials);
+ // Check that the user has provided callback for the previous blocked stage.
+ EXPECT_EQ(NOT_BLOCKED, stage_blocked_for_callback_);
+
+ if ((block_on_ & ON_AUTH_REQUIRED) == 0) {
+ return AUTH_REQUIRED_RESPONSE_NO_ACTION;
+ }
+
+ target_auth_credentials_ = credentials;
+
+ switch (block_mode_) {
+ case SYNCHRONOUS:
+ if (auth_retval_ == AUTH_REQUIRED_RESPONSE_SET_AUTH)
+ *target_auth_credentials_ = auth_credentials_;
+ return auth_retval_;
+
+ case AUTO_CALLBACK:
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlockingNetworkDelegate::RunAuthCallback,
+ weak_factory_.GetWeakPtr(), auth_retval_, callback));
+ return AUTH_REQUIRED_RESPONSE_IO_PENDING;
+
+ case USER_CALLBACK:
+ auth_callback_ = callback;
+ stage_blocked_for_callback_ = ON_AUTH_REQUIRED;
+ MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ return AUTH_REQUIRED_RESPONSE_IO_PENDING;
+ }
+ NOTREACHED();
+ return AUTH_REQUIRED_RESPONSE_NO_ACTION; // Dummy value.
+}
+
+void BlockingNetworkDelegate::Reset() {
+ EXPECT_NE(NOT_BLOCKED, stage_blocked_for_callback_);
+ stage_blocked_for_callback_ = NOT_BLOCKED;
+ callback_.Reset();
+ auth_callback_.Reset();
+}
+
+int BlockingNetworkDelegate::MaybeBlockStage(
+ BlockingNetworkDelegate::Stage stage,
+ const CompletionCallback& callback) {
+ // Check that the user has provided callback for the previous blocked stage.
+ EXPECT_EQ(NOT_BLOCKED, stage_blocked_for_callback_);
+
+ if ((block_on_ & stage) == 0) {
+ return OK;
+ }
+
+ switch (block_mode_) {
+ case SYNCHRONOUS:
+ EXPECT_NE(OK, retval_);
+ return retval_;
+
+ case AUTO_CALLBACK:
+ MessageLoop::current()->PostTask(
+ FROM_HERE,
+ base::Bind(&BlockingNetworkDelegate::RunCallback,
+ weak_factory_.GetWeakPtr(), retval_, callback));
+ return ERR_IO_PENDING;
+
+ case USER_CALLBACK:
+ callback_ = callback;
+ stage_blocked_for_callback_ = stage;
+ MessageLoop::current()->PostTask(FROM_HERE, MessageLoop::QuitClosure());
+ return ERR_IO_PENDING;
+ }
+ NOTREACHED();
+ return 0;
+}
+
+class TestURLRequestContextWithProxy : public TestURLRequestContext {
+ public:
+ // Does not own |delegate|.
+ TestURLRequestContextWithProxy(const std::string& proxy,
+ NetworkDelegate* delegate)
+ : TestURLRequestContext(true) {
+ context_storage_.set_proxy_service(ProxyService::CreateFixed(proxy));
+ set_network_delegate(delegate);
+ Init();
+ }
+ virtual ~TestURLRequestContextWithProxy() {}
+};
+
+} // namespace
+
+// Inherit PlatformTest since we require the autorelease pool on Mac OS X.
+class URLRequestTest : public PlatformTest {
+ public:
+ URLRequestTest() : default_context_(true) {
+ default_context_.set_network_delegate(&default_network_delegate_);
+ default_context_.Init();
+ }
+
+ // Adds the TestJobInterceptor to the default context.
+ TestJobInterceptor* AddTestInterceptor() {
+ TestJobInterceptor* interceptor = new TestJobInterceptor();
+ default_context_.set_job_factory(&job_factory_);
+ job_factory_.AddInterceptor(interceptor);
+ return interceptor;
+ }
+
+ protected:
+ TestNetworkDelegate default_network_delegate_; // Must outlive URLRequest.
+ URLRequestJobFactoryImpl job_factory_;
+ TestURLRequestContext default_context_;
+};
+
+TEST_F(URLRequestTest, AboutBlankTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("about:blank"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ EXPECT_EQ("", r.GetSocketAddress().host());
+ EXPECT_EQ(0, r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(URLRequestTest, DataURLImageTest) {
+ TestDelegate d;
+ {
+ // Use our nice little Chrome logo.
+ URLRequest r(GURL(
+ "data:image/png;base64,"
+ "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAADVklEQVQ4jX2TfUwUBBjG3"
+ "w1y+HGcd9dxhXR8T4awOccJGgOSWclHImznLkTlSw0DDQXkrmgYgbUYnlQTqQxIEVxitD"
+ "5UMCATRA1CEEg+Qjw3bWDxIauJv/5oumqs39/P827vnucRmYN0gyF01GI5MpCVdW0gO7t"
+ "vNC+vqSEtbZefk5NuLv1jdJ46p/zw0HeH4+PHr3h7c1mjoV2t5rKzMx1+fg9bAgK6zHq9"
+ "cU5z+LpA3xOtx34+vTeT21onRuzssC3zxbbSwC13d/pFuC7CkIMDxQpF7r/MWq12UctI1"
+ "dWWm99ypqSYmRUBdKem8MkrO/kgaTt1O7YzlpzE5GIVd0WYUqt57yWf2McHTObYPbVD+Z"
+ "wbtlLTVMZ3BW+TnLyXLaWtmEq6WJVbT3HBh3Svj2HQQcm43XwmtoYM6vVKleh0uoWvnzW"
+ "3v3MpidruPTQPf0bia7sJOtBM0ufTWNvus/nkDFHF9ZS+uYVjRUasMeHUmyLYtcklTvzW"
+ "GFZnNOXczThvpKIzjcahSqIzkvDLayDq6D3eOjtBbNUEIZYyqsvj4V4wY92eNJ4IoyhTb"
+ "xXX1T5xsV9tm9r4TQwHLiZw/pdDZJea8TKmsmR/K0uLh/GwnCHghTja6lPhphezPfO5/5"
+ "MrVvMzNaI3+ERHfrFzPKQukrQGI4d/3EFD/3E2mVNYvi4at7CXWREaxZGD+3hg28zD3gV"
+ "Md6q5c8GdosynKmSeRuGzpjyl1/9UDGtPR5HeaKT8Wjo17WXk579BXVUhN64ehF9fhRtq"
+ "/uxxZKzNiZFGD0wRC3NFROZ5mwIPL/96K/rKMMLrIzF9uhHr+/sYH7DAbwlgC4J+R2Z7F"
+ "Ux1qLnV7MGF40smVSoJ/jvHRfYhQeUJd/SnYtGWhPHR0Sz+GE2F2yth0B36Vcz2KpnufB"
+ "JbsysjjW4kblBUiIjiURUWqJY65zxbnTy57GQyH58zgy0QBtTQv5gH15XMdKkYu+TGaJM"
+ "nlm2O34uI4b9tflqp1+QEFGzoW/ulmcofcpkZCYJhDfSpme7QcrHa+Xfji8paEQkTkSfm"
+ "moRWRNZr/F1KfVMjW+IKEnv2FwZfKdzt0BQR6lClcZR0EfEXEfv/G6W9iLiIyCoReV5En"
+ "hORIBHx+ufPj/gLB/zGI/G4Bk0AAAAASUVORK5CYII="),
+ &d,
+ &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 911);
+ EXPECT_EQ("", r.GetSocketAddress().host());
+ EXPECT_EQ(0, r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(URLRequestTest, FileTest) {
+ FilePath app_path;
+ PathService::Get(base::FILE_EXE, &app_path);
+ GURL app_url = FilePathToFileURL(app_path);
+
+ TestDelegate d;
+ {
+ URLRequest r(app_url, &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = -1;
+ EXPECT_TRUE(file_util::GetFileSize(app_path, &file_size));
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ EXPECT_EQ("", r.GetSocketAddress().host());
+ EXPECT_EQ(0, r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(URLRequestTest, FileTestCancel) {
+ FilePath app_path;
+ PathService::Get(base::FILE_EXE, &app_path);
+ GURL app_url = FilePathToFileURL(app_path);
+
+ TestDelegate d;
+ {
+ URLRequest r(app_url, &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+ r.Cancel();
+ }
+ // Async cancelation should be safe even when URLRequest has been already
+ // destroyed.
+ MessageLoop::current()->RunUntilIdle();
+}
+
+TEST_F(URLRequestTest, FileTestFullSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ scoped_array<char> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+
+ FilePath temp_path;
+ EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_path));
+ GURL temp_url = FilePathToFileURL(temp_path);
+ EXPECT_TRUE(file_util::WriteFile(temp_path, buffer.get(), buffer_size));
+
+ int64 file_size;
+ EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size));
+
+ const size_t first_byte_position = 500;
+ const size_t last_byte_position = buffer_size - first_byte_position;
+ const size_t content_length = last_byte_position - first_byte_position + 1;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + last_byte_position + 1);
+
+ TestDelegate d;
+ {
+ URLRequest r(temp_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kRange,
+ base::StringPrintf(
+ "bytes=%" PRIuS "-%" PRIuS,
+ first_byte_position, last_byte_position));
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(static_cast<int>(content_length), d.bytes_received());
+ // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
+ EXPECT_TRUE(partial_buffer_string == d.data_received());
+ }
+
+ EXPECT_TRUE(file_util::Delete(temp_path, false));
+}
+
+TEST_F(URLRequestTest, FileTestHalfSpecifiedRange) {
+ const size_t buffer_size = 4000;
+ scoped_array<char> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+
+ FilePath temp_path;
+ EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_path));
+ GURL temp_url = FilePathToFileURL(temp_path);
+ EXPECT_TRUE(file_util::WriteFile(temp_path, buffer.get(), buffer_size));
+
+ int64 file_size;
+ EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size));
+
+ const size_t first_byte_position = 500;
+ const size_t last_byte_position = buffer_size - 1;
+ const size_t content_length = last_byte_position - first_byte_position + 1;
+ std::string partial_buffer_string(buffer.get() + first_byte_position,
+ buffer.get() + last_byte_position + 1);
+
+ TestDelegate d;
+ {
+ URLRequest r(temp_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kRange,
+ base::StringPrintf("bytes=%" PRIuS "-",
+ first_byte_position));
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(static_cast<int>(content_length), d.bytes_received());
+ // Don't use EXPECT_EQ, it will print out a lot of garbage if check failed.
+ EXPECT_TRUE(partial_buffer_string == d.data_received());
+ }
+
+ EXPECT_TRUE(file_util::Delete(temp_path, false));
+}
+
+TEST_F(URLRequestTest, FileTestMultipleRanges) {
+ const size_t buffer_size = 400000;
+ scoped_array<char> buffer(new char[buffer_size]);
+ FillBuffer(buffer.get(), buffer_size);
+
+ FilePath temp_path;
+ EXPECT_TRUE(file_util::CreateTemporaryFile(&temp_path));
+ GURL temp_url = FilePathToFileURL(temp_path);
+ EXPECT_TRUE(file_util::WriteFile(temp_path, buffer.get(), buffer_size));
+
+ int64 file_size;
+ EXPECT_TRUE(file_util::GetFileSize(temp_path, &file_size));
+
+ TestDelegate d;
+ {
+ URLRequest r(temp_url, &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kRange,
+ "bytes=0-0,10-200,200-300");
+ r.SetExtraRequestHeaders(headers);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(d.request_failed());
+ }
+
+ EXPECT_TRUE(file_util::Delete(temp_path, false));
+}
+
+TEST_F(URLRequestTest, InvalidUrlTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("invalid url"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(d.request_failed());
+ }
+}
+
+#if defined(OS_WIN)
+TEST_F(URLRequestTest, ResolveShortcutTest) {
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("net");
+ app_path = app_path.AppendASCII("data");
+ app_path = app_path.AppendASCII("url_request_unittest");
+ app_path = app_path.AppendASCII("with-headers.html");
+
+ std::wstring lnk_path = app_path.value() + L".lnk";
+
+ base::win::ScopedCOMInitializer com_initializer;
+
+ // Temporarily create a shortcut for test
+ IShellLink* shell = NULL;
+ ASSERT_TRUE(SUCCEEDED(CoCreateInstance(
+ CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink,
+ reinterpret_cast<LPVOID*>(&shell))));
+ IPersistFile* persist = NULL;
+ ASSERT_TRUE(SUCCEEDED(shell->QueryInterface(
+ IID_IPersistFile, reinterpret_cast<LPVOID*>(&persist))));
+ EXPECT_TRUE(SUCCEEDED(shell->SetPath(app_path.value().c_str())));
+ EXPECT_TRUE(SUCCEEDED(shell->SetDescription(L"ResolveShortcutTest")));
+ EXPECT_TRUE(SUCCEEDED(persist->Save(lnk_path.c_str(), TRUE)));
+ if (persist)
+ persist->Release();
+ if (shell)
+ shell->Release();
+
+ TestDelegate d;
+ {
+ URLRequest r(FilePathToFileURL(FilePath(lnk_path)), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ WIN32_FILE_ATTRIBUTE_DATA data;
+ GetFileAttributesEx(app_path.value().c_str(),
+ GetFileExInfoStandard, &data);
+ HANDLE file = CreateFile(app_path.value().c_str(), GENERIC_READ,
+ FILE_SHARE_READ, NULL, OPEN_EXISTING,
+ FILE_ATTRIBUTE_NORMAL, NULL);
+ EXPECT_NE(INVALID_HANDLE_VALUE, file);
+ scoped_array<char> buffer(new char[data.nFileSizeLow]);
+ DWORD read_size;
+ BOOL result;
+ result = ReadFile(file, buffer.get(), data.nFileSizeLow,
+ &read_size, NULL);
+ std::string content(buffer.get(), read_size);
+ CloseHandle(file);
+
+ EXPECT_TRUE(!r.is_pending());
+ EXPECT_EQ(1, d.received_redirect_count());
+ EXPECT_EQ(content, d.data_received());
+ }
+
+ // Clean the shortcut
+ DeleteFile(lnk_path.c_str());
+}
+#endif // defined(OS_WIN)
+
+TEST_F(URLRequestTest, FileDirCancelTest) {
+ // Put in mock resource provider.
+ NetModule::SetResourceProvider(TestNetResourceProvider);
+
+ TestDelegate d;
+ {
+ FilePath file_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &file_path);
+ file_path = file_path.Append(FILE_PATH_LITERAL("net"));
+ file_path = file_path.Append(FILE_PATH_LITERAL("data"));
+
+ URLRequest req(FilePathToFileURL(file_path), &d, &default_context_);
+ req.Start();
+ EXPECT_TRUE(req.is_pending());
+
+ d.set_cancel_in_received_data_pending(true);
+
+ MessageLoop::current()->Run();
+ }
+
+ // Take out mock resource provider.
+ NetModule::SetResourceProvider(NULL);
+}
+
+TEST_F(URLRequestTest, FileDirRedirectNoCrash) {
+ // There is an implicit redirect when loading a file path that matches a
+ // directory and does not end with a slash. Ensure that following such
+ // redirects does not crash. See http://crbug.com/18686.
+
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+
+ TestDelegate d;
+ URLRequest req(FilePathToFileURL(path), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.received_redirect_count());
+ ASSERT_LT(0, d.bytes_received());
+ ASSERT_FALSE(d.request_failed());
+ ASSERT_TRUE(req.status().is_success());
+}
+
+#if defined(OS_WIN)
+// Don't accept the url "file:///" on windows. See http://crbug.com/1474.
+TEST_F(URLRequestTest, FileDirRedirectSingleSlash) {
+ TestDelegate d;
+ URLRequest req(GURL("file:///"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.received_redirect_count());
+ ASSERT_FALSE(req.status().is_success());
+}
+#endif
+
+// Custom URLRequestJobs for use with interceptor tests
+class RestartTestJob : public URLRequestTestJob {
+ public:
+ RestartTestJob(URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestTestJob(request, network_delegate, true) {}
+ protected:
+ virtual void StartAsync() {
+ this->NotifyRestartRequired();
+ }
+ private:
+ ~RestartTestJob() {}
+};
+
+class CancelTestJob : public URLRequestTestJob {
+ public:
+ explicit CancelTestJob(URLRequest* request, NetworkDelegate* network_delegate)
+ : URLRequestTestJob(request, network_delegate, true) {}
+ protected:
+ virtual void StartAsync() {
+ request_->Cancel();
+ }
+ private:
+ ~CancelTestJob() {}
+};
+
+class CancelThenRestartTestJob : public URLRequestTestJob {
+ public:
+ explicit CancelThenRestartTestJob(URLRequest* request,
+ NetworkDelegate* network_delegate)
+ : URLRequestTestJob(request, network_delegate, true) {
+ }
+ protected:
+ virtual void StartAsync() {
+ request_->Cancel();
+ this->NotifyRestartRequired();
+ }
+ private:
+ ~CancelThenRestartTestJob() {}
+};
+
+// An Interceptor for use with interceptor tests
+class TestInterceptor : URLRequest::Interceptor {
+ public:
+ TestInterceptor()
+ : intercept_main_request_(false), restart_main_request_(false),
+ cancel_main_request_(false), cancel_then_restart_main_request_(false),
+ simulate_main_network_error_(false),
+ intercept_redirect_(false), cancel_redirect_request_(false),
+ intercept_final_response_(false), cancel_final_request_(false),
+ did_intercept_main_(false), did_restart_main_(false),
+ did_cancel_main_(false), did_cancel_then_restart_main_(false),
+ did_simulate_error_main_(false),
+ did_intercept_redirect_(false), did_cancel_redirect_(false),
+ did_intercept_final_(false), did_cancel_final_(false) {
+ URLRequest::Deprecated::RegisterRequestInterceptor(this);
+ }
+
+ ~TestInterceptor() {
+ URLRequest::Deprecated::UnregisterRequestInterceptor(this);
+ }
+
+ virtual URLRequestJob* MaybeIntercept(URLRequest* request,
+ NetworkDelegate* network_delegate) {
+ if (restart_main_request_) {
+ restart_main_request_ = false;
+ did_restart_main_ = true;
+ return new RestartTestJob(request, network_delegate);
+ }
+ if (cancel_main_request_) {
+ cancel_main_request_ = false;
+ did_cancel_main_ = true;
+ return new CancelTestJob(request, network_delegate);
+ }
+ if (cancel_then_restart_main_request_) {
+ cancel_then_restart_main_request_ = false;
+ did_cancel_then_restart_main_ = true;
+ return new CancelThenRestartTestJob(request, network_delegate);
+ }
+ if (simulate_main_network_error_) {
+ simulate_main_network_error_ = false;
+ did_simulate_error_main_ = true;
+ // will error since the requeted url is not one of its canned urls
+ return new URLRequestTestJob(request, network_delegate, true);
+ }
+ if (!intercept_main_request_)
+ return NULL;
+ intercept_main_request_ = false;
+ did_intercept_main_ = true;
+ return new URLRequestTestJob(request,
+ network_delegate,
+ main_headers_,
+ main_data_,
+ true);
+ }
+
+ virtual URLRequestJob* MaybeInterceptRedirect(
+ URLRequest* request,
+ NetworkDelegate* network_delegate,
+ const GURL& location) {
+ if (cancel_redirect_request_) {
+ cancel_redirect_request_ = false;
+ did_cancel_redirect_ = true;
+ return new CancelTestJob(request, network_delegate);
+ }
+ if (!intercept_redirect_)
+ return NULL;
+ intercept_redirect_ = false;
+ did_intercept_redirect_ = true;
+ return new URLRequestTestJob(request,
+ network_delegate,
+ redirect_headers_,
+ redirect_data_,
+ true);
+ }
+
+ virtual URLRequestJob* MaybeInterceptResponse(
+ URLRequest* request, NetworkDelegate* network_delegate) {
+ if (cancel_final_request_) {
+ cancel_final_request_ = false;
+ did_cancel_final_ = true;
+ return new CancelTestJob(request, network_delegate);
+ }
+ if (!intercept_final_response_)
+ return NULL;
+ intercept_final_response_ = false;
+ did_intercept_final_ = true;
+ return new URLRequestTestJob(request,
+ network_delegate,
+ final_headers_,
+ final_data_,
+ true);
+ }
+
+ // Whether to intercept the main request, and if so the response to return.
+ bool intercept_main_request_;
+ std::string main_headers_;
+ std::string main_data_;
+
+ // Other actions we take at MaybeIntercept time
+ bool restart_main_request_;
+ bool cancel_main_request_;
+ bool cancel_then_restart_main_request_;
+ bool simulate_main_network_error_;
+
+ // Whether to intercept redirects, and if so the response to return.
+ bool intercept_redirect_;
+ std::string redirect_headers_;
+ std::string redirect_data_;
+
+ // Other actions we can take at MaybeInterceptRedirect time
+ bool cancel_redirect_request_;
+
+ // Whether to intercept final response, and if so the response to return.
+ bool intercept_final_response_;
+ std::string final_headers_;
+ std::string final_data_;
+
+ // Other actions we can take at MaybeInterceptResponse time
+ bool cancel_final_request_;
+
+ // If we did something or not
+ bool did_intercept_main_;
+ bool did_restart_main_;
+ bool did_cancel_main_;
+ bool did_cancel_then_restart_main_;
+ bool did_simulate_error_main_;
+ bool did_intercept_redirect_;
+ bool did_cancel_redirect_;
+ bool did_intercept_final_;
+ bool did_cancel_final_;
+
+ // Static getters for canned response header and data strings
+
+ static std::string ok_data() {
+ return URLRequestTestJob::test_data_1();
+ }
+
+ static std::string ok_headers() {
+ return URLRequestTestJob::test_headers();
+ }
+
+ static std::string redirect_data() {
+ return std::string();
+ }
+
+ static std::string redirect_headers() {
+ return URLRequestTestJob::test_redirect_headers();
+ }
+
+ static std::string error_data() {
+ return std::string("ohhh nooooo mr. bill!");
+ }
+
+ static std::string error_headers() {
+ return URLRequestTestJob::test_error_headers();
+ }
+};
+
+TEST_F(URLRequestTest, Intercept) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a simple response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::ok_headers();
+ interceptor.main_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ base::SupportsUserData::Data* user_data0 = new base::SupportsUserData::Data();
+ base::SupportsUserData::Data* user_data1 = new base::SupportsUserData::Data();
+ base::SupportsUserData::Data* user_data2 = new base::SupportsUserData::Data();
+ req.SetUserData(NULL, user_data0);
+ req.SetUserData(&user_data1, user_data1);
+ req.SetUserData(&user_data2, user_data2);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Make sure we can retrieve our specific user data
+ EXPECT_EQ(user_data0, req.GetUserData(NULL));
+ EXPECT_EQ(user_data1, req.GetUserData(&user_data1));
+ EXPECT_EQ(user_data2, req.GetUserData(&user_data2));
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRedirect) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a redirect
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::redirect_headers();
+ interceptor.main_data_ = TestInterceptor::redirect_data();
+
+ // intercept that redirect and respond a final OK response
+ interceptor.intercept_redirect_ = true;
+ interceptor.redirect_headers_ = TestInterceptor::ok_headers();
+ interceptor.redirect_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_intercept_redirect_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ if (req.status().is_success()) {
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ }
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptServerError) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to generate a server error response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::error_headers();
+ interceptor.main_data_ = TestInterceptor::error_data();
+
+ // intercept that error and respond with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_intercept_final_);
+
+ // Check we got one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptNetworkError) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to simulate a network error
+ interceptor.simulate_main_network_error_ = true;
+
+ // intercept that error and respond with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_simulate_error_main_);
+ EXPECT_TRUE(interceptor.did_intercept_final_);
+
+ // Check we received one good response
+ EXPECT_TRUE(req.status().is_success());
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRestartRequired) {
+ TestInterceptor interceptor;
+
+ // restart the main request
+ interceptor.restart_main_request_ = true;
+
+ // then intercept the new main request and respond with an OK response
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::ok_headers();
+ interceptor.main_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_restart_main_);
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+
+ // Check we received one good response
+ EXPECT_TRUE(req.status().is_success());
+ if (req.status().is_success()) {
+ EXPECT_EQ(200, req.response_headers()->response_code());
+ }
+ EXPECT_EQ(TestInterceptor::ok_data(), d.data_received());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.received_redirect_count());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelMain) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and cancel from within the restarted job
+ interceptor.cancel_main_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_cancel_main_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelRedirect) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and respond with a redirect
+ interceptor.intercept_main_request_ = true;
+ interceptor.main_headers_ = TestInterceptor::redirect_headers();
+ interceptor.main_data_ = TestInterceptor::redirect_data();
+
+ // intercept the redirect and cancel from within that job
+ interceptor.cancel_redirect_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_intercept_main_);
+ EXPECT_TRUE(interceptor.did_cancel_redirect_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelFinal) {
+ TestInterceptor interceptor;
+
+ // intercept the main request to simulate a network error
+ interceptor.simulate_main_network_error_ = true;
+
+ // setup to intercept final response and cancel from within that job
+ interceptor.cancel_final_request_ = true;
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_simulate_error_main_);
+ EXPECT_TRUE(interceptor.did_cancel_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+TEST_F(URLRequestTest, InterceptRespectsCancelInRestart) {
+ TestInterceptor interceptor;
+
+ // intercept the main request and cancel then restart from within that job
+ interceptor.cancel_then_restart_main_request_ = true;
+
+ // setup to intercept final response and override it with an OK response
+ interceptor.intercept_final_response_ = true;
+ interceptor.final_headers_ = TestInterceptor::ok_headers();
+ interceptor.final_data_ = TestInterceptor::ok_data();
+
+ TestDelegate d;
+ URLRequest req(GURL("http://test_intercept/foo"), &d, &default_context_);
+ req.set_method("GET");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check the interceptor got called as expected
+ EXPECT_TRUE(interceptor.did_cancel_then_restart_main_);
+ EXPECT_FALSE(interceptor.did_intercept_final_);
+
+ // Check we see a canceled request
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+}
+
+// Check that two different URL requests have different identifiers.
+TEST_F(URLRequestTest, Identifiers) {
+ TestDelegate d;
+ TestURLRequestContext context;
+ TestURLRequest req(GURL("http://example.com"), &d, &context);
+ TestURLRequest other_req(GURL("http://example.com"), &d, &context);
+
+ ASSERT_NE(req.identifier(), other_req.identifier());
+}
+
+// Check that a failure to connect to the proxy is reported to the network
+// delegate.
+TEST_F(URLRequestTest, NetworkDelegateProxyError) {
+ MockHostResolver host_resolver;
+ host_resolver.rules()->AddSimulatedFailure("*");
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContextWithProxy context("myproxy:70", &network_delegate);
+
+ TestDelegate d;
+ URLRequest req(GURL("http://example.com"), &d, &context);
+ req.set_method("GET");
+
+ req.Start();
+ MessageLoop::current()->Run();
+
+ // Check we see a failed request.
+ EXPECT_FALSE(req.status().is_success());
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, req.status().error());
+
+ EXPECT_EQ(1, network_delegate.error_count());
+ EXPECT_EQ(ERR_PROXY_CONNECTION_FAILED, network_delegate.last_error());
+ EXPECT_EQ(1, network_delegate.completed_requests());
+}
+
+// Make sure that net::NetworkDelegate::NotifyCompleted is called if
+// content is empty.
+TEST_F(URLRequestTest, RequestCompletionForEmptyResponse) {
+ TestDelegate d;
+ URLRequest req(GURL("data:,"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("", d.data_received());
+ EXPECT_EQ(1, default_network_delegate_.completed_requests());
+}
+
+// TODO(droger): Support TestServer on iOS (see http://crbug.com/148666).
+#if !defined(OS_IOS)
+// A subclass of TestServer that uses a statically-configured hostname. This is
+// to work around mysterious failures in chrome_frame_net_tests. See:
+// http://crbug.com/114369
+class LocalHttpTestServer : public TestServer {
+ public:
+ explicit LocalHttpTestServer(const FilePath& document_root)
+ : TestServer(TestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ document_root) {}
+ LocalHttpTestServer()
+ : TestServer(TestServer::TYPE_HTTP,
+ ScopedCustomUrlRequestTestHttpHost::value(),
+ FilePath()) {}
+};
+
+TEST_F(URLRequestTest, FLAKY_DelayedCookieCallback) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ TestURLRequestContext context;
+ scoped_refptr<DelayedCookieMonster> delayed_cm =
+ new DelayedCookieMonster();
+ scoped_refptr<CookieStore> cookie_store = delayed_cm;
+ context.set_cookie_store(delayed_cm);
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ context.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSend=1"), &d, &context);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(1, network_delegate.set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ context.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("echoheader?Cookie"), &d, &context);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSendCookies) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotSend=1"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie isn't sent when LOAD_DO_NOT_SEND_COOKIES is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.set_load_flags(LOAD_DO_NOT_SEND_COOKIES);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
+ == std::string::npos);
+
+ // LOAD_DO_NOT_SEND_COOKIES does not trigger OnGetCookies.
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveCookies) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotUpdate=2"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(1, network_delegate.set_cookie_count());
+ }
+
+ // Try to set-up another cookie and update the previous cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSave=1&CookieToNotUpdate=1"),
+ &d,
+ &default_context_);
+ req.set_load_flags(LOAD_DO_NOT_SAVE_COOKIES);
+ req.Start();
+
+ MessageLoop::current()->Run();
+
+ // LOAD_DO_NOT_SAVE_COOKIES does not trigger OnSetCookie.
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(0, network_delegate.set_cookie_count());
+ }
+
+ // Verify the cookies weren't saved or updated.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSave=1")
+ == std::string::npos);
+ EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(0, network_delegate.set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSendCookies_ViaPolicy) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotSend=1"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie isn't sent.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_GET_COOKIES);
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
+ == std::string::npos);
+
+ EXPECT_EQ(1, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveCookies_ViaPolicy) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotUpdate=2"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Try to set-up another cookie and update the previous cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_SET_COOKIE);
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSave=1&CookieToNotUpdate=1"),
+ &d,
+ &default_context_);
+ req.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(2, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify the cookies weren't saved or updated.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSave=1")
+ == std::string::npos);
+ EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveEmptyCookies) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up an empty cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ EXPECT_EQ(0, network_delegate.set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSendCookies_ViaPolicy_Async) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotSend=1"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSend=1")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify that the cookie isn't sent.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_GET_COOKIES);
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("Cookie: CookieToNotSend=1")
+ == std::string::npos);
+
+ EXPECT_EQ(1, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+TEST_F(URLRequestTest, DoNotSaveCookies_ViaPolicy_Async) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up a cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL("set-cookie?CookieToNotUpdate=2"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Try to set-up another cookie and update the previous cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ network_delegate.set_cookie_options(TestNetworkDelegate::NO_SET_COOKIE);
+ URLRequest req(
+ test_server.GetURL("set-cookie?CookieToNotSave=1&CookieToNotUpdate=1"),
+ &d,
+ &default_context_);
+ req.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(2, network_delegate.blocked_set_cookie_count());
+ }
+
+ // Verify the cookies weren't saved or updated.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("CookieToNotSave=1")
+ == std::string::npos);
+ EXPECT_TRUE(d.data_received().find("CookieToNotUpdate=2")
+ != std::string::npos);
+
+ EXPECT_EQ(0, network_delegate.blocked_get_cookies_count());
+ EXPECT_EQ(0, network_delegate.blocked_set_cookie_count());
+ }
+}
+
+// FixedDateNetworkDelegate swaps out the server's HTTP Date response header
+// value for the |fixed_date| argument given to the constructor.
+class FixedDateNetworkDelegate : public TestNetworkDelegate {
+ public:
+ explicit FixedDateNetworkDelegate(const std::string& fixed_date)
+ : fixed_date_(fixed_date) {}
+ virtual ~FixedDateNetworkDelegate() {}
+
+ // 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)
+ OVERRIDE;
+
+ private:
+ std::string fixed_date_;
+
+ DISALLOW_COPY_AND_ASSIGN(FixedDateNetworkDelegate);
+};
+
+int FixedDateNetworkDelegate::OnHeadersReceived(
+ net::URLRequest* request,
+ const net::CompletionCallback& callback,
+ const net::HttpResponseHeaders* original_response_headers,
+ scoped_refptr<net::HttpResponseHeaders>* override_response_headers) {
+ net::HttpResponseHeaders* new_response_headers =
+ new net::HttpResponseHeaders(original_response_headers->raw_headers());
+
+ new_response_headers->RemoveHeader("Date");
+ new_response_headers->AddHeader("Date: " + fixed_date_);
+
+ *override_response_headers = new_response_headers;
+ return TestNetworkDelegate::OnHeadersReceived(request,
+ callback,
+ original_response_headers,
+ override_response_headers);
+}
+
+// Test that cookie expiration times are adjusted for server/client clock
+// skew and that we handle incorrect timezone specifier "UTC" in HTTP Date
+// headers by defaulting to GMT. (crbug.com/135131)
+TEST_F(URLRequestTest, AcceptClockSkewCookieWithWrongDateTimezone) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // Set up an expired cookie.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL(
+ "set-cookie?StillGood=1;expires=Mon,18-Apr-1977,22:50:13,GMT"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+ }
+ // Verify that the cookie is not set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("StillGood=1") == std::string::npos);
+ }
+ // Set up a cookie with clock skew and "UTC" HTTP Date timezone specifier.
+ {
+ FixedDateNetworkDelegate network_delegate("18-Apr-1977 22:49:13 UTC");
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(test_server.GetURL(
+ "set-cookie?StillGood=1;expires=Mon,18-Apr-1977,22:50:13,GMT"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+ }
+ // Verify that the cookie is set.
+ {
+ TestNetworkDelegate network_delegate;
+ default_context_.set_network_delegate(&network_delegate);
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Cookie"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("StillGood=1") != std::string::npos);
+ }
+}
+
+
+// Check that it is impossible to change the referrer in the extra headers of
+// an URLRequest.
+TEST_F(URLRequestTest, DoNotOverrideReferrer) {
+ LocalHttpTestServer test_server;
+ ASSERT_TRUE(test_server.Start());
+
+ // If extra headers contain referer and the request contains a referer,
+ // only the latter shall be respected.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Referer"), &d, &default_context_);
+ req.set_referrer("http://foo.com/");
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kReferer, "http://bar.com/");
+ req.SetExtraRequestHeaders(headers);
+
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ("http://foo.com/", d.data_received());
+ }
+
+ // If extra headers contain a referer but the request does not, no referer
+ // shall be sent in the header.
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server.GetURL("echoheader?Referer"), &d, &default_context_);
+
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kReferer, "http://bar.com/");
+ req.SetExtraRequestHeaders(headers);
+ req.set_load_flags(LOAD_VALIDATE_CACHE);
+
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ("None", d.data_received());
+ }
+}
+
+class URLRequestTestHTTP : public URLRequestTest {
+ public:
+ URLRequestTestHTTP()
+ : test_server_(FilePath(FILE_PATH_LITERAL(
+ "net/data/url_request_unittest"))) {
+ }
+
+ protected:
+ // Requests |redirect_url|, which must return a HTTP 3xx redirect.
+ // |request_method| is the method to use for the initial request.
+ // |redirect_method| is the method that is expected to be used for the second
+ // request, after redirection.
+ // If |include_data| is true, data is uploaded with the request. The
+ // response body is expected to match it exactly, if and only if
+ // |request_method| == |redirect_method|.
+ void HTTPRedirectMethodTest(const GURL& redirect_url,
+ const std::string& request_method,
+ const std::string& redirect_method,
+ bool include_data) {
+ static const char kData[] = "hello world";
+ TestDelegate d;
+ URLRequest req(redirect_url, &d, &default_context_);
+ req.set_method(request_method);
+ if (include_data) {
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
+ }
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(redirect_method, req.method());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req.status().status());
+ EXPECT_EQ(OK, req.status().error());
+ if (include_data) {
+ if (request_method == redirect_method) {
+ EXPECT_EQ(kData, d.data_received());
+ } else {
+ EXPECT_NE(kData, d.data_received());
+ }
+ }
+ if (HasFailure())
+ LOG(WARNING) << "Request method was: " << request_method;
+ }
+
+ void HTTPUploadDataOperationTest(const std::string& method) {
+ const int kMsgSize = 20000; // multiple of 10
+ const int kIterations = 50;
+ char* uploadBytes = new char[kMsgSize+1];
+ char* ptr = uploadBytes;
+ char marker = 'a';
+ for (int idx = 0; idx < kMsgSize/10; idx++) {
+ memcpy(ptr, "----------", 10);
+ ptr += 10;
+ if (idx % 100 == 0) {
+ ptr--;
+ *ptr++ = marker;
+ if (++marker > 'z')
+ marker = 'a';
+ }
+ }
+ uploadBytes[kMsgSize] = '\0';
+
+ for (int i = 0; i < kIterations; ++i) {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.set_method(method.c_str());
+
+ r.set_upload(make_scoped_ptr(CreateSimpleUploadData(uploadBytes)));
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.response_started_count())
+ << "request failed: " << r.status().status()
+ << ", os error: " << r.status().error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(uploadBytes, d.data_received());
+ }
+ delete[] uploadBytes;
+ }
+
+ void AddChunksToUpload(URLRequest* r) {
+ r->AppendChunkToUpload("a", 1, false);
+ r->AppendChunkToUpload("bcd", 3, false);
+ r->AppendChunkToUpload("this is a longer chunk than before.", 35, false);
+ r->AppendChunkToUpload("\r\n\r\n", 4, false);
+ r->AppendChunkToUpload("0", 1, false);
+ r->AppendChunkToUpload("2323", 4, true);
+ }
+
+ void VerifyReceivedDataMatchesChunks(URLRequest* r, TestDelegate* d) {
+ // This should match the chunks sent by AddChunksToUpload().
+ const std::string expected_data =
+ "abcdthis is a longer chunk than before.\r\n\r\n02323";
+
+ ASSERT_EQ(1, d->response_started_count())
+ << "request failed: " << r->status().status()
+ << ", os error: " << r->status().error();
+
+ EXPECT_FALSE(d->received_data_before_response());
+
+ EXPECT_EQ(expected_data.size(), static_cast<size_t>(d->bytes_received()));
+ EXPECT_EQ(expected_data, d->data_received());
+ }
+
+ bool DoManyCookiesRequest(int num_cookies) {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("set-many-cookies?" +
+ base::IntToString(num_cookies)),
+ &d,
+ &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ bool is_success = r.status().is_success();
+
+ if (!is_success) {
+ // Requests handled by ChromeFrame send a less precise error message,
+ // ERR_CONNECTION_ABORTED.
+ EXPECT_TRUE(r.status().error() == ERR_RESPONSE_HEADERS_TOO_BIG ||
+ r.status().error() == ERR_CONNECTION_ABORTED);
+ // The test server appears to be unable to handle subsequent requests
+ // after this error is triggered. Force it to restart.
+ EXPECT_TRUE(test_server_.Stop());
+ EXPECT_TRUE(test_server_.Start());
+ }
+
+ return is_success;
+ }
+
+ LocalHttpTestServer test_server_;
+};
+
+// 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
+// follow.
+// flaky: crbug.com/96594
+TEST_F(URLRequestTestHTTP, FLAKY_ProxyTunnelRedirectTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ TestDelegate d;
+ {
+ URLRequest r(GURL("https://www.redirect.com/"), &d, &context);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, r.status().error());
+ EXPECT_EQ(1, d.response_started_count());
+ // We should not have followed the redirect.
+ EXPECT_EQ(0, d.received_redirect_count());
+ }
+}
+
+// This is the same as the previous test, but checks that the network delegate
+// registers the error.
+// This test was disabled because it made chrome_frame_net_tests hang
+// (see bug 102991).
+TEST_F(URLRequestTestHTTP, DISABLED_NetworkDelegateTunnelConnectionFailed) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ TestDelegate d;
+ {
+ URLRequest r(GURL("https://www.redirect.com/"), &d, &context);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, r.status().error());
+ EXPECT_EQ(1, d.response_started_count());
+ // We should not have followed the redirect.
+ EXPECT_EQ(0, d.received_redirect_count());
+
+ EXPECT_EQ(1, network_delegate.error_count());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, network_delegate.last_error());
+ }
+}
+
+// Tests that we can block and asynchronously return OK in various stages.
+TEST_F(URLRequestTestHTTP, NetworkDelegateBlockAsynchronously) {
+ static const BlockingNetworkDelegate::Stage blocking_stages[] = {
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED
+ };
+ static const size_t blocking_stages_length = arraysize(blocking_stages);
+
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST |
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS |
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL("empty.html"), &d, &context);
+
+ r.Start();
+ for (size_t i = 0; i < blocking_stages_length; ++i) {
+ MessageLoop::current()->Run();
+ EXPECT_EQ(blocking_stages[i],
+ network_delegate.stage_blocked_for_callback());
+ network_delegate.DoCallback(OK);
+ }
+ MessageLoop::current()->Run();
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can block and cancel a request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+ network_delegate.set_retval(ERR_EMPTY_RESPONSE);
+
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_EMPTY_RESPONSE, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Helper function for NetworkDelegateCancelRequestAsynchronously and
+// NetworkDelegateCancelRequestSynchronously. Sets up a blocking network
+// delegate operating in |block_mode| and a request for |url|. It blocks the
+// request in |stage| and cancels it with ERR_BLOCKED_BY_CLIENT.
+void NetworkDelegateCancelRequest(BlockingNetworkDelegate::BlockMode block_mode,
+ BlockingNetworkDelegate::Stage stage,
+ const GURL& url) {
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(block_mode);
+ network_delegate.set_retval(ERR_BLOCKED_BY_CLIENT);
+ network_delegate.set_block_on(stage);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(url, &d, &context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_BLOCKED_BY_CLIENT, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// The following 3 tests check that the network delegate can cancel a request
+// synchronously in various stages of the request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestSynchronously1) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::SYNCHRONOUS,
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ test_server_.GetURL(""));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestSynchronously2) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::SYNCHRONOUS,
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ test_server_.GetURL(""));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestSynchronously3) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::SYNCHRONOUS,
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED,
+ test_server_.GetURL(""));
+}
+
+// The following 3 tests check that the network delegate can cancel a request
+// asynchronously in various stages of the request.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestAsynchronously1) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::AUTO_CALLBACK,
+ BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ test_server_.GetURL(""));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestAsynchronously2) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::AUTO_CALLBACK,
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ test_server_.GetURL(""));
+}
+
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelRequestAsynchronously3) {
+ ASSERT_TRUE(test_server_.Start());
+ NetworkDelegateCancelRequest(BlockingNetworkDelegate::AUTO_CALLBACK,
+ BlockingNetworkDelegate::ON_HEADERS_RECEIVED,
+ test_server_.GetURL(""));
+}
+
+// Tests that the network delegate can block and redirect a request to a new
+// URL.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+ GURL redirect_url(test_server_.GetURL("simple.html"));
+ network_delegate.set_redirect_url(redirect_url);
+
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ {
+ GURL original_url(test_server_.GetURL("empty.html"));
+ URLRequest r(original_url, &d, &context);
+
+ r.Start();
+ MessageLoop::current()->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(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can block and redirect a request to a new
+// URL by setting a redirect_url and returning in OnBeforeURLRequest directly.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequestSynchronously) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ GURL redirect_url(test_server_.GetURL("simple.html"));
+ network_delegate.set_redirect_url(redirect_url);
+
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ {
+ GURL original_url(test_server_.GetURL("empty.html"));
+ URLRequest r(original_url, &d, &context);
+
+ r.Start();
+ MessageLoop::current()->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(1, 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 preserve POST data.
+TEST_F(URLRequestTestHTTP, NetworkDelegateRedirectRequestPost) {
+ 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_BEFORE_URL_REQUEST);
+ GURL redirect_url(test_server_.GetURL("echo"));
+ network_delegate.set_redirect_url(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, &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();
+ MessageLoop::current()->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(1, 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
+// URLRequest::Delegate.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredSyncNoAction) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_TRUE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can synchronously complete OnAuthRequired
+// by setting credentials.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredSyncSetAuth) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH);
+
+ network_delegate.set_auth_credentials(AuthCredentials(kUser, kSecret));
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can synchronously complete OnAuthRequired
+// by cancelling authentication.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredSyncCancel) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::SYNCHRONOUS);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(OK, r.status().error());
+ EXPECT_EQ(401, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can asynchronously 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
+// URLRequest::Delegate.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredAsyncNoAction) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_TRUE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can asynchronously complete OnAuthRequired
+// by setting credentials.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredAsyncSetAuth) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_SET_AUTH);
+
+ AuthCredentials auth_credentials(kUser, kSecret);
+ network_delegate.set_auth_credentials(auth_credentials);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(0, r.status().error());
+
+ EXPECT_EQ(200, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that the network delegate can asynchronously complete OnAuthRequired
+// by cancelling authentication.
+TEST_F(URLRequestTestHTTP, NetworkDelegateOnAuthRequiredAsyncCancel) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::AUTO_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+ network_delegate.set_auth_retval(
+ NetworkDelegate::AUTH_REQUIRED_RESPONSE_CANCEL_AUTH);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ GURL url(test_server_.GetURL("auth-basic"));
+ URLRequest r(url, &d, &context);
+ r.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ EXPECT_EQ(OK, r.status().error());
+ EXPECT_EQ(401, r.GetResponseCode());
+ EXPECT_FALSE(d.auth_required_called());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 1: Request is cancelled while waiting for OnBeforeURLRequest callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting1) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_BEFORE_URL_REQUEST,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 2: Request is cancelled while waiting for OnBeforeSendHeaders callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting2) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(
+ BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_BEFORE_SEND_HEADERS,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 3: Request is cancelled while waiting for OnHeadersReceived callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting3) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_HEADERS_RECEIVED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_HEADERS_RECEIVED,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// Tests that we can handle when a network request was canceled while we were
+// waiting for the network delegate.
+// Part 4: Request is cancelled while waiting for OnAuthRequired callback.
+TEST_F(URLRequestTestHTTP, NetworkDelegateCancelWhileWaiting4) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ BlockingNetworkDelegate network_delegate(
+ BlockingNetworkDelegate::USER_CALLBACK);
+ network_delegate.set_block_on(BlockingNetworkDelegate::ON_AUTH_REQUIRED);
+
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ {
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &context);
+
+ r.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(BlockingNetworkDelegate::ON_AUTH_REQUIRED,
+ network_delegate.stage_blocked_for_callback());
+ EXPECT_EQ(0, network_delegate.completed_requests());
+ // Cancel before callback.
+ r.Cancel();
+ // Ensure that network delegate is notified.
+ EXPECT_EQ(1, network_delegate.completed_requests());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(ERR_ABORTED, r.status().error());
+ EXPECT_EQ(1, network_delegate.created_requests());
+ EXPECT_EQ(0, network_delegate.destroyed_requests());
+ }
+ EXPECT_EQ(1, network_delegate.destroyed_requests());
+}
+
+// In this unit test, we're using the HTTPTestServer as a proxy server and
+// issuing a CONNECT request with the magic host name "www.server-auth.com".
+// The HTTPTestServer will return a 401 response, which we should balk at.
+TEST_F(URLRequestTestHTTP, UnexpectedServerAuthTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContextWithProxy context(
+ test_server_.host_port_pair().ToString(),
+ &network_delegate);
+
+ TestDelegate d;
+ {
+ URLRequest r(GURL("https://www.server-auth.com/"), &d, &context);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_TUNNEL_CONNECTION_FAILED, r.status().error());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, GetTest_NoCache) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+
+ // TODO(eroman): Add back the NetLog tests...
+ }
+}
+
+// This test has the server send a large number of cookies to the client.
+// To ensure that no number of cookies causes a crash, a galloping binary
+// search is used to estimate that maximum number of cookies that are accepted
+// by the browser. Beyond the maximum number, the request will fail with
+// ERR_RESPONSE_HEADERS_TOO_BIG.
+TEST_F(URLRequestTestHTTP, GetTest_ManyCookies) {
+ ASSERT_TRUE(test_server_.Start());
+
+ int lower_bound = 0;
+ int upper_bound = 1;
+
+ // Double the number of cookies until the response header limits are
+ // exceeded.
+ while (DoManyCookiesRequest(upper_bound)) {
+ lower_bound = upper_bound;
+ upper_bound *= 2;
+ ASSERT_LT(upper_bound, 1000000);
+ }
+
+ int tolerance = upper_bound * 0.005;
+ if (tolerance < 2)
+ tolerance = 2;
+
+ // Perform a binary search to find the highest possible number of cookies,
+ // within the desired tolerance.
+ while (upper_bound - lower_bound >= tolerance) {
+ int num_cookies = (lower_bound + upper_bound) / 2;
+
+ if (DoManyCookiesRequest(num_cookies))
+ lower_bound = num_cookies;
+ else
+ upper_bound = num_cookies;
+ }
+ // Success: the test did not crash.
+}
+
+TEST_F(URLRequestTestHTTP, GetTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, GetZippedTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // Parameter that specifies the Content-Length field in the response:
+ // C - Compressed length.
+ // U - Uncompressed length.
+ // L - Large length (larger than both C & U).
+ // M - Medium length (between C & U).
+ // S - Small length (smaller than both C & U).
+ const char test_parameters[] = "CULMS";
+ const int num_tests = arraysize(test_parameters)- 1; // Skip NULL.
+ // C & U should be OK.
+ // L & M are larger than the data sent, and show an error.
+ // S has too little data, but we seem to accept it.
+ const bool test_expect_success[num_tests] =
+ { true, true, false, false, true };
+
+ for (int i = 0; i < num_tests ; i++) {
+ TestDelegate d;
+ {
+ std::string test_file =
+ base::StringPrintf("compressedfiles/BullRunSpeech.txt?%c",
+ test_parameters[i]);
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ URLRequest r(test_server_.GetURL(test_file), &d, &context);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ VLOG(1) << " Received " << d.bytes_received() << " bytes"
+ << " status = " << r.status().status()
+ << " error = " << r.status().error();
+ if (test_expect_success[i]) {
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status())
+ << " Parameter = \"" << test_file << "\"";
+ } else {
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_CONTENT_LENGTH_MISMATCH, r.status().error())
+ << " Parameter = \"" << test_file << "\"";
+ }
+ }
+ }
+}
+
+// This test was disabled because it made chrome_frame_net_tests hang
+// (see bug 102991).
+TEST_F(URLRequestTestHTTP, DISABLED_HTTPSToHTTPRedirectNoRefererTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestServer https_test_server(TestServer::TYPE_HTTPS,
+ TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ // An https server is sent a request with an https referer,
+ // and responds with a redirect to an http url. The http
+ // server should not be sent the referer.
+ GURL http_destination = test_server_.GetURL("");
+ TestDelegate d;
+ URLRequest req(https_test_server.GetURL(
+ "server-redirect?" + http_destination.spec()), &d, &default_context_);
+ req.set_referrer("https://www.referrer.com/");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(1, d.received_redirect_count());
+ EXPECT_EQ(http_destination, req.url());
+ EXPECT_EQ(std::string(), req.referrer());
+}
+
+TEST_F(URLRequestTestHTTP, MultipleRedirectTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL("");
+ GURL middle_redirect_url = test_server_.GetURL(
+ "server-redirect?" + destination_url.spec());
+ GURL original_url = test_server_.GetURL(
+ "server-redirect?" + middle_redirect_url.spec());
+ TestDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(2, d.received_redirect_count());
+ EXPECT_EQ(destination_url, req.url());
+ EXPECT_EQ(original_url, req.original_url());
+ ASSERT_EQ(3U, req.url_chain().size());
+ EXPECT_EQ(original_url, req.url_chain()[0]);
+ EXPECT_EQ(middle_redirect_url, req.url_chain()[1]);
+ EXPECT_EQ(destination_url, req.url_chain()[2]);
+}
+
+namespace {
+
+const char kExtraHeader[] = "Allow-Snafu";
+const char kExtraValue[] = "fubar";
+
+class RedirectWithAdditionalHeadersDelegate : public TestDelegate {
+ void OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ TestDelegate::OnReceivedRedirect(request, new_url, defer_redirect);
+ request->SetExtraRequestHeaderByName(kExtraHeader, kExtraValue, false);
+ }
+};
+
+} // namespace
+
+TEST_F(URLRequestTestHTTP, RedirectWithAdditionalHeadersTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL(
+ "echoheader?" + std::string(kExtraHeader));
+ GURL original_url = test_server_.GetURL(
+ "server-redirect?" + destination_url.spec());
+ RedirectWithAdditionalHeadersDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ std::string value;
+ const HttpRequestHeaders& headers = req.extra_request_headers();
+ EXPECT_TRUE(headers.GetHeader(kExtraHeader, &value));
+ EXPECT_EQ(kExtraValue, value);
+ EXPECT_FALSE(req.is_pending());
+ EXPECT_FALSE(req.is_redirecting());
+ EXPECT_EQ(kExtraValue, d.data_received());
+}
+
+namespace {
+
+const char kExtraHeaderToRemove[] = "To-Be-Removed";
+
+class RedirectWithHeaderRemovalDelegate : public TestDelegate {
+ void OnReceivedRedirect(net::URLRequest* request,
+ const GURL& new_url,
+ bool* defer_redirect) {
+ TestDelegate::OnReceivedRedirect(request, new_url, defer_redirect);
+ request->RemoveRequestHeaderByName(kExtraHeaderToRemove);
+ }
+};
+
+} // namespace
+
+TEST_F(URLRequestTestHTTP, RedirectWithHeaderRemovalTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL destination_url = test_server_.GetURL(
+ "echoheader?" + std::string(kExtraHeaderToRemove));
+ GURL original_url = test_server_.GetURL(
+ "server-redirect?" + destination_url.spec());
+ RedirectWithHeaderRemovalDelegate d;
+ URLRequest req(original_url, &d, &default_context_);
+ req.SetExtraRequestHeaderByName(kExtraHeaderToRemove, "dummy", false);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ std::string value;
+ const HttpRequestHeaders& headers = req.extra_request_headers();
+ EXPECT_FALSE(headers.GetHeader(kExtraHeaderToRemove, &value));
+ EXPECT_FALSE(req.is_pending());
+ EXPECT_FALSE(req.is_redirecting());
+ EXPECT_EQ("None", d.data_received());
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest) {
+ TestDelegate d;
+ {
+ URLRequest r(GURL("http://www.google.com/"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ r.Cancel();
+
+ MessageLoop::current()->Run();
+
+ // We expect to receive OnResponseStarted even though the request has been
+ // cancelled.
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest2) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &default_context_);
+
+ d.set_cancel_in_response_started(true);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest3) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &default_context_);
+
+ d.set_cancel_in_received_data(true);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ // There is no guarantee about how much data was received
+ // before the cancel was issued. It could have been 0 bytes,
+ // or it could have been all the bytes.
+ // EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest4) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL(""), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ // The request will be implicitly canceled when it is destroyed. The
+ // test delegate must not post a quit message when this happens because
+ // this test doesn't actually have a message loop. The quit message would
+ // get put on this thread's message queue and the next test would exit
+ // early, causing problems.
+ d.set_quit_on_complete(false);
+ }
+ // expect things to just cleanup properly.
+
+ // we won't actually get a received reponse here because we've never run the
+ // message loop
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(0, d.bytes_received());
+}
+
+TEST_F(URLRequestTestHTTP, CancelTest5) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // populate cache
+ {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("cachetime"), &d, &default_context_);
+ r.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(URLRequestStatus::SUCCESS, r.status().status());
+ }
+
+ // cancel read from cache (see bug 990242)
+ {
+ TestDelegate d;
+ URLRequest r(test_server_.GetURL("cachetime"), &d, &default_context_);
+ r.Start();
+ r.Cancel();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::CANCELED, r.status().status());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, PostTest) {
+ ASSERT_TRUE(test_server_.Start());
+ HTTPUploadDataOperationTest("POST");
+}
+
+TEST_F(URLRequestTestHTTP, PutTest) {
+ ASSERT_TRUE(test_server_.Start());
+ HTTPUploadDataOperationTest("PUT");
+}
+
+TEST_F(URLRequestTestHTTP, PostEmptyTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.set_method("POST");
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ ASSERT_EQ(1, d.response_started_count())
+ << "request failed: " << r.status().status()
+ << ", error: " << r.status().error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.data_received().empty());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, PostFileTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.set_method("POST");
+
+#if !defined(OS_STARBOARD)
+ FilePath dir;
+ PathService::Get(base::DIR_EXE, &dir);
+ file_util::SetCurrentDirectory(dir);
+#endif
+
+ ScopedVector<UploadElementReader> element_readers;
+
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+ path = path.Append(FILE_PATH_LITERAL("with-headers.html"));
+ element_readers.push_back(new UploadFileElementReader(
+ path, 0, kuint64max, base::Time()));
+
+ // This file should just be ignored in the upload stream.
+ element_readers.push_back(new UploadFileElementReader(
+ FilePath(FILE_PATH_LITERAL(
+ "c:\\path\\to\\non\\existant\\file.randomness.12345")),
+ 0, kuint64max, base::Time()));
+ r.set_upload(make_scoped_ptr(new UploadDataStream(&element_readers, 0)));
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 size = 0;
+ ASSERT_EQ(true, file_util::GetFileSize(path, &size));
+ scoped_array<char> buf(new char[size]);
+
+ ASSERT_EQ(size, file_util::ReadFile(path, buf.get(), size));
+
+ ASSERT_EQ(1, d.response_started_count())
+ << "request failed: " << r.status().status()
+ << ", error: " << r.status().error();
+
+ EXPECT_FALSE(d.received_data_before_response());
+
+ EXPECT_EQ(size, d.bytes_received());
+ EXPECT_EQ(std::string(&buf[0], size), d.data_received());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, TestPostChunkedDataBeforeStart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.EnableChunkedUpload();
+ r.set_method("POST");
+ AddChunksToUpload(&r);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ VerifyReceivedDataMatchesChunks(&r, &d);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, TestPostChunkedDataJustAfterStart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.EnableChunkedUpload();
+ r.set_method("POST");
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+ AddChunksToUpload(&r);
+ MessageLoop::current()->Run();
+
+ VerifyReceivedDataMatchesChunks(&r, &d);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, TestPostChunkedDataAfterStart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("echo"), &d, &default_context_);
+ r.EnableChunkedUpload();
+ r.set_method("POST");
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->RunUntilIdle();
+ AddChunksToUpload(&r);
+ MessageLoop::current()->Run();
+
+ VerifyReceivedDataMatchesChunks(&r, &d);
+ }
+}
+
+TEST_F(URLRequestTestHTTP, ResponseHeadersTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("files/with-headers.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ const HttpResponseHeaders* headers = req.response_headers();
+
+ // Simple sanity check that response_info() accesses the same data.
+ EXPECT_EQ(headers, req.response_info().headers.get());
+
+ std::string header;
+ EXPECT_TRUE(headers->GetNormalizedHeader("cache-control", &header));
+ EXPECT_EQ("private", header);
+
+ header.clear();
+ EXPECT_TRUE(headers->GetNormalizedHeader("content-type", &header));
+ EXPECT_EQ("text/html; charset=ISO-8859-1", header);
+
+ // The response has two "X-Multiple-Entries" headers.
+ // This verfies our output has them concatenated together.
+ header.clear();
+ EXPECT_TRUE(headers->GetNormalizedHeader("x-multiple-entries", &header));
+ EXPECT_EQ("a, b", header);
+}
+
+TEST_F(URLRequestTestHTTP, ProcessSTS) {
+ TestServer::SSLOptions ssl_options;
+ TestServer https_test_server(
+ TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ MessageLoop::current()->Run();
+
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ TestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+ EXPECT_TRUE(domain_state.include_subdomains);
+}
+
+TEST_F(URLRequestTestHTTP, ProcessSTSOnce) {
+ TestServer::SSLOptions ssl_options;
+ TestServer https_test_server(
+ TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest")));
+ ASSERT_TRUE(https_test_server.Start());
+
+ TestDelegate d;
+ URLRequest request(
+ https_test_server.GetURL("files/hsts-multiple-headers.html"),
+ &d,
+ &default_context_);
+ request.Start();
+ MessageLoop::current()->Run();
+
+ // We should have set parameters from the first header, not the second.
+ TransportSecurityState* security_state =
+ default_context_.transport_security_state();
+ bool sni_available = true;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(security_state->GetDomainState(
+ TestServer::kLocalhost, sni_available, &domain_state));
+ EXPECT_EQ(TransportSecurityState::DomainState::MODE_FORCE_HTTPS,
+ domain_state.upgrade_mode);
+ EXPECT_FALSE(domain_state.include_subdomains);
+}
+
+TEST_F(URLRequestTestHTTP, ContentTypeNormalizationTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(
+ "files/content-type-normalization.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ std::string mime_type;
+ req.GetMimeType(&mime_type);
+ EXPECT_EQ("text/html", mime_type);
+
+ std::string charset;
+ req.GetCharset(&charset);
+ EXPECT_EQ("utf-8", charset);
+ req.Cancel();
+}
+
+TEST_F(URLRequestTestHTTP, RestrictRedirects) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(
+ "files/redirect-to-file.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(ERR_UNSAFE_REDIRECT, req.status().error());
+}
+
+TEST_F(URLRequestTestHTTP, RedirectToInvalidURL) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(
+ "files/redirect-to-invalid-url.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(URLRequestStatus::FAILED, req.status().status());
+ EXPECT_EQ(ERR_INVALID_URL, req.status().error());
+}
+
+TEST_F(URLRequestTestHTTP, NoUserPassInReferrer) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Referer"), &d, &default_context_);
+ req.set_referrer("http://user:pass@foo.com/");
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(std::string("http://foo.com/"), d.data_received());
+}
+
+TEST_F(URLRequestTestHTTP, CancelRedirect) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_cancel_in_received_redirect(true);
+ URLRequest req(
+ test_server_.GetURL("files/redirect-test.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, DeferredRedirect) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_quit_on_redirect(true);
+ URLRequest req(
+ test_server_.GetURL("files/redirect-test.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ req.FollowDeferredRedirect();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::SUCCESS, req.status().status());
+
+ FilePath path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ path = path.Append(FILE_PATH_LITERAL("net"));
+ path = path.Append(FILE_PATH_LITERAL("data"));
+ path = path.Append(FILE_PATH_LITERAL("url_request_unittest"));
+ path = path.Append(FILE_PATH_LITERAL("with-headers.html"));
+
+ std::string contents;
+ EXPECT_TRUE(file_util::ReadFileToString(path, &contents));
+ EXPECT_EQ(contents, d.data_received());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, CancelDeferredRedirect) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ d.set_quit_on_redirect(true);
+ URLRequest req(
+ test_server_.GetURL("files/redirect-test.html"), &d, &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.received_redirect_count());
+
+ req.Cancel();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_EQ(0, d.bytes_received());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(URLRequestStatus::CANCELED, req.status().status());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, VaryHeader) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // populate the cache
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheadercache?foo"), &d, &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader("foo", "1");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+ }
+
+ // expect a cache hit
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheadercache?foo"), &d, &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader("foo", "1");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(req.was_cached());
+ }
+
+ // expect a cache miss
+ {
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheadercache?foo"), &d, &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader("foo", "2");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(req.was_cached());
+ }
+}
+
+TEST_F(URLRequestTestHTTP, BasicAuth) {
+ ASSERT_TRUE(test_server_.Start());
+
+ // populate the cache
+ {
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &default_context_);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+ }
+
+ // repeat request with end-to-end validation. since auth-basic results in a
+ // cachable page, we expect this test to result in a 304. in which case, the
+ // response should be fetched from the cache.
+ {
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(test_server_.GetURL("auth-basic"), &d, &default_context_);
+ r.set_load_flags(LOAD_VALIDATE_CACHE);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Should be the same cached document.
+ EXPECT_TRUE(r.was_cached());
+ }
+}
+
+// Check that Set-Cookie headers in 401 responses are respected.
+// http://crbug.com/6450
+TEST_F(URLRequestTestHTTP, BasicAuthWithCookies) {
+ ASSERT_TRUE(test_server_.Start());
+
+ GURL url_requiring_auth =
+ test_server_.GetURL("auth-basic?set-cookie-if-challenged");
+
+ // Request a page that will give a 401 containing a Set-Cookie header.
+ // Verify that when the transaction is restarted, it includes the new cookie.
+ {
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+ d.set_credentials(AuthCredentials(kUser, kSecret));
+
+ URLRequest r(url_requiring_auth, &d, &context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user/secret") != std::string::npos);
+
+ // Make sure we sent the cookie in the restarted transaction.
+ EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true")
+ != std::string::npos);
+ }
+
+ // Same test as above, except this time the restart is initiated earlier
+ // (without user intervention since identity is embedded in the URL).
+ {
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+
+ GURL::Replacements replacements;
+ std::string username("user2");
+ std::string password("secret");
+ replacements.SetUsernameStr(username);
+ replacements.SetPasswordStr(password);
+ GURL url_with_identity = url_requiring_auth.ReplaceComponents(replacements);
+
+ URLRequest r(url_with_identity, &d, &context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_TRUE(d.data_received().find("user2/secret") != std::string::npos);
+
+ // Make sure we sent the cookie in the restarted transaction.
+ EXPECT_TRUE(d.data_received().find("Cookie: got_challenged=true")
+ != std::string::npos);
+ }
+}
+
+// In this test, we do a POST which the server will 302 redirect.
+// The subsequent transaction should use GET, and should not send the
+// Content-Type header.
+// http://code.google.com/p/chromium/issues/detail?id=843
+TEST_F(URLRequestTestHTTP, Post302RedirectGet) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("files/redirect-to-echoall"), &d, &default_context_);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+
+ // Set headers (some of which are specific to the POST).
+ HttpRequestHeaders headers;
+ headers.AddHeadersFromString(
+ "Content-Type: multipart/form-data; "
+ "boundary=----WebKitFormBoundaryAADeAA+NAAWMAAwZ\r\n"
+ "Accept: text/xml,application/xml,application/xhtml+xml,text/html;q=0.9,"
+ "text/plain;q=0.8,image/png,*/*;q=0.5\r\n"
+ "Accept-Language: en-US,en\r\n"
+ "Accept-Charset: ISO-8859-1,*,utf-8\r\n"
+ "Content-Length: 11\r\n"
+ "Origin: http://localhost:1337/");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+
+ std::string mime_type;
+ req.GetMimeType(&mime_type);
+ EXPECT_EQ("text/html", mime_type);
+
+ const std::string& data = d.data_received();
+
+ // Check that the post-specific headers were stripped:
+ EXPECT_FALSE(ContainsString(data, "Content-Length:"));
+ EXPECT_FALSE(ContainsString(data, "Content-Type:"));
+ EXPECT_FALSE(ContainsString(data, "Origin:"));
+
+ // These extra request headers should not have been stripped.
+ EXPECT_TRUE(ContainsString(data, "Accept:"));
+ EXPECT_TRUE(ContainsString(data, "Accept-Language:"));
+ EXPECT_TRUE(ContainsString(data, "Accept-Charset:"));
+}
+
+// The following tests check that we handle mutating the request method for
+// HTTP redirects as expected.
+// See http://crbug.com/56373 and http://crbug.com/102130.
+
+TEST_F(URLRequestTestHTTP, Redirect301Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect301-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "GET", true);
+ HTTPRedirectMethodTest(url, "PUT", "PUT", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, Redirect302Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect302-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "GET", true);
+ HTTPRedirectMethodTest(url, "PUT", "PUT", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, Redirect303Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect303-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "GET", true);
+ HTTPRedirectMethodTest(url, "PUT", "GET", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, Redirect307Tests) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const GURL url = test_server_.GetURL("files/redirect307-to-echo");
+
+ HTTPRedirectMethodTest(url, "POST", "POST", true);
+ HTTPRedirectMethodTest(url, "PUT", "PUT", true);
+ HTTPRedirectMethodTest(url, "HEAD", "HEAD", false);
+}
+
+TEST_F(URLRequestTestHTTP, InterceptPost302RedirectGet) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("empty.html"), &d, &default_context_);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
+
+ URLRequestRedirectJob* job = new URLRequestRedirectJob(
+ &req, default_context_.network_delegate(), test_server_.GetURL("echo"),
+ URLRequestRedirectJob::REDIRECT_302_FOUND);
+ AddTestInterceptor()->set_main_intercept_job(job);
+
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("GET", req.method());
+}
+
+TEST_F(URLRequestTestHTTP, InterceptPost307RedirectPost) {
+ ASSERT_TRUE(test_server_.Start());
+
+ const char kData[] = "hello world";
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("empty.html"), &d, &default_context_);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kContentLength,
+ base::UintToString(arraysize(kData) - 1));
+ req.SetExtraRequestHeaders(headers);
+
+ URLRequestRedirectJob* job = new URLRequestRedirectJob(
+ &req, default_context_.network_delegate(), test_server_.GetURL("echo"),
+ URLRequestRedirectJob::REDIRECT_307_TEMPORARY_REDIRECT);
+ AddTestInterceptor()->set_main_intercept_job(job);
+
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("POST", req.method());
+ EXPECT_EQ(kData, d.data_received());
+}
+
+// Check that default A-L header is sent.
+TEST_F(URLRequestTestHTTP, DefaultAcceptLanguage) {
+ ASSERT_TRUE(test_server_.Start());
+
+ StaticHttpUserAgentSettings settings("en", EmptyString(), EmptyString());
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_http_user_agent_settings(&settings);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Accept-Language"), &d, &context);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("en", d.data_received());
+}
+
+// Check that an empty A-L header is not sent. http://crbug.com/77365.
+TEST_F(URLRequestTestHTTP, EmptyAcceptLanguage) {
+ ASSERT_TRUE(test_server_.Start());
+
+ StaticHttpUserAgentSettings settings(
+ EmptyString(), EmptyString(), EmptyString());
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+ // We override the language after initialization because empty entries
+ // get overridden by Init().
+ context.set_http_user_agent_settings(&settings);
+
+ TestDelegate d;
+ URLRequest req(
+ test_server_.GetURL("echoheader?Accept-Language"), &d, &context);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("None", d.data_received());
+}
+
+// Check that if request overrides the A-L header, the default is not appended.
+// See http://crbug.com/20894
+TEST_F(URLRequestTestHTTP, OverrideAcceptLanguage) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Language"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kAcceptLanguage, "ru");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(std::string("ru"), d.data_received());
+}
+
+// Check that default A-E header is sent.
+TEST_F(URLRequestTestHTTP, DefaultAcceptEncoding) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Encoding"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_TRUE(ContainsString(d.data_received(), "gzip"));
+}
+
+// Check that if request overrides the A-E header, the default is not appended.
+// See http://crbug.com/47381
+TEST_F(URLRequestTestHTTP, OverrideAcceptEncoding) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Encoding"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kAcceptEncoding, "identity");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_FALSE(ContainsString(d.data_received(), "gzip"));
+ EXPECT_TRUE(ContainsString(d.data_received(), "identity"));
+}
+
+// Check that default A-C header is sent.
+TEST_F(URLRequestTestHTTP, DefaultAcceptCharset) {
+ ASSERT_TRUE(test_server_.Start());
+
+ StaticHttpUserAgentSettings settings(EmptyString(), "en", EmptyString());
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_http_user_agent_settings(&settings);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Charset"),
+ &d,
+ &context);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("en", d.data_received());
+}
+
+// Check that an empty A-C header is not sent. http://crbug.com/77365.
+TEST_F(URLRequestTestHTTP, EmptyAcceptCharset) {
+ ASSERT_TRUE(test_server_.Start());
+
+ StaticHttpUserAgentSettings settings(
+ EmptyString(), EmptyString(), EmptyString());
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+ // We override the accepted charset after initialization because empty
+ // entries get overridden otherwise.
+ context.set_http_user_agent_settings(&settings);
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Charset"),
+ &d,
+ &context);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ("None", d.data_received());
+}
+
+// Check that if request overrides the A-C header, the default is not appended.
+// See http://crbug.com/20894
+TEST_F(URLRequestTestHTTP, OverrideAcceptCharset) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?Accept-Charset"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kAcceptCharset, "koi-8r");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(std::string("koi-8r"), d.data_received());
+}
+
+// Check that default User-Agent header is sent.
+TEST_F(URLRequestTestHTTP, DefaultUserAgent) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?User-Agent"),
+ &d,
+ &default_context_);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(req.context()->GetUserAgent(req.url()), d.data_received());
+}
+
+// Check that if request overrides the User-Agent header,
+// the default is not appended.
+TEST_F(URLRequestTestHTTP, OverrideUserAgent) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL("echoheader?User-Agent"),
+ &d,
+ &default_context_);
+ HttpRequestHeaders headers;
+ headers.SetHeader(HttpRequestHeaders::kUserAgent, "Lynx (textmode)");
+ req.SetExtraRequestHeaders(headers);
+ req.Start();
+ MessageLoop::current()->Run();
+ // If the net tests are being run with ChromeFrame then we need to allow for
+ // the 'chromeframe' suffix which is added to the user agent before the
+ // closing parentheses.
+ EXPECT_TRUE(StartsWithASCII(d.data_received(), "Lynx (textmode", true));
+}
+
+// Check that a NULL HttpUserAgentSettings causes the corresponding empty
+// User-Agent header to be sent but does not send the Accept-Language and
+// Accept-Charset headers.
+TEST_F(URLRequestTestHTTP, EmptyHttpUserAgentSettings) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequests.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+ // We override the HttpUserAgentSettings after initialization because empty
+ // entries get overridden by Init().
+ context.set_http_user_agent_settings(NULL);
+
+ struct {
+ const char* request;
+ const char* expected_response;
+ } tests[] = { { "echoheader?Accept-Language", "None" },
+ { "echoheader?Accept-Charset", "None" },
+ { "echoheader?User-Agent", "" } };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(tests); i++) {
+ TestDelegate d;
+ URLRequest req(test_server_.GetURL(tests[i].request), &d, &context);
+ req.Start();
+ MessageLoop::current()->Run();
+ EXPECT_EQ(tests[i].expected_response, d.data_received())
+ << " Request = \"" << tests[i].request << "\"";
+ }
+}
+
+class HTTPSRequestTest : public testing::Test {
+ public:
+ HTTPSRequestTest() : default_context_(true) {
+ default_context_.set_network_delegate(&default_network_delegate_);
+ default_context_.Init();
+ }
+ virtual ~HTTPSRequestTest() {}
+
+ protected:
+ TestNetworkDelegate default_network_delegate_; // Must outlive URLRequest.
+ TestURLRequestContext default_context_;
+};
+
+// This test was disabled because it made chrome_frame_net_tests hang
+// (see bug 102991).
+TEST_F(HTTPSRequestTest, DISABLED_HTTPSGetTest) {
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ TestServer::kLocalhost,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server.GetURL(""), &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ CheckSSLInfo(r.ssl_info());
+ EXPECT_EQ(test_server.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+TEST_F(HTTPSRequestTest, HTTPSMismatchedTest) {
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ bool err_allowed = true;
+ for (int i = 0; i < 2 ; i++, err_allowed = !err_allowed) {
+ TestDelegate d;
+ {
+ d.set_allow_certificate_errors(err_allowed);
+ URLRequest r(test_server.GetURL(""), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ if (err_allowed) {
+ EXPECT_NE(0, d.bytes_received());
+ CheckSSLInfo(r.ssl_info());
+ } else {
+ EXPECT_EQ(0, d.bytes_received());
+ }
+ }
+ }
+}
+
+TEST_F(HTTPSRequestTest, HTTPSExpiredTest) {
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_EXPIRED);
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ // Iterate from false to true, just so that we do the opposite of the
+ // previous test in order to increase test coverage.
+ bool err_allowed = false;
+ for (int i = 0; i < 2 ; i++, err_allowed = !err_allowed) {
+ TestDelegate d;
+ {
+ d.set_allow_certificate_errors(err_allowed);
+ URLRequest r(test_server.GetURL(""), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ if (err_allowed) {
+ EXPECT_NE(0, d.bytes_received());
+ CheckSSLInfo(r.ssl_info());
+ } else {
+ EXPECT_EQ(0, d.bytes_received());
+ }
+ }
+ }
+}
+
+// Tests TLSv1.1 -> TLSv1 fallback. Verifies that we don't fall back more
+// than necessary.
+TEST_F(HTTPSRequestTest, TLSv1Fallback) {
+ uint16 default_version_max = SSLConfigService::default_version_max();
+ // The OpenSSL library in use may not support TLS 1.1.
+#if !defined(USE_OPENSSL)
+ EXPECT_GT(default_version_max, SSL_PROTOCOL_VERSION_TLS1);
+#endif
+ if (default_version_max <= SSL_PROTOCOL_VERSION_TLS1)
+ return;
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_OK);
+ ssl_options.tls_intolerant =
+ TestServer::SSLOptions::TLS_INTOLERANT_TLS1_1;
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ TestURLRequestContext context(true);
+ context.Init();
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(""), &d, &context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(static_cast<int>(SSL_CONNECTION_VERSION_TLS1),
+ SSLConnectionStatusToVersion(r.ssl_info().connection_status));
+ EXPECT_TRUE(r.ssl_info().connection_status & SSL_CONNECTION_VERSION_FALLBACK);
+}
+
+// This tests that a load of www.google.com with a certificate error sets
+// the |certificate_errors_are_fatal| flag correctly. This flag will cause
+// the interstitial to be fatal.
+TEST_F(HTTPSRequestTest, HTTPSPreloadedHSTSTest) {
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ // We require that the URL be www.google.com in order to pick up the
+ // preloaded HSTS entries in the TransportSecurityState. This means that we
+ // have to use a MockHostResolver in order to direct www.google.com to the
+ // testserver. By default, MockHostResolver maps all hosts to 127.0.0.1.
+
+ MockHostResolver host_resolver;
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_host_resolver(&host_resolver);
+ TransportSecurityState transport_security_state;
+ context.set_transport_security_state(&transport_security_state);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest r(GURL(StringPrintf("https://www.google.com:%d",
+ test_server.host_port_pair().port())),
+ &d,
+ &context);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ EXPECT_TRUE(d.certificate_errors_are_fatal());
+}
+
+// This tests that cached HTTPS page loads do not cause any updates to the
+// TransportSecurityState.
+TEST_F(HTTPSRequestTest, HTTPSErrorsNoClobberTSSTest) {
+ // The actual problem -- CERT_MISMATCHED_NAME in this case -- doesn't
+ // matter. It just has to be any error.
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_MISMATCHED_NAME);
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ // We require that the URL be www.google.com in order to pick up the
+ // preloaded and dynamic HSTS and public key pin entries in the
+ // TransportSecurityState. This means that we have to use a
+ // MockHostResolver in order to direct www.google.com to the testserver.
+ // By default, MockHostResolver maps all hosts to 127.0.0.1.
+
+ MockHostResolver host_resolver;
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+ TestURLRequestContext context(true);
+ context.set_network_delegate(&network_delegate);
+ context.set_host_resolver(&host_resolver);
+ TransportSecurityState transport_security_state;
+ TransportSecurityState::DomainState domain_state;
+ EXPECT_TRUE(transport_security_state.GetDomainState("www.google.com", true,
+ &domain_state));
+ context.set_transport_security_state(&transport_security_state);
+ context.Init();
+
+ TestDelegate d;
+ URLRequest r(GURL(StringPrintf("https://www.google.com:%d",
+ test_server.host_port_pair().port())),
+ &d,
+ &context);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_TRUE(d.have_certificate_errors());
+ EXPECT_TRUE(d.certificate_errors_are_fatal());
+
+ // Get a fresh copy of the state, and check that it hasn't been updated.
+ TransportSecurityState::DomainState new_domain_state;
+ EXPECT_TRUE(transport_security_state.GetDomainState("www.google.com", true,
+ &new_domain_state));
+ EXPECT_EQ(new_domain_state.upgrade_mode, domain_state.upgrade_mode);
+ EXPECT_EQ(new_domain_state.include_subdomains,
+ domain_state.include_subdomains);
+ EXPECT_TRUE(FingerprintsEqual(new_domain_state.static_spki_hashes,
+ domain_state.static_spki_hashes));
+ EXPECT_TRUE(FingerprintsEqual(new_domain_state.dynamic_spki_hashes,
+ domain_state.dynamic_spki_hashes));
+ EXPECT_TRUE(FingerprintsEqual(new_domain_state.bad_static_spki_hashes,
+ domain_state.bad_static_spki_hashes));
+}
+
+// Make sure HSTS preserves a POST request's method and body.
+TEST_F(HTTPSRequestTest, HSTSPreservesPosts) {
+ static const char kData[] = "hello world";
+
+ TestServer::SSLOptions ssl_options(TestServer::SSLOptions::CERT_OK);
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+
+ // Per spec, TransportSecurityState expects a domain name, rather than an IP
+ // address, so a MockHostResolver is needed to redirect www.somewhere.com to
+ // the TestServer. By default, MockHostResolver maps all hosts to 127.0.0.1.
+ MockHostResolver host_resolver;
+
+ // Force https for www.somewhere.com.
+ TransportSecurityState transport_security_state;
+ net::TransportSecurityState::DomainState domain_state;
+ domain_state.upgrade_expiry =
+ domain_state.created + base::TimeDelta::FromDays(1000);
+ transport_security_state.EnableHost("www.somewhere.com", domain_state);
+
+ TestNetworkDelegate network_delegate; // Must outlive URLRequest.
+
+ TestURLRequestContext context(true);
+ context.set_host_resolver(&host_resolver);
+ context.set_transport_security_state(&transport_security_state);
+ context.set_network_delegate(&network_delegate);
+ context.Init();
+
+ TestDelegate d;
+ // Navigating to https://www.somewhere.com instead of https://127.0.0.1 will
+ // cause a certificate error. Ignore the error.
+ d.set_allow_certificate_errors(true);
+
+ URLRequest req(GURL(StringPrintf("http://www.somewhere.com:%d/echo",
+ test_server.host_port_pair().port())),
+ &d,
+ &context);
+ req.set_method("POST");
+ req.set_upload(make_scoped_ptr(CreateSimpleUploadData(kData)));
+
+ req.Start();
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ("https", req.url().scheme());
+ EXPECT_EQ("POST", req.method());
+ EXPECT_EQ(kData, d.data_received());
+}
+
+TEST_F(HTTPSRequestTest, SSLv3Fallback) {
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_OK);
+ ssl_options.tls_intolerant = TestServer::SSLOptions::TLS_INTOLERANT_ALL;
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ TestURLRequestContext context(true);
+ context.Init();
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(""), &d, &context);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_NE(0, d.bytes_received());
+ EXPECT_EQ(static_cast<int>(SSL_CONNECTION_VERSION_SSL3),
+ SSLConnectionStatusToVersion(r.ssl_info().connection_status));
+ EXPECT_TRUE(r.ssl_info().connection_status & SSL_CONNECTION_VERSION_FALLBACK);
+}
+
+namespace {
+
+class SSLClientAuthTestDelegate : public TestDelegate {
+ public:
+ SSLClientAuthTestDelegate() : on_certificate_requested_count_(0) {
+ }
+ virtual void OnCertificateRequested(
+ URLRequest* request,
+ SSLCertRequestInfo* cert_request_info) {
+ on_certificate_requested_count_++;
+ MessageLoop::current()->Quit();
+ }
+ int on_certificate_requested_count() {
+ return on_certificate_requested_count_;
+ }
+ private:
+ int on_certificate_requested_count_;
+};
+
+} // namespace
+
+// TODO(davidben): Test the rest of the code. Specifically,
+// - Filtering which certificates to select.
+// - Sending a certificate back.
+// - Getting a certificate request in an SSL renegotiation sending the
+// HTTP request.
+TEST_F(HTTPSRequestTest, ClientAuthTest) {
+ TestServer::SSLOptions ssl_options;
+ ssl_options.request_client_certificate = true;
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ SSLClientAuthTestDelegate d;
+ {
+ URLRequest r(test_server.GetURL(""), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.on_certificate_requested_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(0, d.bytes_received());
+
+ // Send no certificate.
+ // TODO(davidben): Get temporary client cert import (with keys) working on
+ // all platforms so we can test sending a cert as well.
+ r.ContinueWithCertificate(NULL);
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_NE(0, d.bytes_received());
+ }
+}
+
+TEST_F(HTTPSRequestTest, ResumeTest) {
+ // Test that we attempt a session resume when making two connections to the
+ // same host.
+ TestServer::SSLOptions ssl_options;
+ ssl_options.record_resume = true;
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ SSLClientSocket::ClearSessionCache();
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ }
+
+ reinterpret_cast<HttpCache*>(default_context_.http_transaction_factory())->
+ CloseAllConnections();
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ // The response will look like;
+ // insert abc
+ // lookup abc
+ // insert xyz
+ //
+ // With a newline at the end which makes the split think that there are
+ // four lines.
+
+ EXPECT_EQ(1, d.response_started_count());
+ std::vector<std::string> lines;
+ base::SplitString(d.data_received(), '\n', &lines);
+ ASSERT_EQ(4u, lines.size()) << d.data_received();
+
+ std::string session_id;
+
+ for (size_t i = 0; i < 2; i++) {
+ std::vector<std::string> parts;
+ base::SplitString(lines[i], '\t', &parts);
+ ASSERT_EQ(2u, parts.size());
+ if (i == 0) {
+ EXPECT_EQ("insert", parts[0]);
+ session_id = parts[1];
+ } else {
+ EXPECT_EQ("lookup", parts[0]);
+ EXPECT_EQ(session_id, parts[1]);
+ }
+ }
+ }
+}
+
+TEST_F(HTTPSRequestTest, SSLSessionCacheShardTest) {
+ // Test that sessions aren't resumed when the value of ssl_session_cache_shard
+ // differs.
+ TestServer::SSLOptions ssl_options;
+ ssl_options.record_resume = true;
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ SSLClientSocket::ClearSessionCache();
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ }
+
+ // Now create a new HttpCache with a different ssl_session_cache_shard value.
+ HttpNetworkSession::Params params;
+ params.host_resolver = default_context_.host_resolver();
+ params.cert_verifier = default_context_.cert_verifier();
+ params.proxy_service = default_context_.proxy_service();
+ params.ssl_config_service = default_context_.ssl_config_service();
+ params.http_auth_handler_factory =
+ default_context_.http_auth_handler_factory();
+ params.network_delegate = default_context_.network_delegate();
+ params.http_server_properties = default_context_.http_server_properties();
+ params.ssl_session_cache_shard = "alternate";
+
+ scoped_ptr<net::HttpCache> cache(new net::HttpCache(
+ new net::HttpNetworkSession(params),
+ net::HttpCache::DefaultBackend::InMemory(0)));
+
+ default_context_.set_http_transaction_factory(cache.get());
+
+ {
+ TestDelegate d;
+ URLRequest r(
+ test_server.GetURL("ssl-session-cache"), &d, &default_context_);
+
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ // The response will look like;
+ // insert abc
+ // insert xyz
+ //
+ // With a newline at the end which makes the split think that there are
+ // three lines.
+
+ EXPECT_EQ(1, d.response_started_count());
+ std::vector<std::string> lines;
+ base::SplitString(d.data_received(), '\n', &lines);
+ ASSERT_EQ(3u, lines.size());
+
+ std::string session_id;
+ for (size_t i = 0; i < 2; i++) {
+ std::vector<std::string> parts;
+ base::SplitString(lines[i], '\t', &parts);
+ ASSERT_EQ(2u, parts.size());
+ EXPECT_EQ("insert", parts[0]);
+ if (i == 0) {
+ session_id = parts[1];
+ } else {
+ EXPECT_NE(session_id, parts[1]);
+ }
+ }
+ }
+}
+
+class TestSSLConfigService : public SSLConfigService {
+ public:
+ TestSSLConfigService(bool ev_enabled, bool online_rev_checking)
+ : ev_enabled_(ev_enabled),
+ online_rev_checking_(online_rev_checking) {
+ }
+
+ // SSLConfigService:
+ virtual void GetSSLConfig(SSLConfig* config) OVERRIDE {
+ *config = SSLConfig();
+ config->rev_checking_enabled = online_rev_checking_;
+ config->verify_ev_cert = ev_enabled_;
+ }
+
+ protected:
+ virtual ~TestSSLConfigService() {}
+
+ private:
+ const bool ev_enabled_;
+ const bool online_rev_checking_;
+};
+
+// This the fingerprint of the "Testing CA" certificate used by the testserver.
+// See net/data/ssl/certificates/ocsp-test-root.pem.
+static const SHA1HashValue kOCSPTestCertFingerprint =
+ { { 0xf1, 0xad, 0xf6, 0xce, 0x42, 0xac, 0xe7, 0xb4, 0xf4, 0x24,
+ 0xdb, 0x1a, 0xf7, 0xa0, 0x9f, 0x09, 0xa1, 0xea, 0xf1, 0x5c } };
+
+// This is the policy OID contained in the certificates that testserver
+// generates.
+static const char kOCSPTestCertPolicy[] = "1.3.6.1.4.1.11129.2.4.1";
+
+class HTTPSOCSPTest : public HTTPSRequestTest {
+ public:
+ HTTPSOCSPTest()
+ : context_(true),
+ ev_test_policy_(
+ new ScopedTestEVPolicy(EVRootCAMetadata::GetInstance(),
+ kOCSPTestCertFingerprint,
+ kOCSPTestCertPolicy)) {
+ }
+
+ virtual void SetUp() OVERRIDE {
+ SetupContext(&context_);
+ context_.Init();
+
+ scoped_refptr<net::X509Certificate> root_cert =
+ ImportCertFromFile(GetTestCertsDirectory(), "ocsp-test-root.pem");
+ CHECK_NE(static_cast<X509Certificate*>(NULL), root_cert);
+ test_root_.reset(new ScopedTestRoot(root_cert));
+
+#if defined(USE_NSS) || defined(OS_IOS)
+ SetURLRequestContextForNSSHttpIO(&context_);
+ EnsureNSSHttpIOInit();
+#endif
+ }
+
+ void DoConnection(const TestServer::SSLOptions& ssl_options,
+ CertStatus* out_cert_status) {
+ // We always overwrite out_cert_status.
+ *out_cert_status = 0;
+ TestServer test_server(TestServer::TYPE_HTTPS,
+ ssl_options,
+ FilePath(FILE_PATH_LITERAL("net/data/ssl")));
+ ASSERT_TRUE(test_server.Start());
+
+ TestDelegate d;
+ d.set_allow_certificate_errors(true);
+ URLRequest r(test_server.GetURL(""), &d, &context_);
+ r.Start();
+
+ MessageLoop::current()->Run();
+
+ EXPECT_EQ(1, d.response_started_count());
+ *out_cert_status = r.ssl_info().cert_status;
+ }
+
+ ~HTTPSOCSPTest() {
+#if defined(USE_NSS) || defined(OS_IOS)
+ ShutdownNSSHttpIO();
+#endif
+ }
+
+ protected:
+ // SetupContext configures the URLRequestContext that will be used for making
+ // connetions to testserver. This can be overridden in test subclasses for
+ // different behaviour.
+ virtual void SetupContext(URLRequestContext* context) {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(true /* check for EV */,
+ true /* online revocation checking */));
+ }
+
+ scoped_ptr<ScopedTestRoot> test_root_;
+ TestURLRequestContext context_;
+ scoped_ptr<ScopedTestEVPolicy> ev_test_policy_;
+};
+
+static CertStatus ExpectedCertStatusForFailedOnlineRevocationCheck() {
+#if defined(OS_WIN)
+ // Windows can return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION but we don't
+ // have that ability on other platforms.
+ return CERT_STATUS_UNABLE_TO_CHECK_REVOCATION;
+#else
+ return 0;
+#endif
+}
+
+// SystemUsesChromiumEVMetadata returns true iff the current operating system
+// uses Chromium's EV metadata (i.e. EVRootCAMetadata). If it does not, then
+// several tests are effected because our testing EV certificate won't be
+// recognised as EV.
+static bool SystemUsesChromiumEVMetadata() {
+#if defined(USE_OPENSSL)
+ // http://crbug.com/117478 - OpenSSL does not support EV validation.
+ return false;
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // On OS X, we use the system to tell us whether a certificate is EV or not
+ // and the system won't recognise our testing root.
+ return false;
+#else
+ return true;
+#endif
+}
+
+static bool SystemSupportsOCSP() {
+#if defined(USE_OPENSSL)
+ // http://crbug.com/117478 - OpenSSL does not support OCSP.
+ return false;
+#elif defined(OS_WIN)
+ return base::win::GetVersion() >= base::win::VERSION_VISTA;
+#elif defined(OS_ANDROID)
+ // TODO(jnd): http://crbug.com/117478 - EV verification is not yet supported.
+ return false;
+#else
+ return true;
+#endif
+}
+
+TEST_F(HTTPSOCSPTest, Valid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::SSLOptions ssl_options(TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_OK;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_IS_EV));
+
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSOCSPTest, Revoked) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_REVOKED;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+#if !(defined(OS_MACOSX) && !defined(OS_IOS))
+ // Doesn't pass on OS X yet for reasons that need to be investigated.
+ EXPECT_EQ(CERT_STATUS_REVOKED, cert_status & CERT_STATUS_ALL_ERRORS);
+#endif
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSOCSPTest, Invalid) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_INVALID;
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ // Without a positive OCSP response, we shouldn't show the EV status.
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_TRUE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+class HTTPSEVCRLSetTest : public HTTPSOCSPTest {
+ protected:
+ virtual void SetupContext(URLRequestContext* context) OVERRIDE {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(true /* check for EV */,
+ false /* online revocation checking */));
+ }
+};
+
+TEST_F(HTTPSEVCRLSetTest, MissingCRLSetAndInvalidOCSP) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(scoped_refptr<CRLSet>());
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, MissingCRLSetAndGoodOCSP) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_OK;
+ SSLConfigService::SetCRLSet(scoped_refptr<CRLSet>());
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_IS_EV));
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, ExpiredCRLSet) {
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ExpiredCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(ExpectedCertStatusForFailedOnlineRevocationCheck(),
+ cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_REV_CHECKING_ENABLED));
+}
+
+TEST_F(HTTPSEVCRLSetTest, FreshCRLSet) {
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::EmptyCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ // With a valid, fresh CRLSet the bad OCSP response shouldn't matter because
+ // we wont check it.
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_EQ(SystemUsesChromiumEVMetadata(),
+ static_cast<bool>(cert_status & CERT_STATUS_IS_EV));
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+TEST_F(HTTPSEVCRLSetTest, ExpiredCRLSetAndRevokedNonEVCert) {
+ // Test that when EV verification is requested, but online revocation
+ // checking is disabled, and the leaf certificate is not in fact EV, that
+ // no revocation checking actually happens.
+ if (!SystemSupportsOCSP()) {
+ LOG(WARNING) << "Skipping test because system doesn't support OCSP";
+ return;
+ }
+
+ // Unmark the certificate's OID as EV, which should disable revocation
+ // checking (as per the user preference)
+ ev_test_policy_.reset();
+
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_REVOKED;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ExpiredCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_FALSE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+
+class HTTPSCRLSetTest : public HTTPSOCSPTest {
+ protected:
+ virtual void SetupContext(URLRequestContext* context) OVERRIDE {
+ context->set_ssl_config_service(
+ new TestSSLConfigService(false /* check for EV */,
+ false /* online revocation checking */));
+ }
+};
+
+TEST_F(HTTPSCRLSetTest, ExpiredCRLSet) {
+ TestServer::SSLOptions ssl_options(
+ TestServer::SSLOptions::CERT_AUTO);
+ ssl_options.ocsp_status = TestServer::SSLOptions::OCSP_INVALID;
+ SSLConfigService::SetCRLSet(
+ scoped_refptr<CRLSet>(CRLSet::ExpiredCRLSetForTesting()));
+
+ CertStatus cert_status;
+ DoConnection(ssl_options, &cert_status);
+
+ // If we're not trying EV verification then, even if the CRLSet has expired,
+ // we don't fall back to online revocation checks.
+ EXPECT_EQ(0u, cert_status & CERT_STATUS_ALL_ERRORS);
+ EXPECT_FALSE(cert_status & CERT_STATUS_IS_EV);
+ EXPECT_FALSE(cert_status & CERT_STATUS_REV_CHECKING_ENABLED);
+}
+#endif // !defined(OS_IOS)
+
+#if !defined(DISABLE_FTP_SUPPORT)
+class URLRequestTestFTP : public URLRequestTest {
+ public:
+ URLRequestTestFTP()
+ : test_server_(TestServer::TYPE_FTP, TestServer::kLocalhost, FilePath()) {
+ }
+
+ protected:
+ TestServer test_server_;
+};
+
+// Make sure an FTP request using an unsafe ports fails.
+TEST_F(URLRequestTestFTP, FLAKY_UnsafePort) {
+ ASSERT_TRUE(test_server_.Start());
+
+ URLRequestJobFactoryImpl job_factory;
+
+ GURL url("ftp://127.0.0.1:7");
+ FtpProtocolHandler ftp_protocol_handler(
+ default_context_.ftp_transaction_factory(),
+ default_context_.ftp_auth_cache());
+ job_factory.SetProtocolHandler(
+ "ftp",
+ new FtpProtocolHandler(default_context_.ftp_transaction_factory(),
+ default_context_.ftp_auth_cache()));
+ default_context_.set_job_factory(&job_factory);
+
+ TestDelegate d;
+ {
+ URLRequest r(url, &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(URLRequestStatus::FAILED, r.status().status());
+ EXPECT_EQ(ERR_UNSAFE_PORT, r.status().error());
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPDirectoryListing) {
+ ASSERT_TRUE(test_server_.Start());
+
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("/"), &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_LT(0, d.bytes_received());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPGetTestAnonymous) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(test_server_.GetURL("/LICENSE"), &d, &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPGetTest) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE", "chrome", "chrome"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(test_server_.host_port_pair().host(),
+ r.GetSocketAddress().host());
+ EXPECT_EQ(test_server_.host_port_pair().port(),
+ r.GetSocketAddress().port());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongPassword) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "wrong_password"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongPasswordRestart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ // Set correct login credentials. The delegate will be asked for them when
+ // the initial login with wrong credentials will fail.
+ d.set_credentials(AuthCredentials(kChrome, kChrome));
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "wrong_password"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongUser) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "wrong_user",
+ "chrome"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), 0);
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCheckWrongUserRestart) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+ TestDelegate d;
+ // Set correct login credentials. The delegate will be asked for them when
+ // the initial login with wrong credentials will fail.
+ d.set_credentials(AuthCredentials(kChrome, kChrome));
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "wrong_user",
+ "chrome"),
+ &d,
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d.response_started_count());
+ EXPECT_FALSE(d.received_data_before_response());
+ EXPECT_EQ(d.bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCacheURLCredentials) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+
+ scoped_ptr<TestDelegate> d(new TestDelegate);
+ {
+ // Pass correct login identity in the URL.
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "chrome"),
+ d.get(),
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+
+ d.reset(new TestDelegate);
+ {
+ // This request should use cached identity from previous request.
+ URLRequest r(test_server_.GetURL("/LICENSE"), d.get(), &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+}
+
+// Flaky, see http://crbug.com/25045.
+TEST_F(URLRequestTestFTP, DISABLED_FTPCacheLoginBoxCredentials) {
+ ASSERT_TRUE(test_server_.Start());
+
+ FilePath app_path;
+ PathService::Get(base::DIR_SOURCE_ROOT, &app_path);
+ app_path = app_path.AppendASCII("LICENSE");
+
+ scoped_ptr<TestDelegate> d(new TestDelegate);
+ // Set correct login credentials. The delegate will be asked for them when
+ // the initial login with wrong credentials will fail.
+ d->set_credentials(AuthCredentials(kChrome, kChrome));
+ {
+ URLRequest r(
+ test_server_.GetURLWithUserAndPassword("/LICENSE",
+ "chrome",
+ "wrong_password"),
+ d.get(),
+ &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+
+ // Use a new delegate without explicit credentials. The cached ones should be
+ // used.
+ d.reset(new TestDelegate);
+ {
+ // Don't pass wrong credentials in the URL, they would override valid cached
+ // ones.
+ URLRequest r(test_server_.GetURL("/LICENSE"), d.get(), &default_context_);
+ r.Start();
+ EXPECT_TRUE(r.is_pending());
+
+ MessageLoop::current()->Run();
+
+ int64 file_size = 0;
+ file_util::GetFileSize(app_path, &file_size);
+
+ EXPECT_FALSE(r.is_pending());
+ EXPECT_EQ(1, d->response_started_count());
+ EXPECT_FALSE(d->received_data_before_response());
+ EXPECT_EQ(d->bytes_received(), static_cast<int>(file_size));
+ }
+}
+#endif // !defined(DISABLE_FTP_SUPPORT)
+
+} // namespace net
diff --git a/src/net/url_request/view_cache_helper.cc b/src/net/url_request/view_cache_helper.cc
new file mode 100644
index 0000000..d468075
--- /dev/null
+++ b/src/net/url_request/view_cache_helper.cc
@@ -0,0 +1,360 @@
+// Copyright (c) 2012 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 "net/url_request/view_cache_helper.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/stringprintf.h"
+#include "net/base/escape.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_errors.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/http/http_response_headers.h"
+#include "net/http/http_response_info.h"
+#include "net/url_request/url_request_context.h"
+
+#define VIEW_CACHE_HEAD \
+ "<html><meta charset=\"utf-8\">" \
+ "<meta http-equiv=\"Content-Security-Policy\" " \
+ " content=\"object-src 'none'; script-src 'none' 'unsafe-eval'\">" \
+ "<body><table>"
+
+#define VIEW_CACHE_TAIL \
+ "</table></body></html>"
+
+namespace net {
+
+namespace {
+
+std::string FormatEntryInfo(disk_cache::Entry* entry,
+ const std::string& url_prefix) {
+ std::string key = entry->GetKey();
+ GURL url = GURL(url_prefix + key);
+ std::string row =
+ "<tr><td><a href=\"" + url.spec() + "\">" + EscapeForHTML(key) +
+ "</a></td></tr>";
+ return row;
+}
+
+} // namespace.
+
+ViewCacheHelper::ViewCacheHelper()
+ : context_(NULL),
+ disk_cache_(NULL),
+ entry_(NULL),
+ iter_(NULL),
+ buf_len_(0),
+ index_(0),
+ data_(NULL),
+ next_state_(STATE_NONE),
+ ALLOW_THIS_IN_INITIALIZER_LIST(weak_factory_(this)) {
+}
+
+ViewCacheHelper::~ViewCacheHelper() {
+ if (entry_)
+ entry_->Close();
+}
+
+int ViewCacheHelper::GetEntryInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ std::string* out,
+ const CompletionCallback& callback) {
+ return GetInfoHTML(key, context, std::string(), out, callback);
+}
+
+int ViewCacheHelper::GetContentsHTML(const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback) {
+ return GetInfoHTML(std::string(), context, url_prefix, out, callback);
+}
+
+// static
+void ViewCacheHelper::HexDump(const char *buf, size_t buf_len,
+ std::string* result) {
+ const size_t kMaxRows = 16;
+ int offset = 0;
+
+ const unsigned char *p;
+ while (buf_len) {
+ base::StringAppendF(result, "%08x: ", offset);
+ offset += kMaxRows;
+
+ p = (const unsigned char *) buf;
+
+ size_t i;
+ size_t row_max = std::min(kMaxRows, buf_len);
+
+ // print hex codes:
+ for (i = 0; i < row_max; ++i)
+ base::StringAppendF(result, "%02x ", *p++);
+ for (i = row_max; i < kMaxRows; ++i)
+ result->append(" ");
+
+ // print ASCII glyphs if possible:
+ p = (const unsigned char *) buf;
+ for (i = 0; i < row_max; ++i, ++p) {
+ if (*p < 0x7F && *p > 0x1F) {
+ AppendEscapedCharForHTML(*p, result);
+ } else {
+ result->push_back('.');
+ }
+ }
+
+ result->push_back('\n');
+
+ buf += row_max;
+ buf_len -= row_max;
+ }
+}
+
+//-----------------------------------------------------------------------------
+
+int ViewCacheHelper::GetInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback) {
+ DCHECK(callback_.is_null());
+ DCHECK(context);
+ key_ = key;
+ context_ = context;
+ url_prefix_ = url_prefix;
+ data_ = out;
+ next_state_ = STATE_GET_BACKEND;
+ int rv = DoLoop(OK);
+
+ if (rv == ERR_IO_PENDING)
+ callback_ = callback;
+
+ return rv;
+}
+
+void ViewCacheHelper::DoCallback(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK(!callback_.is_null());
+
+ callback_.Run(rv);
+ callback_.Reset();
+}
+
+void ViewCacheHelper::HandleResult(int rv) {
+ DCHECK_NE(ERR_IO_PENDING, rv);
+ DCHECK_NE(ERR_FAILED, rv);
+ context_ = NULL;
+ if (!callback_.is_null())
+ DoCallback(rv);
+}
+
+int ViewCacheHelper::DoLoop(int result) {
+ DCHECK(next_state_ != STATE_NONE);
+
+ int rv = result;
+ do {
+ State state = next_state_;
+ next_state_ = STATE_NONE;
+ switch (state) {
+ case STATE_GET_BACKEND:
+ DCHECK_EQ(OK, rv);
+ rv = DoGetBackend();
+ break;
+ case STATE_GET_BACKEND_COMPLETE:
+ rv = DoGetBackendComplete(rv);
+ break;
+ case STATE_OPEN_NEXT_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenNextEntry();
+ break;
+ case STATE_OPEN_NEXT_ENTRY_COMPLETE:
+ rv = DoOpenNextEntryComplete(rv);
+ break;
+ case STATE_OPEN_ENTRY:
+ DCHECK_EQ(OK, rv);
+ rv = DoOpenEntry();
+ break;
+ case STATE_OPEN_ENTRY_COMPLETE:
+ rv = DoOpenEntryComplete(rv);
+ break;
+ case STATE_READ_RESPONSE:
+ DCHECK_EQ(OK, rv);
+ rv = DoReadResponse();
+ break;
+ case STATE_READ_RESPONSE_COMPLETE:
+ rv = DoReadResponseComplete(rv);
+ break;
+ case STATE_READ_DATA:
+ DCHECK_EQ(OK, rv);
+ rv = DoReadData();
+ break;
+ case STATE_READ_DATA_COMPLETE:
+ rv = DoReadDataComplete(rv);
+ break;
+
+ default:
+ NOTREACHED() << "bad state";
+ rv = ERR_FAILED;
+ break;
+ }
+ } while (rv != ERR_IO_PENDING && next_state_ != STATE_NONE);
+
+ if (rv != ERR_IO_PENDING)
+ HandleResult(rv);
+
+ return rv;
+}
+
+int ViewCacheHelper::DoGetBackend() {
+ next_state_ = STATE_GET_BACKEND_COMPLETE;
+
+ if (!context_->http_transaction_factory())
+ return ERR_FAILED;
+
+ HttpCache* http_cache = context_->http_transaction_factory()->GetCache();
+ if (!http_cache)
+ return ERR_FAILED;
+
+ return http_cache->GetBackend(
+ &disk_cache_, base::Bind(&ViewCacheHelper::OnIOComplete,
+ base::Unretained(this)));
+}
+
+int ViewCacheHelper::DoGetBackendComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append("no disk cache");
+ return OK;
+ }
+
+ DCHECK_EQ(OK, result);
+ if (key_.empty()) {
+ data_->assign(VIEW_CACHE_HEAD);
+ DCHECK(!iter_);
+ next_state_ = STATE_OPEN_NEXT_ENTRY;
+ return OK;
+ }
+
+ next_state_ = STATE_OPEN_ENTRY;
+ return OK;
+}
+
+int ViewCacheHelper::DoOpenNextEntry() {
+ next_state_ = STATE_OPEN_NEXT_ENTRY_COMPLETE;
+ return disk_cache_->OpenNextEntry(
+ &iter_, &entry_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, base::Unretained(this)));
+}
+
+int ViewCacheHelper::DoOpenNextEntryComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append(VIEW_CACHE_TAIL);
+ return OK;
+ }
+
+ DCHECK_EQ(OK, result);
+ data_->append(FormatEntryInfo(entry_, url_prefix_));
+ entry_->Close();
+ entry_ = NULL;
+
+ next_state_ = STATE_OPEN_NEXT_ENTRY;
+ return OK;
+}
+
+int ViewCacheHelper::DoOpenEntry() {
+ next_state_ = STATE_OPEN_ENTRY_COMPLETE;
+ return disk_cache_->OpenEntry(
+ key_, &entry_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, base::Unretained(this)));
+}
+
+int ViewCacheHelper::DoOpenEntryComplete(int result) {
+ if (result == ERR_FAILED) {
+ data_->append("no matching cache entry for: " + EscapeForHTML(key_));
+ return OK;
+ }
+
+ data_->assign(VIEW_CACHE_HEAD);
+ data_->append(EscapeForHTML(entry_->GetKey()));
+ next_state_ = STATE_READ_RESPONSE;
+ return OK;
+}
+
+int ViewCacheHelper::DoReadResponse() {
+ next_state_ = STATE_READ_RESPONSE_COMPLETE;
+ buf_len_ = entry_->GetDataSize(0);
+ if (!buf_len_)
+ return buf_len_;
+
+ buf_ = new IOBuffer(buf_len_);
+ return entry_->ReadData(
+ 0, 0, buf_, buf_len_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, weak_factory_.GetWeakPtr()));
+}
+
+int ViewCacheHelper::DoReadResponseComplete(int result) {
+ if (result && result == buf_len_) {
+ HttpResponseInfo response;
+ bool truncated;
+ if (HttpCache::ParseResponseInfo(buf_->data(), buf_len_, &response,
+ &truncated) &&
+ response.headers) {
+ if (truncated)
+ data_->append("<pre>RESPONSE_INFO_TRUNCATED</pre>");
+
+ data_->append("<hr><pre>");
+ data_->append(EscapeForHTML(response.headers->GetStatusLine()));
+ data_->push_back('\n');
+
+ void* iter = NULL;
+ std::string name, value;
+ while (response.headers->EnumerateHeaderLines(&iter, &name, &value)) {
+ data_->append(EscapeForHTML(name));
+ data_->append(": ");
+ data_->append(EscapeForHTML(value));
+ data_->push_back('\n');
+ }
+ data_->append("</pre>");
+ }
+ }
+
+ index_ = 0;
+ next_state_ = STATE_READ_DATA;
+ return OK;
+}
+
+int ViewCacheHelper::DoReadData() {
+ data_->append("<hr><pre>");
+
+ next_state_ = STATE_READ_DATA_COMPLETE;
+ buf_len_ = entry_->GetDataSize(index_);
+ if (!buf_len_)
+ return buf_len_;
+
+ buf_ = new IOBuffer(buf_len_);
+ return entry_->ReadData(
+ index_, 0, buf_, buf_len_,
+ base::Bind(&ViewCacheHelper::OnIOComplete, weak_factory_.GetWeakPtr()));
+}
+
+int ViewCacheHelper::DoReadDataComplete(int result) {
+ if (result && result == buf_len_) {
+ HexDump(buf_->data(), buf_len_, data_);
+ }
+ data_->append("</pre>");
+ index_++;
+ if (index_ < HttpCache::kNumCacheEntryDataIndices) {
+ next_state_ = STATE_READ_DATA;
+ } else {
+ data_->append(VIEW_CACHE_TAIL);
+ entry_->Close();
+ entry_ = NULL;
+ }
+ return OK;
+}
+
+void ViewCacheHelper::OnIOComplete(int result) {
+ DoLoop(result);
+}
+
+} // namespace net.
diff --git a/src/net/url_request/view_cache_helper.h b/src/net/url_request/view_cache_helper.h
new file mode 100644
index 0000000..d5a7c42
--- /dev/null
+++ b/src/net/url_request/view_cache_helper.h
@@ -0,0 +1,124 @@
+// Copyright (c) 2012 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.
+
+#ifndef NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
+#define NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
+
+#include <string>
+
+#include "base/memory/weak_ptr.h"
+#include "net/base/completion_callback.h"
+#include "net/base/io_buffer.h"
+#include "net/base/net_export.h"
+
+namespace disk_cache {
+class Backend;
+class Entry;
+} // namespace disk_cache
+
+namespace net {
+
+class URLRequestContext;
+
+class NET_EXPORT ViewCacheHelper {
+ public:
+ ViewCacheHelper();
+ ~ViewCacheHelper();
+
+ // Formats the cache information for |key| as HTML. Returns a net error code.
+ // If this method returns ERR_IO_PENDING, |callback| will be notified when the
+ // operation completes. |out| must remain valid until this operation completes
+ // or the object is destroyed.
+ int GetEntryInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ std::string* out,
+ const CompletionCallback& callback);
+
+ // Formats the cache contents as HTML. Returns a net error code.
+ // If this method returns ERR_IO_PENDING, |callback| will be notified when the
+ // operation completes. |out| must remain valid until this operation completes
+ // or the object is destroyed. |url_prefix| will be prepended to each entry
+ // key as a link to the entry.
+ int GetContentsHTML(const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback);
+
+ // Lower-level helper to produce a textual representation of binary data.
+ // The results are appended to |result| and can be used in HTML pages
+ // provided the dump is contained within <pre></pre> tags.
+ static void HexDump(const char *buf, size_t buf_len, std::string* result);
+
+ private:
+ enum State {
+ STATE_NONE,
+ STATE_GET_BACKEND,
+ STATE_GET_BACKEND_COMPLETE,
+ STATE_OPEN_NEXT_ENTRY,
+ STATE_OPEN_NEXT_ENTRY_COMPLETE,
+ STATE_OPEN_ENTRY,
+ STATE_OPEN_ENTRY_COMPLETE,
+ STATE_READ_RESPONSE,
+ STATE_READ_RESPONSE_COMPLETE,
+ STATE_READ_DATA,
+ STATE_READ_DATA_COMPLETE
+ };
+
+ // Implements GetEntryInfoHTML and GetContentsHTML.
+ int GetInfoHTML(const std::string& key,
+ const URLRequestContext* context,
+ const std::string& url_prefix,
+ std::string* out,
+ const CompletionCallback& callback);
+
+ // This is a helper function used to trigger a completion callback. It may
+ // only be called if callback_ is non-null.
+ void DoCallback(int rv);
+
+ // This will trigger the completion callback if appropriate.
+ void HandleResult(int rv);
+
+ // Runs the state transition loop.
+ int DoLoop(int result);
+
+ // Each of these methods corresponds to a State value. If there is an
+ // argument, the value corresponds to the return of the previous state or
+ // corresponding callback.
+ int DoGetBackend();
+ int DoGetBackendComplete(int result);
+ int DoOpenNextEntry();
+ int DoOpenNextEntryComplete(int result);
+ int DoOpenEntry();
+ int DoOpenEntryComplete(int result);
+ int DoReadResponse();
+ int DoReadResponseComplete(int result);
+ int DoReadData();
+ int DoReadDataComplete(int result);
+
+ // Called to signal completion of asynchronous IO.
+ void OnIOComplete(int result);
+
+ const URLRequestContext* context_;
+ disk_cache::Backend* disk_cache_;
+ disk_cache::Entry* entry_;
+ void* iter_;
+ scoped_refptr<IOBuffer> buf_;
+ int buf_len_;
+ int index_;
+
+ std::string key_;
+ std::string url_prefix_;
+ std::string* data_;
+ CompletionCallback callback_;
+
+ State next_state_;
+
+ base::WeakPtrFactory<ViewCacheHelper> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ViewCacheHelper);
+};
+
+} // namespace net.
+
+#endif // NET_URL_REQUEST_VIEW_CACHE_HELPER_H_
diff --git a/src/net/url_request/view_cache_helper_unittest.cc b/src/net/url_request/view_cache_helper_unittest.cc
new file mode 100644
index 0000000..9fda85b
--- /dev/null
+++ b/src/net/url_request/view_cache_helper_unittest.cc
@@ -0,0 +1,225 @@
+// Copyright (c) 2012 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 "net/url_request/view_cache_helper.h"
+
+#include "base/pickle.h"
+#include "net/base/net_errors.h"
+#include "net/base/test_completion_callback.h"
+#include "net/disk_cache/disk_cache.h"
+#include "net/http/http_cache.h"
+#include "net/url_request/url_request_context.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace net {
+
+namespace {
+
+// Disk cache is turned off in lb_shell (http_cache.cc:CreateBackend())
+// The following cases which are using backend cache should be disabled:
+// ListContents, DumpEntry, Prefix, and TruncatedFlag
+#if defined(__LB_SHELL__) || defined(COBALT)
+#define MAYBE_ListContents DISABLED_ListContents
+#define MAYBE_DumpEntry DISABLED_DumpEntry
+#define MAYBE_Prefix DISABLED_Prefix
+#define MAYBE_TruncatedFlag DISABLED_TruncatedFlag
+#else
+#define MAYBE_ListContents ListContents
+#define MAYBE_DumpEntry DumpEntry
+#define MAYBE_Prefix Prefix
+#define MAYBE_TruncatedFlag TruncatedFlag
+#endif
+
+class TestURLRequestContext : public URLRequestContext {
+ public:
+ TestURLRequestContext();
+ virtual ~TestURLRequestContext() {}
+
+ // Gets a pointer to the cache backend.
+ disk_cache::Backend* GetBackend();
+
+ private:
+ HttpCache cache_;
+};
+
+TestURLRequestContext::TestURLRequestContext()
+ : cache_(reinterpret_cast<HttpTransactionFactory*>(NULL), NULL,
+ HttpCache::DefaultBackend::InMemory(0)) {
+ set_http_transaction_factory(&cache_);
+}
+
+void WriteHeaders(disk_cache::Entry* entry, int flags, const std::string data) {
+ if (data.empty())
+ return;
+
+ Pickle pickle;
+ pickle.WriteInt(flags | 1); // Version 1.
+ pickle.WriteInt64(0);
+ pickle.WriteInt64(0);
+ pickle.WriteString(data);
+
+ scoped_refptr<WrappedIOBuffer> buf(new WrappedIOBuffer(
+ reinterpret_cast<const char*>(pickle.data())));
+ int len = static_cast<int>(pickle.size());
+
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(0, 0, buf, len, cb.callback(), true);
+ ASSERT_EQ(len, cb.GetResult(rv));
+}
+
+void WriteData(disk_cache::Entry* entry, int index, const std::string data) {
+ if (data.empty())
+ return;
+
+ int len = data.length();
+ scoped_refptr<IOBuffer> buf(new IOBuffer(len));
+ memcpy(buf->data(), data.data(), data.length());
+
+ net::TestCompletionCallback cb;
+ int rv = entry->WriteData(index, 0, buf, len, cb.callback(), true);
+ ASSERT_EQ(len, cb.GetResult(rv));
+}
+
+void WriteToEntry(disk_cache::Backend* cache, const std::string key,
+ const std::string data0, const std::string data1,
+ const std::string data2) {
+ net::TestCompletionCallback cb;
+ disk_cache::Entry* entry;
+ int rv = cache->CreateEntry(key, &entry, cb.callback());
+ rv = cb.GetResult(rv);
+ if (rv != OK) {
+ rv = cache->OpenEntry(key, &entry, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+ }
+
+ WriteHeaders(entry, 0, data0);
+ WriteData(entry, 1, data1);
+ WriteData(entry, 2, data2);
+
+ entry->Close();
+}
+
+void FillCache(URLRequestContext* context) {
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv =
+ context->http_transaction_factory()->GetCache()->GetBackend(
+ &cache, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+
+ std::string empty;
+ WriteToEntry(cache, "first", "some", empty, empty);
+ WriteToEntry(cache, "second", "only hex_dumped", "same", "kind");
+ WriteToEntry(cache, "third", empty, "another", "thing");
+}
+
+} // namespace.
+
+TEST(ViewCacheHelper, EmptyCache) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ TestCompletionCallback cb;
+ std::string prefix, data;
+ int rv = helper.GetContentsHTML(&context, prefix, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+ EXPECT_FALSE(data.empty());
+}
+
+TEST(ViewCacheHelper, MAYBE_ListContents) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ FillCache(&context);
+
+ std::string prefix, data;
+ TestCompletionCallback cb;
+ int rv = helper.GetContentsHTML(&context, prefix, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+ EXPECT_NE(std::string::npos, data.find("first"));
+ EXPECT_NE(std::string::npos, data.find("second"));
+ EXPECT_NE(std::string::npos, data.find("third"));
+
+ EXPECT_EQ(std::string::npos, data.find("some"));
+ EXPECT_EQ(std::string::npos, data.find("same"));
+ EXPECT_EQ(std::string::npos, data.find("thing"));
+}
+
+TEST(ViewCacheHelper, MAYBE_DumpEntry) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ FillCache(&context);
+
+ std::string data;
+ TestCompletionCallback cb;
+ int rv = helper.GetEntryInfoHTML("second", &context, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+
+ EXPECT_NE(std::string::npos, data.find("hex_dumped"));
+ EXPECT_NE(std::string::npos, data.find("same"));
+ EXPECT_NE(std::string::npos, data.find("kind"));
+
+ EXPECT_EQ(std::string::npos, data.find("first"));
+ EXPECT_EQ(std::string::npos, data.find("third"));
+ EXPECT_EQ(std::string::npos, data.find("some"));
+ EXPECT_EQ(std::string::npos, data.find("another"));
+}
+
+// Makes sure the links are correct.
+TEST(ViewCacheHelper, MAYBE_Prefix) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ FillCache(&context);
+
+ std::string key, data;
+ std::string prefix("prefix:");
+ TestCompletionCallback cb;
+ int rv = helper.GetContentsHTML(&context, prefix, &data, cb.callback());
+ EXPECT_EQ(OK, cb.GetResult(rv));
+
+ EXPECT_EQ(0U, data.find("<html>"));
+ EXPECT_NE(std::string::npos, data.find("</html>"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:first\">"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:second\">"));
+ EXPECT_NE(std::string::npos, data.find("<a href=\"prefix:third\">"));
+}
+
+TEST(ViewCacheHelper, MAYBE_TruncatedFlag) {
+ TestURLRequestContext context;
+ ViewCacheHelper helper;
+
+ net::TestCompletionCallback cb;
+ disk_cache::Backend* cache;
+ int rv =
+ context.http_transaction_factory()->GetCache()->GetBackend(
+ &cache, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+
+ std::string key("the key");
+ disk_cache::Entry* entry;
+ rv = cache->CreateEntry(key, &entry, cb.callback());
+ ASSERT_EQ(OK, cb.GetResult(rv));
+
+ // RESPONSE_INFO_TRUNCATED defined on response_info.cc
+ int flags = 1 << 12;
+ WriteHeaders(entry, flags, "something");
+ entry->Close();
+
+ std::string data;
+ TestCompletionCallback cb1;
+ rv = helper.GetEntryInfoHTML(key, &context, &data, cb1.callback());
+ EXPECT_EQ(OK, cb1.GetResult(rv));
+
+ EXPECT_NE(std::string::npos, data.find("RESPONSE_INFO_TRUNCATED"));
+}
+
+} // namespace net