| // Copyright 2014 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/http/http_transaction_test_util.h" |
| |
| #include <algorithm> |
| #include <unordered_map> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/location.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/clock.h" |
| #include "base/time/time.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/base/net_errors.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/log/net_log_with_source.h" |
| #include "starboard/memory.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| using MockTransactionMap = |
| std::unordered_map<std::string, const MockTransaction*>; |
| static MockTransactionMap mock_transactions; |
| } // namespace |
| |
| //----------------------------------------------------------------------------- |
| // mock transaction data |
| |
| const MockTransaction kSimpleGET_Transaction = { |
| "http://www.google.com/", |
| "GET", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK}; |
| |
| const MockTransaction kSimplePOST_Transaction = { |
| "http://bugdatabase.com/edit", |
| "POST", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| "HTTP/1.1 200 OK", |
| "", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| 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, |
| "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>", |
| TEST_MODE_NORMAL, |
| nullptr, |
| nullptr, |
| nullptr, |
| 0, |
| 0, |
| OK, |
| OK}; |
| |
| const MockTransaction kETagGET_Transaction = { |
| "http://www.google.com/foopy", |
| "GET", |
| base::Time(), |
| "", |
| LOAD_NORMAL, |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n" |
| "Etag: \"foopy\"\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| 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, |
| "HTTP/1.1 200 OK", |
| "Cache-Control: max-age=10000\n", |
| base::Time(), |
| "<html><body>Google Blah Blah</body></html>", |
| 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 (size_t i = 0; i < arraysize(kBuiltinMockTransactions); ++i) { |
| if (url == GURL(kBuiltinMockTransactions[i]->url)) |
| return kBuiltinMockTransactions[i]; |
| } |
| 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; |
| } |
| |
| //----------------------------------------------------------------------------- |
| |
| // static |
| int TestTransactionConsumer::quit_counter_ = 0; |
| |
| TestTransactionConsumer::TestTransactionConsumer( |
| RequestPriority priority, |
| HttpTransactionFactory* factory) |
| : state_(IDLE), error_(OK) { |
| // Disregard the error code. |
| factory->CreateTransaction(priority, &trans_); |
| ++quit_counter_; |
| } |
| |
| TestTransactionConsumer::~TestTransactionConsumer() = default; |
| |
| void TestTransactionConsumer::Start(const HttpRequestInfo* request, |
| const NetLogWithSource& net_log) { |
| state_ = STARTING; |
| int result = trans_->Start( |
| request, base::Bind(&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_ = DONE; |
| error_ = result; |
| if (--quit_counter_ == 0) |
| base::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| } |
| |
| void TestTransactionConsumer::Read() { |
| state_ = READING; |
| read_buf_ = base::MakeRefCounted<IOBuffer>(1024); |
| int result = trans_->Read(read_buf_.get(), |
| 1024, |
| base::Bind(&TestTransactionConsumer::OnIOComplete, |
| base::Unretained(this))); |
| if (result != ERR_IO_PENDING) |
| DidRead(result); |
| } |
| |
| void TestTransactionConsumer::OnIOComplete(int result) { |
| switch (state_) { |
| case STARTING: |
| DidStart(result); |
| break; |
| case READING: |
| DidRead(result); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| MockNetworkTransaction::MockNetworkTransaction(RequestPriority priority, |
| MockNetworkLayer* factory) |
| : request_(nullptr), |
| data_cursor_(0), |
| content_length_(0), |
| priority_(priority), |
| read_handler_(nullptr), |
| websocket_handshake_stream_create_helper_(nullptr), |
| transaction_factory_(factory->AsWeakPtr()), |
| received_bytes_(0), |
| sent_bytes_(0), |
| socket_log_id_(NetLogSource::kInvalidId), |
| done_reading_called_(false), |
| reading_(false), |
| weak_factory_(this) {} |
| |
| 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(); |
| } |
| |
| bool MockNetworkTransaction::GetFullRequestHeaders( |
| HttpRequestHeaders* headers) const { |
| return false; |
| } |
| |
| 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_.response_time = transaction_factory_->Now(); |
| if (!t->response_time.is_null()) |
| response_.response_time = t->response_time; |
| |
| response_.headers = new HttpResponseHeaders(header_data); |
| response_.vary_data.Init(*request, *response_.headers.get()); |
| response_.ssl_info.cert = t->cert; |
| response_.ssl_info.cert_status = t->cert_status; |
| response_.ssl_info.connection_status = t->ssl_connection_status; |
| 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; |
| |
| // Pause and resume. |
| if (!before_network_start_callback_.is_null()) { |
| bool defer = false; |
| 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; |
| |
| CallbackLater(std::move(callback), OK); |
| return ERR_IO_PENDING; |
| } |
| |
| void MockNetworkTransaction::SetBeforeNetworkStartCallback( |
| const BeforeNetworkStartCallback& callback) { |
| before_network_start_callback_ = callback; |
| } |
| |
| void MockNetworkTransaction::SetBeforeHeadersSentCallback( |
| const BeforeHeadersSentCallback& callback) {} |
| |
| int MockNetworkTransaction::ResumeNetworkStart() { |
| DCHECK(!resume_start_callback_.is_null()); |
| CallbackLater(std::move(resume_start_callback_), OK); |
| return ERR_IO_PENDING; |
| } |
| |
| void MockNetworkTransaction::GetConnectionAttempts( |
| ConnectionAttempts* out) const { |
| NOTIMPLEMENTED(); |
| } |
| |
| void MockNetworkTransaction::CallbackLater(CompletionOnceCallback callback, |
| int result) { |
| base::ThreadTaskRunnerHandle::Get()->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() |
| : transaction_count_(0), |
| done_reading_called_(false), |
| stop_caching_called_(false), |
| last_create_transaction_priority_(DEFAULT_PRIORITY), |
| clock_(nullptr) { |
| } |
| |
| 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; |
| std::unique_ptr<MockNetworkTransaction> mock_transaction( |
| new MockNetworkTransaction(priority, this)); |
| last_transaction_ = mock_transaction->AsWeakPtr(); |
| *trans = std::move(mock_transaction); |
| return OK; |
| } |
| |
| HttpCache* MockNetworkLayer::GetCache() { |
| return NULL; |
| } |
| |
| HttpNetworkSession* MockNetworkLayer::GetSession() { |
| return NULL; |
| } |
| |
| 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; |
| |
| TestCompletionCallback callback; |
| |
| std::string content; |
| do { |
| 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; |
| } |
| |
| } // namespace net |