| // 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/http/http_response_body_drainer.h" |
| |
| #include <stdint.h> |
| |
| #include <cstring> |
| |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/compiler_specific.h" |
| #include "base/location.h" |
| #include "base/macros.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "net/base/completion_once_callback.h" |
| #include "net/base/io_buffer.h" |
| #include "net/base/net_errors.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cert/ct_policy_enforcer.h" |
| #include "net/cert/mock_cert_verifier.h" |
| #include "net/cert/multi_log_ct_verifier.h" |
| #include "net/http/http_network_session.h" |
| #include "net/http/http_server_properties_impl.h" |
| #include "net/http/http_stream.h" |
| #include "net/http/transport_security_state.h" |
| #include "net/proxy_resolution/proxy_resolution_service.h" |
| #include "net/ssl/ssl_config_service_defaults.h" |
| #include "net/test/test_with_scoped_task_environment.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const int kMagicChunkSize = 1024; |
| static_assert((HttpResponseBodyDrainer::kDrainBodyBufferSize % |
| kMagicChunkSize) == 0, |
| "chunk size needs to divide evenly into buffer size"); |
| |
| class CloseResultWaiter { |
| public: |
| CloseResultWaiter() |
| : result_(false), |
| have_result_(false), |
| waiting_for_result_(false) {} |
| |
| int WaitForResult() { |
| CHECK(!waiting_for_result_); |
| while (!have_result_) { |
| waiting_for_result_ = true; |
| base::RunLoop().Run(); |
| waiting_for_result_ = false; |
| } |
| return result_; |
| } |
| |
| void set_result(bool result) { |
| result_ = result; |
| have_result_ = true; |
| if (waiting_for_result_) |
| base::RunLoop::QuitCurrentWhenIdleDeprecated(); |
| } |
| |
| private: |
| int result_; |
| bool have_result_; |
| bool waiting_for_result_; |
| |
| DISALLOW_COPY_AND_ASSIGN(CloseResultWaiter); |
| }; |
| |
| class MockHttpStream : public HttpStream { |
| public: |
| MockHttpStream(CloseResultWaiter* result_waiter) |
| : result_waiter_(result_waiter), |
| buf_len_(0), |
| closed_(false), |
| stall_reads_forever_(false), |
| num_chunks_(0), |
| is_sync_(false), |
| is_last_chunk_zero_size_(false), |
| is_complete_(false), |
| can_reuse_connection_(true), |
| weak_factory_(this) {} |
| ~MockHttpStream() override = default; |
| |
| // HttpStream implementation. |
| int InitializeStream(const HttpRequestInfo* request_info, |
| bool can_send_early, |
| RequestPriority priority, |
| const NetLogWithSource& net_log, |
| CompletionOnceCallback callback) override { |
| return ERR_UNEXPECTED; |
| } |
| int SendRequest(const HttpRequestHeaders& request_headers, |
| HttpResponseInfo* response, |
| CompletionOnceCallback callback) override { |
| return ERR_UNEXPECTED; |
| } |
| int ReadResponseHeaders(CompletionOnceCallback callback) override { |
| return ERR_UNEXPECTED; |
| } |
| |
| bool IsConnectionReused() const override { return false; } |
| void SetConnectionReused() override {} |
| bool CanReuseConnection() const override { return can_reuse_connection_; } |
| int64_t GetTotalReceivedBytes() const override { return 0; } |
| int64_t GetTotalSentBytes() const override { return 0; } |
| bool GetAlternativeService( |
| AlternativeService* alternative_service) const override { |
| return false; |
| } |
| void GetSSLInfo(SSLInfo* ssl_info) override {} |
| void GetSSLCertRequestInfo(SSLCertRequestInfo* cert_request_info) override {} |
| bool GetRemoteEndpoint(IPEndPoint* endpoint) override { return false; } |
| |
| // Mocked API |
| int ReadResponseBody(IOBuffer* buf, |
| int buf_len, |
| CompletionOnceCallback callback) override; |
| void Close(bool not_reusable) override { |
| CHECK(!closed_); |
| closed_ = true; |
| result_waiter_->set_result(not_reusable); |
| } |
| |
| HttpStream* RenewStreamForAuth() override { return nullptr; } |
| |
| bool IsResponseBodyComplete() const override { return is_complete_; } |
| |
| bool GetLoadTimingInfo(LoadTimingInfo* load_timing_info) const override { |
| return false; |
| } |
| |
| void Drain(HttpNetworkSession*) override {} |
| |
| void PopulateNetErrorDetails(NetErrorDetails* details) override { return; } |
| |
| void SetPriority(RequestPriority priority) override {} |
| |
| // Methods to tweak/observer mock behavior: |
| void set_stall_reads_forever() { stall_reads_forever_ = true; } |
| |
| void set_num_chunks(int num_chunks) { num_chunks_ = num_chunks; } |
| |
| void set_sync() { is_sync_ = true; } |
| |
| void set_is_last_chunk_zero_size() { is_last_chunk_zero_size_ = true; } |
| |
| // Sets result value of CanReuseConnection. Defaults to true. |
| void set_can_reuse_connection(bool can_reuse_connection) { |
| can_reuse_connection_ = can_reuse_connection; |
| } |
| |
| void SetRequestHeadersCallback(RequestHeadersCallback callback) override {} |
| |
| private: |
| int ReadResponseBodyImpl(IOBuffer* buf, int buf_len); |
| void CompleteRead(); |
| |
| bool closed() const { return closed_; } |
| |
| CloseResultWaiter* const result_waiter_; |
| scoped_refptr<IOBuffer> user_buf_; |
| CompletionOnceCallback callback_; |
| int buf_len_; |
| bool closed_; |
| bool stall_reads_forever_; |
| int num_chunks_; |
| bool is_sync_; |
| bool is_last_chunk_zero_size_; |
| bool is_complete_; |
| bool can_reuse_connection_; |
| |
| base::WeakPtrFactory<MockHttpStream> weak_factory_; |
| |
| DISALLOW_COPY_AND_ASSIGN(MockHttpStream); |
| }; |
| |
| int MockHttpStream::ReadResponseBody(IOBuffer* buf, |
| int buf_len, |
| CompletionOnceCallback callback) { |
| CHECK(!callback.is_null()); |
| CHECK(callback_.is_null()); |
| CHECK(buf); |
| |
| if (stall_reads_forever_) |
| return ERR_IO_PENDING; |
| |
| if (is_complete_) |
| return ERR_UNEXPECTED; |
| |
| if (!is_sync_) { |
| user_buf_ = buf; |
| buf_len_ = buf_len; |
| callback_ = std::move(callback); |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::BindOnce(&MockHttpStream::CompleteRead, |
| weak_factory_.GetWeakPtr())); |
| return ERR_IO_PENDING; |
| } else { |
| return ReadResponseBodyImpl(buf, buf_len); |
| } |
| } |
| |
| int MockHttpStream::ReadResponseBodyImpl(IOBuffer* buf, int buf_len) { |
| if (is_last_chunk_zero_size_ && num_chunks_ == 1) { |
| buf_len = 0; |
| } else { |
| if (buf_len > kMagicChunkSize) |
| buf_len = kMagicChunkSize; |
| std::memset(buf->data(), 1, buf_len); |
| } |
| num_chunks_--; |
| if (!num_chunks_) |
| is_complete_ = true; |
| |
| return buf_len; |
| } |
| |
| void MockHttpStream::CompleteRead() { |
| int result = ReadResponseBodyImpl(user_buf_.get(), buf_len_); |
| user_buf_ = nullptr; |
| base::ResetAndReturn(&callback_).Run(result); |
| } |
| |
| class HttpResponseBodyDrainerTest : public TestWithScopedTaskEnvironment { |
| protected: |
| HttpResponseBodyDrainerTest() |
| : proxy_resolution_service_(ProxyResolutionService::CreateDirect()), |
| ssl_config_service_(new SSLConfigServiceDefaults), |
| http_server_properties_(new HttpServerPropertiesImpl()), |
| session_(CreateNetworkSession()), |
| mock_stream_(new MockHttpStream(&result_waiter_)), |
| drainer_(new HttpResponseBodyDrainer(mock_stream_)) {} |
| |
| ~HttpResponseBodyDrainerTest() override = default; |
| |
| HttpNetworkSession* CreateNetworkSession() { |
| HttpNetworkSession::Context context; |
| context.proxy_resolution_service = proxy_resolution_service_.get(); |
| context.ssl_config_service = ssl_config_service_.get(); |
| context.http_server_properties = http_server_properties_.get(); |
| context.cert_verifier = &cert_verifier_; |
| context.transport_security_state = &transport_security_state_; |
| context.cert_transparency_verifier = &ct_verifier_; |
| context.ct_policy_enforcer = &ct_policy_enforcer_; |
| return new HttpNetworkSession(HttpNetworkSession::Params(), context); |
| } |
| |
| std::unique_ptr<ProxyResolutionService> proxy_resolution_service_; |
| std::unique_ptr<SSLConfigService> ssl_config_service_; |
| std::unique_ptr<HttpServerPropertiesImpl> http_server_properties_; |
| MockCertVerifier cert_verifier_; |
| TransportSecurityState transport_security_state_; |
| MultiLogCTVerifier ct_verifier_; |
| DefaultCTPolicyEnforcer ct_policy_enforcer_; |
| const std::unique_ptr<HttpNetworkSession> session_; |
| CloseResultWaiter result_waiter_; |
| MockHttpStream* const mock_stream_; // Owned by |drainer_|. |
| HttpResponseBodyDrainer* const drainer_; // Deletes itself. |
| }; |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncSingleOK) { |
| mock_stream_->set_num_chunks(1); |
| mock_stream_->set_sync(); |
| drainer_->Start(session_.get()); |
| EXPECT_FALSE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncOK) { |
| mock_stream_->set_num_chunks(3); |
| mock_stream_->set_sync(); |
| drainer_->Start(session_.get()); |
| EXPECT_FALSE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncOK) { |
| mock_stream_->set_num_chunks(3); |
| drainer_->Start(session_.get()); |
| EXPECT_FALSE(result_waiter_.WaitForResult()); |
| } |
| |
| // Test the case when the final chunk is 0 bytes. This can happen when |
| // the final 0-byte chunk of a chunk-encoded http response is read in a last |
| // call to ReadResponseBody, after all data were returned from HttpStream. |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodyAsyncEmptyChunk) { |
| mock_stream_->set_num_chunks(4); |
| mock_stream_->set_is_last_chunk_zero_size(); |
| drainer_->Start(session_.get()); |
| EXPECT_FALSE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodySyncEmptyChunk) { |
| mock_stream_->set_num_chunks(4); |
| mock_stream_->set_sync(); |
| mock_stream_->set_is_last_chunk_zero_size(); |
| drainer_->Start(session_.get()); |
| EXPECT_FALSE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodySizeEqualsDrainBuffer) { |
| mock_stream_->set_num_chunks( |
| HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize); |
| drainer_->Start(session_.get()); |
| EXPECT_FALSE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodyTimeOut) { |
| mock_stream_->set_num_chunks(2); |
| mock_stream_->set_stall_reads_forever(); |
| drainer_->Start(session_.get()); |
| EXPECT_TRUE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, CancelledBySession) { |
| mock_stream_->set_num_chunks(2); |
| mock_stream_->set_stall_reads_forever(); |
| drainer_->Start(session_.get()); |
| // HttpNetworkSession should delete |drainer_|. |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodyTooLarge) { |
| int too_many_chunks = |
| HttpResponseBodyDrainer::kDrainBodyBufferSize / kMagicChunkSize; |
| too_many_chunks += 1; // Now it's too large. |
| |
| mock_stream_->set_num_chunks(too_many_chunks); |
| drainer_->Start(session_.get()); |
| EXPECT_TRUE(result_waiter_.WaitForResult()); |
| } |
| |
| TEST_F(HttpResponseBodyDrainerTest, DrainBodyCantReuse) { |
| mock_stream_->set_num_chunks(1); |
| mock_stream_->set_can_reuse_connection(false); |
| drainer_->Start(session_.get()); |
| EXPECT_TRUE(result_waiter_.WaitForResult()); |
| } |
| |
| } // namespace |
| |
| } // namespace net |