| // Copyright 2014 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/http/http_transaction_test_util.h" |
| |
| #include <algorithm> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "net/base/ip_address.h" |
| #include "net/base/ip_endpoint.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/network_isolation_key.h" |
| #include "net/base/schemeful_site.h" |
| #include "net/cert/x509_certificate.h" |
| #include "net/disk_cache/disk_cache.h" |
| #include "net/http/http_cache.h" |
| #include "net/http/http_request_info.h" |
| #include "net/http/http_response_info.h" |
| #include "net/http/http_transaction.h" |
| #include "net/log/net_log.h" |
| #include "net/log/net_log_source.h" |
| #include "net/ssl/ssl_private_key.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| |
| namespace net { |
| |
| namespace { |
| using MockTransactionMap = |
| std::unordered_map<std::string, const MockTransaction*>; |
| static MockTransactionMap mock_transactions; |
| } // namespace |
| |
| TransportInfo DefaultTransportInfo() { |
| return TransportInfo(TransportType::kDirect, |
| IPEndPoint(IPAddress::IPv4Localhost(), 80), ""); |
| } |
| |
| //----------------------------------------------------------------------------- |
| // mock transaction data |
| |
| const MockTransaction kSimpleGET_Transaction = { |
| "http://www.google.com/", |
| "GET", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| DefaultTransportInfo(), |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| {}, |
| absl::nullopt, |
| absl::nullopt, |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK, |
| }; |
| |
| const MockTransaction kSimplePOST_Transaction = { |
| "http://bugdatabase.com/edit", |
| "POST", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| DefaultTransportInfo(), |
| "HTTP/1.1 200 OK", |
| "", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| {}, |
| absl::nullopt, |
| absl::nullopt, |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK, |
| }; |
| |
| const MockTransaction kTypicalGET_Transaction = { |
| "http://www.example.com/~foo/bar.html", |
| "GET", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| DefaultTransportInfo(), |
| "HTTP/1.1 200 OK", |
| "Date: Wed, 28 Nov 2007 09:40:09 GMT\n" |
| "Last-Modified: Wed, 28 Nov 2007 00:40:09 GMT\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| {}, |
| absl::nullopt, |
| absl::nullopt, |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK, |
| }; |
| |
| const MockTransaction kETagGET_Transaction = { |
| "http://www.google.com/foopy", |
| "GET", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| DefaultTransportInfo(), |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n" |
| "Etag: \"foopy\"\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| {}, |
| absl::nullopt, |
| absl::nullopt, |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK, |
| }; |
| |
| const MockTransaction kRangeGET_Transaction = { |
| "http://www.google.com/", |
| "GET", |
| base::Time(), |
| "Range: 0-100\r\n", |
| LOAD_NORMAL, |
| DefaultTransportInfo(), |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| {}, |
| absl::nullopt, |
| absl::nullopt, |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK, |
| }; |
| |
| static const MockTransaction* const kBuiltinMockTransactions[] = { |
| &kSimpleGET_Transaction, |
| &kSimplePOST_Transaction, |
| &kTypicalGET_Transaction, |
| &kETagGET_Transaction, |
| &kRangeGET_Transaction |
| }; |
| |
| const MockTransaction* FindMockTransaction(const GURL& url) { |
| // look for overrides: |
| MockTransactionMap::const_iterator it = mock_transactions.find(url.spec()); |
| if (it != mock_transactions.end()) |
| return it->second; |
| |
| // look for builtins: |
| for (const auto* transaction : kBuiltinMockTransactions) { |
| if (url == GURL(transaction->url)) |
| return transaction; |
| } |
| return nullptr; |
| } |
| |
| void AddMockTransaction(const MockTransaction* trans) { |
| mock_transactions[GURL(trans->url).spec()] = trans; |
| } |
| |
| void RemoveMockTransaction(const MockTransaction* trans) { |
| mock_transactions.erase(GURL(trans->url).spec()); |
| } |
| |
| MockHttpRequest::MockHttpRequest(const MockTransaction& t) { |
| url = GURL(t.url); |
| method = t.method; |
| extra_headers.AddHeadersFromString(t.request_headers); |
| load_flags = t.load_flags; |
| SchemefulSite site(url); |
| network_isolation_key = NetworkIsolationKey(site, site); |
| network_anonymization_key = NetworkAnonymizationKey(site, site); |
| fps_cache_filter = t.fps_cache_filter; |
| browser_run_id = t.browser_run_id; |
| } |
| |
| std::string MockHttpRequest::CacheKey() { |
| return *HttpCache::GenerateCacheKeyForRequest(this); |
| } |
| |
| //----------------------------------------------------------------------------- |
| |
| // static |
| int TestTransactionConsumer::quit_counter_ = 0; |
| |
| TestTransactionConsumer::TestTransactionConsumer( |
| RequestPriority priority, |
| HttpTransactionFactory* factory) { |
| // Disregard the error code. |
| factory->CreateTransaction(priority, &trans_); |
| ++quit_counter_; |
| } |
| |
| TestTransactionConsumer::~TestTransactionConsumer() = default; |
| |
| void TestTransactionConsumer::Start(const HttpRequestInfo* request, |
| const NetLogWithSource& net_log) { |
| state_ = State::kStarting; |
| int result = |
| trans_->Start(request, |
| base::BindOnce(&TestTransactionConsumer::OnIOComplete, |
| base::Unretained(this)), |
| net_log); |
| if (result != ERR_IO_PENDING) |
| DidStart(result); |
| } |
| |
| void TestTransactionConsumer::DidStart(int result) { |
| if (result != OK) { |
| DidFinish(result); |
| } else { |
| Read(); |
| } |
| } |
| |
| void TestTransactionConsumer::DidRead(int result) { |
| if (result <= 0) { |
| DidFinish(result); |
| } else { |
| content_.append(read_buf_->data(), result); |
| Read(); |
| } |
| } |
| |
| void TestTransactionConsumer::DidFinish(int result) { |
| state_ = State::kDone; |
| error_ = result; |
| if (--quit_counter_ == 0) |
| base::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| } |
| |
| void TestTransactionConsumer::Read() { |
| state_ = State::kReading; |
| read_buf_ = base::MakeRefCounted<IOBuffer>(1024); |
| int result = |
| trans_->Read(read_buf_.get(), 1024, |
| base::BindOnce(&TestTransactionConsumer::OnIOComplete, |
| base::Unretained(this))); |
| if (result != ERR_IO_PENDING) |
| DidRead(result); |
| } |
| |
| void TestTransactionConsumer::OnIOComplete(int result) { |
| switch (state_) { |
| case State::kStarting: |
| DidStart(result); |
| break; |
| case State::kReading: |
| DidRead(result); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| MockNetworkTransaction::MockNetworkTransaction(RequestPriority priority, |
| MockNetworkLayer* factory) |
| : priority_(priority), transaction_factory_(factory->AsWeakPtr()) {} |
| |
| MockNetworkTransaction::~MockNetworkTransaction() { |
| // Use request_ as in ~HttpNetworkTransaction to make sure its valid and not |
| // already freed by the consumer. Only check till Read is invoked since |
| // HttpNetworkTransaction sets request_ to nullptr when Read is invoked. |
| // See crbug.com/734037. |
| if (request_ && !reading_) |
| DCHECK(request_->load_flags >= 0); |
| } |
| |
| int MockNetworkTransaction::Start(const HttpRequestInfo* request, |
| CompletionOnceCallback callback, |
| const NetLogWithSource& net_log) { |
| if (request_) |
| return ERR_FAILED; |
| |
| request_ = request; |
| return StartInternal(request, std::move(callback), net_log); |
| } |
| |
| int MockNetworkTransaction::RestartIgnoringLastError( |
| CompletionOnceCallback callback) { |
| return ERR_FAILED; |
| } |
| |
| int MockNetworkTransaction::RestartWithCertificate( |
| scoped_refptr<X509Certificate> client_cert, |
| scoped_refptr<SSLPrivateKey> client_private_key, |
| CompletionOnceCallback callback) { |
| return ERR_FAILED; |
| } |
| |
| int MockNetworkTransaction::RestartWithAuth(const AuthCredentials& credentials, |
| CompletionOnceCallback callback) { |
| if (!IsReadyToRestartForAuth()) |
| return ERR_FAILED; |
| |
| HttpRequestInfo auth_request_info = *request_; |
| auth_request_info.extra_headers.SetHeader("Authorization", "Bar"); |
| |
| // Let the MockTransactionHandler worry about this: the only way for this |
| // test to succeed is by using an explicit handler for the transaction so |
| // that server behavior can be simulated. |
| return StartInternal(&auth_request_info, std::move(callback), |
| NetLogWithSource()); |
| } |
| |
| void MockNetworkTransaction::PopulateNetErrorDetails( |
| NetErrorDetails* /*details*/) const { |
| NOTIMPLEMENTED(); |
| } |
| |
| bool MockNetworkTransaction::IsReadyToRestartForAuth() { |
| if (!request_) |
| return false; |
| |
| if (!request_->extra_headers.HasHeader("X-Require-Mock-Auth")) |
| return false; |
| |
| // Allow the mock server to decide whether authentication is required or not. |
| std::string status_line = response_.headers->GetStatusLine(); |
| return status_line.find(" 401 ") != std::string::npos || |
| status_line.find(" 407 ") != std::string::npos; |
| } |
| |
| int MockNetworkTransaction::Read(net::IOBuffer* buf, |
| int buf_len, |
| CompletionOnceCallback callback) { |
| const MockTransaction* t = FindMockTransaction(request_->url); |
| DCHECK(t); |
| |
| CHECK(!done_reading_called_); |
| reading_ = true; |
| |
| int num = t->read_return_code; |
| |
| if (OK == num) { |
| if (read_handler_) { |
| num = (*read_handler_)(content_length_, data_cursor_, buf, buf_len); |
| data_cursor_ += num; |
| } else { |
| int data_len = static_cast<int>(data_.size()); |
| num = std::min(static_cast<int64_t>(buf_len), data_len - data_cursor_); |
| if (test_mode_ & TEST_MODE_SLOW_READ) |
| num = std::min(num, 1); |
| if (num) { |
| memcpy(buf->data(), data_.data() + data_cursor_, num); |
| data_cursor_ += num; |
| } |
| } |
| } |
| |
| if (test_mode_ & TEST_MODE_SYNC_NET_READ) |
| return num; |
| |
| CallbackLater(std::move(callback), num); |
| return ERR_IO_PENDING; |
| } |
| |
| void MockNetworkTransaction::StopCaching() { |
| if (transaction_factory_.get()) |
| transaction_factory_->TransactionStopCaching(); |
| } |
| |
| int64_t MockNetworkTransaction::GetTotalReceivedBytes() const { |
| return received_bytes_; |
| } |
| |
| int64_t MockNetworkTransaction::GetTotalSentBytes() const { |
| return sent_bytes_; |
| } |
| |
| void MockNetworkTransaction::DoneReading() { |
| CHECK(!done_reading_called_); |
| done_reading_called_ = true; |
| if (transaction_factory_.get()) |
| transaction_factory_->TransactionDoneReading(); |
| } |
| |
| const HttpResponseInfo* MockNetworkTransaction::GetResponseInfo() const { |
| return &response_; |
| } |
| |
| LoadState MockNetworkTransaction::GetLoadState() const { |
| if (data_cursor_) |
| return LOAD_STATE_READING_RESPONSE; |
| return LOAD_STATE_IDLE; |
| } |
| |
| void MockNetworkTransaction::SetQuicServerInfo( |
| QuicServerInfo* quic_server_info) { |
| } |
| |
| bool MockNetworkTransaction::GetLoadTimingInfo( |
| LoadTimingInfo* load_timing_info) const { |
| if (socket_log_id_ != NetLogSource::kInvalidId) { |
| // The minimal set of times for a request that gets a response, assuming it |
| // gets a new socket. |
| load_timing_info->socket_reused = false; |
| load_timing_info->socket_log_id = socket_log_id_; |
| load_timing_info->connect_timing.connect_start = base::TimeTicks::Now(); |
| load_timing_info->connect_timing.connect_end = base::TimeTicks::Now(); |
| load_timing_info->send_start = base::TimeTicks::Now(); |
| load_timing_info->send_end = base::TimeTicks::Now(); |
| } else { |
| // If there's no valid socket ID, just use the generic socket reused values. |
| // No tests currently depend on this, just should not match the values set |
| // by a cache hit. |
| load_timing_info->socket_reused = true; |
| load_timing_info->send_start = base::TimeTicks::Now(); |
| load_timing_info->send_end = base::TimeTicks::Now(); |
| } |
| return true; |
| } |
| |
| bool MockNetworkTransaction::GetRemoteEndpoint(IPEndPoint* endpoint) const { |
| *endpoint = IPEndPoint(IPAddress(127, 0, 0, 1), 80); |
| return true; |
| } |
| |
| void MockNetworkTransaction::SetPriority(RequestPriority priority) { |
| priority_ = priority; |
| } |
| |
| void MockNetworkTransaction::SetWebSocketHandshakeStreamCreateHelper( |
| WebSocketHandshakeStreamBase::CreateHelper* create_helper) { |
| websocket_handshake_stream_create_helper_ = create_helper; |
| } |
| |
| // static |
| const int64_t MockNetworkTransaction::kTotalReceivedBytes = 1000; |
| |
| // static |
| const int64_t MockNetworkTransaction::kTotalSentBytes = 100; |
| |
| int MockNetworkTransaction::StartInternal(const HttpRequestInfo* request, |
| CompletionOnceCallback callback, |
| const NetLogWithSource& net_log) { |
| const MockTransaction* t = FindMockTransaction(request->url); |
| if (!t) |
| return ERR_FAILED; |
| |
| test_mode_ = t->test_mode; |
| |
| // Return immediately if we're returning an error. |
| if (OK != t->start_return_code) { |
| if (test_mode_ & TEST_MODE_SYNC_NET_START) |
| return t->start_return_code; |
| CallbackLater(std::move(callback), t->start_return_code); |
| return ERR_IO_PENDING; |
| } |
| |
| sent_bytes_ = kTotalSentBytes; |
| received_bytes_ = kTotalReceivedBytes; |
| |
| std::string resp_status = t->status; |
| std::string resp_headers = t->response_headers; |
| std::string resp_data = t->data; |
| if (t->handler) |
| (t->handler)(request, &resp_status, &resp_headers, &resp_data); |
| if (t->read_handler) |
| read_handler_ = t->read_handler; |
| |
| std::string header_data = base::StringPrintf( |
| "%s\n%s\n", resp_status.c_str(), resp_headers.c_str()); |
| std::replace(header_data.begin(), header_data.end(), '\n', '\0'); |
| |
| response_.request_time = transaction_factory_->Now(); |
| if (!t->request_time.is_null()) |
| response_.request_time = t->request_time; |
| |
| response_.was_cached = false; |
| response_.network_accessed = true; |
| response_.remote_endpoint = t->transport_info.endpoint; |
| response_.was_fetched_via_proxy = |
| t->transport_info.type == TransportType::kProxied; |
| |
| response_.response_time = transaction_factory_->Now(); |
| if (!t->response_time.is_null()) |
| response_.response_time = t->response_time; |
| |
| response_.headers = base::MakeRefCounted<HttpResponseHeaders>(header_data); |
| response_.ssl_info.cert = t->cert; |
| response_.ssl_info.cert_status = t->cert_status; |
| response_.ssl_info.connection_status = t->ssl_connection_status; |
| response_.dns_aliases = t->dns_aliases; |
| data_ = resp_data; |
| content_length_ = response_.headers->GetContentLength(); |
| |
| if (net_log.net_log()) |
| socket_log_id_ = net_log.net_log()->NextID(); |
| |
| if (request_->load_flags & LOAD_PREFETCH) |
| response_.unused_since_prefetch = true; |
| |
| if (request_->load_flags & LOAD_RESTRICTED_PREFETCH) { |
| DCHECK(response_.unused_since_prefetch); |
| response_.restricted_prefetch = true; |
| } |
| |
| // Pause and resume. |
| if (!before_network_start_callback_.is_null()) { |
| bool defer = false; |
| std::move(before_network_start_callback_).Run(&defer); |
| if (defer) { |
| resume_start_callback_ = std::move(callback); |
| return net::ERR_IO_PENDING; |
| } |
| } |
| |
| if (test_mode_ & TEST_MODE_SYNC_NET_START) |
| return OK; |
| |
| int result = OK; |
| if (!connected_callback_.is_null()) { |
| result = connected_callback_.Run(t->transport_info, base::DoNothing()); |
| } |
| |
| CallbackLater(std::move(callback), result); |
| return ERR_IO_PENDING; |
| } |
| |
| void MockNetworkTransaction::SetBeforeNetworkStartCallback( |
| BeforeNetworkStartCallback callback) { |
| before_network_start_callback_ = std::move(callback); |
| } |
| |
| void MockNetworkTransaction::SetConnectedCallback( |
| const ConnectedCallback& callback) { |
| connected_callback_ = callback; |
| } |
| |
| int MockNetworkTransaction::ResumeNetworkStart() { |
| DCHECK(!resume_start_callback_.is_null()); |
| CallbackLater(std::move(resume_start_callback_), OK); |
| return ERR_IO_PENDING; |
| } |
| |
| ConnectionAttempts MockNetworkTransaction::GetConnectionAttempts() const { |
| // TODO(ricea): Replace this with a proper implementation if needed. |
| return {}; |
| } |
| |
| void MockNetworkTransaction::CloseConnectionOnDestruction() { |
| NOTIMPLEMENTED(); |
| } |
| |
| void MockNetworkTransaction::CallbackLater(CompletionOnceCallback callback, |
| int result) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, |
| base::BindOnce(&MockNetworkTransaction::RunCallback, |
| weak_factory_.GetWeakPtr(), std::move(callback), result)); |
| } |
| |
| void MockNetworkTransaction::RunCallback(CompletionOnceCallback callback, |
| int result) { |
| std::move(callback).Run(result); |
| } |
| |
| MockNetworkLayer::MockNetworkLayer() = default; |
| |
| MockNetworkLayer::~MockNetworkLayer() = default; |
| |
| void MockNetworkLayer::TransactionDoneReading() { |
| CHECK(!done_reading_called_); |
| done_reading_called_ = true; |
| } |
| |
| void MockNetworkLayer::TransactionStopCaching() { |
| stop_caching_called_ = true; |
| } |
| |
| void MockNetworkLayer::ResetTransactionCount() { |
| transaction_count_ = 0; |
| } |
| |
| int MockNetworkLayer::CreateTransaction( |
| RequestPriority priority, |
| std::unique_ptr<HttpTransaction>* trans) { |
| transaction_count_++; |
| last_create_transaction_priority_ = priority; |
| auto mock_transaction = |
| std::make_unique<MockNetworkTransaction>(priority, this); |
| last_transaction_ = mock_transaction->AsWeakPtr(); |
| *trans = std::move(mock_transaction); |
| return OK; |
| } |
| |
| HttpCache* MockNetworkLayer::GetCache() { |
| return nullptr; |
| } |
| |
| HttpNetworkSession* MockNetworkLayer::GetSession() { |
| return nullptr; |
| } |
| |
| void MockNetworkLayer::SetClock(base::Clock* clock) { |
| DCHECK(!clock_); |
| clock_ = clock; |
| } |
| |
| base::Time MockNetworkLayer::Now() { |
| if (clock_) |
| return clock_->Now(); |
| return base::Time::Now(); |
| } |
| |
| //----------------------------------------------------------------------------- |
| // helpers |
| |
| int ReadTransaction(HttpTransaction* trans, std::string* result) { |
| int rv; |
| |
| std::string content; |
| do { |
| TestCompletionCallback callback; |
| scoped_refptr<IOBuffer> buf = base::MakeRefCounted<IOBuffer>(256); |
| rv = trans->Read(buf.get(), 256, callback.callback()); |
| if (rv == ERR_IO_PENDING) { |
| rv = callback.WaitForResult(); |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| if (rv > 0) |
| content.append(buf->data(), rv); |
| else if (rv < 0) |
| return rv; |
| } while (rv > 0); |
| |
| result->swap(content); |
| return OK; |
| } |
| |
| //----------------------------------------------------------------------------- |
| // connected callback handler |
| |
| ConnectedHandler::ConnectedHandler() = default; |
| ConnectedHandler::~ConnectedHandler() = default; |
| |
| ConnectedHandler::ConnectedHandler(const ConnectedHandler&) = default; |
| ConnectedHandler& ConnectedHandler::operator=(const ConnectedHandler&) = |
| default; |
| ConnectedHandler::ConnectedHandler(ConnectedHandler&&) = default; |
| ConnectedHandler& ConnectedHandler::operator=(ConnectedHandler&&) = default; |
| |
| int ConnectedHandler::OnConnected(const TransportInfo& info, |
| CompletionOnceCallback callback) { |
| transports_.push_back(info); |
| if (run_callback_) { |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( |
| FROM_HERE, base::BindOnce(std::move(callback), result_)); |
| return net::ERR_IO_PENDING; |
| } |
| return result_; |
| } |
| |
| } // namespace net |