| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "storage/browser/blob/blob_impl.h" |
| |
| #include <limits> |
| #include <memory> |
| #include <string> |
| #include <utility> |
| |
| #include "base/bind.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/test/task_environment.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "mojo/public/cpp/system/data_pipe_drainer.h" |
| #include "net/base/net_errors.h" |
| #include "storage/browser/blob/blob_data_builder.h" |
| #include "storage/browser/blob/blob_data_handle.h" |
| #include "storage/browser/blob/blob_storage_context.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace storage { |
| |
| namespace { |
| |
| class DataPipeReader : public mojo::DataPipeDrainer::Client { |
| public: |
| DataPipeReader(std::string* data_out, base::OnceClosure done_callback) |
| : data_out_(data_out), done_callback_(std::move(done_callback)) {} |
| |
| void OnDataAvailable(const void* data, size_t num_bytes) override { |
| data_out_->append(static_cast<const char*>(data), num_bytes); |
| } |
| |
| void OnDataComplete() override { std::move(done_callback_).Run(); } |
| |
| private: |
| raw_ptr<std::string> data_out_; |
| base::OnceClosure done_callback_; |
| }; |
| |
| class MockBlobReaderClient : public blink::mojom::BlobReaderClient { |
| public: |
| void OnCalculatedSize(uint64_t total_size, |
| uint64_t expected_content_size) override { |
| total_size_ = total_size; |
| expected_content_size_ = expected_content_size; |
| calculated_size_ = true; |
| } |
| |
| void OnComplete(int32_t status, uint64_t data_length) override { |
| status_ = static_cast<net::Error>(status); |
| data_length_ = data_length; |
| completed_ = true; |
| } |
| |
| bool calculated_size_ = false; |
| uint64_t total_size_ = 0; |
| uint64_t expected_content_size_ = 0; |
| bool completed_ = false; |
| net::Error status_ = net::OK; |
| uint64_t data_length_ = 0; |
| }; |
| |
| } // namespace |
| |
| class BlobImplTest : public testing::Test { |
| public: |
| void SetUp() override { context_ = std::make_unique<BlobStorageContext>(); } |
| |
| std::unique_ptr<BlobDataHandle> CreateBlobFromString( |
| const std::string& uuid, |
| const std::string& contents) { |
| auto builder = std::make_unique<BlobDataBuilder>(uuid); |
| builder->set_content_type("text/plain"); |
| builder->AppendData(contents); |
| return context_->AddFinishedBlob(std::move(builder)); |
| } |
| |
| std::string UUIDFromBlob(blink::mojom::Blob* blob) { |
| base::RunLoop loop; |
| std::string received_uuid; |
| blob->GetInternalUUID(base::BindOnce( |
| [](base::OnceClosure quit_closure, std::string* uuid_out, |
| const std::string& uuid) { |
| *uuid_out = uuid; |
| std::move(quit_closure).Run(); |
| }, |
| loop.QuitClosure(), &received_uuid)); |
| loop.Run(); |
| return received_uuid; |
| } |
| |
| std::string ReadDataPipe(mojo::ScopedDataPipeConsumerHandle pipe) { |
| base::RunLoop loop; |
| std::string data; |
| DataPipeReader reader(&data, loop.QuitClosure()); |
| mojo::DataPipeDrainer drainer(&reader, std::move(pipe)); |
| loop.Run(); |
| return data; |
| } |
| |
| protected: |
| base::test::TaskEnvironment task_environment_; |
| std::unique_ptr<BlobStorageContext> context_; |
| }; |
| |
| TEST_F(BlobImplTest, GetInternalUUID) { |
| const std::string kId = "id"; |
| auto handle = CreateBlobFromString(kId, "hello world"); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| auto blob = |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| EXPECT_EQ(kId, UUIDFromBlob(blob.get())); |
| EXPECT_EQ(kId, UUIDFromBlob(remote.get())); |
| } |
| |
| TEST_F(BlobImplTest, CloneAndLifetime) { |
| const std::string kId = "id"; |
| auto handle = CreateBlobFromString(kId, "hello world"); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| auto blob = |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| EXPECT_EQ(kId, UUIDFromBlob(remote.get())); |
| |
| // Blob should exist in registry as long as connection is alive. |
| EXPECT_TRUE(context_->registry().HasEntry(kId)); |
| EXPECT_TRUE(blob); |
| |
| mojo::Remote<blink::mojom::Blob> clone; |
| blob->Clone(clone.BindNewPipeAndPassReceiver()); |
| EXPECT_EQ(kId, UUIDFromBlob(clone.get())); |
| clone.FlushForTesting(); |
| |
| remote.reset(); |
| blob->FlushForTesting(); |
| EXPECT_TRUE(context_->registry().HasEntry(kId)); |
| EXPECT_TRUE(blob); |
| |
| clone.reset(); |
| blob->FlushForTesting(); |
| EXPECT_FALSE(context_->registry().HasEntry(kId)); |
| EXPECT_FALSE(blob); |
| } |
| |
| TEST_F(BlobImplTest, ReadAll) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| |
| remote->ReadAll(std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ(kContents, received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_TRUE(client.calculated_size_); |
| EXPECT_EQ(kContents.size(), client.total_size_); |
| EXPECT_EQ(kContents.size(), client.expected_content_size_); |
| |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::OK, client.status_); |
| EXPECT_EQ(kContents.size(), client.data_length_); |
| } |
| |
| TEST_F(BlobImplTest, ReadAll_WithoutClient) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadAll(std::move(producer_handle), mojo::NullRemote()); |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ(kContents, received); |
| } |
| |
| TEST_F(BlobImplTest, ReadAll_BrokenBlob) { |
| const std::string kId = "id"; |
| auto handle = context_->AddBrokenBlob( |
| kId, "", "", BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadAll(std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ("", received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_FALSE(client.calculated_size_); |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::ERR_FAILED, client.status_); |
| EXPECT_EQ(0u, client.data_length_); |
| } |
| |
| TEST_F(BlobImplTest, ReadRange) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadRange(2, 5, std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ(kContents.substr(2, 5), received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_TRUE(client.calculated_size_); |
| EXPECT_EQ(kContents.size(), client.total_size_); |
| EXPECT_EQ(5u, client.expected_content_size_); |
| |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::OK, client.status_); |
| EXPECT_EQ(5u, client.data_length_); |
| } |
| |
| TEST_F(BlobImplTest, ReadRange_WithoutClient) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadRange(2, 5, std::move(producer_handle), mojo::NullRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ(kContents.substr(2, 5), received); |
| } |
| |
| TEST_F(BlobImplTest, ReadRange_TooLargeLength) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadRange(2, 15, std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ(kContents.substr(2, 15), received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_TRUE(client.calculated_size_); |
| EXPECT_EQ(kContents.size(), client.total_size_); |
| EXPECT_EQ(kContents.size() - 2, client.expected_content_size_); |
| |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::OK, client.status_); |
| EXPECT_EQ(kContents.size() - 2, client.data_length_); |
| } |
| |
| TEST_F(BlobImplTest, ReadRange_UnboundedLength) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadRange(2, std::numeric_limits<uint64_t>::max(), |
| std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ(kContents.substr(2, kContents.size()), received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_TRUE(client.calculated_size_); |
| EXPECT_EQ(kContents.size(), client.total_size_); |
| EXPECT_EQ(kContents.size() - 2, client.expected_content_size_); |
| |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::OK, client.status_); |
| EXPECT_EQ(kContents.size() - 2, client.data_length_); |
| } |
| |
| TEST_F(BlobImplTest, ReadRange_BrokenBlob) { |
| const std::string kId = "id"; |
| auto handle = context_->AddBrokenBlob( |
| kId, "", "", BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadRange(2, 5, std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ("", received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_FALSE(client.calculated_size_); |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::ERR_FAILED, client.status_); |
| EXPECT_EQ(0u, client.data_length_); |
| } |
| |
| TEST_F(BlobImplTest, ReadRange_InvalidRange) { |
| const std::string kId = "id"; |
| const std::string kContents = "hello world"; |
| auto handle = CreateBlobFromString(kId, kContents); |
| |
| mojo::Remote<blink::mojom::Blob> remote; |
| BlobImpl::Create(std::move(handle), remote.BindNewPipeAndPassReceiver()); |
| |
| MockBlobReaderClient client; |
| mojo::Receiver<blink::mojom::BlobReaderClient> client_receiver(&client); |
| |
| base::RunLoop loop; |
| mojo::ScopedDataPipeProducerHandle producer_handle; |
| mojo::ScopedDataPipeConsumerHandle consumer_handle; |
| ASSERT_EQ(mojo::CreateDataPipe(nullptr, producer_handle, consumer_handle), |
| MOJO_RESULT_OK); |
| remote->ReadRange(15, 4, std::move(producer_handle), |
| client_receiver.BindNewPipeAndPassRemote()); |
| |
| std::string received = ReadDataPipe(std::move(consumer_handle)); |
| EXPECT_EQ("", received); |
| |
| client_receiver.FlushForTesting(); |
| EXPECT_FALSE(client.calculated_size_); |
| EXPECT_TRUE(client.completed_); |
| EXPECT_EQ(net::ERR_REQUEST_RANGE_NOT_SATISFIABLE, client.status_); |
| EXPECT_EQ(0u, client.data_length_); |
| } |
| |
| } // namespace storage |