| // Copyright (c) 2015 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 "storage/browser/blob/blob_data_item.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/strings/string_number_conversions.h" |
| #include "mojo/public/cpp/bindings/remote.h" |
| #include "net/base/net_errors.h" |
| #include "services/network/public/cpp/data_pipe_to_source_stream.h" |
| #include "storage/browser/file_system/file_system_context.h" |
| #include "third_party/blink/public/common/blob/blob_utils.h" |
| |
| namespace storage { |
| |
| namespace { |
| const base::FilePath::CharType kFutureFileName[] = |
| FILE_PATH_LITERAL("_future_name_"); |
| } |
| |
| class MojoDataItem : public BlobDataItem::DataHandle { |
| public: |
| explicit MojoDataItem(mojom::BlobDataItemPtr element) |
| : item_(std::move(element)) { |
| reader_.Bind(std::move(item_->reader)); |
| } |
| |
| // BlobDataItem::DataHandle implementation. |
| uint64_t GetSize() const override { return item_->size; } |
| |
| void Read(mojo::ScopedDataPipeProducerHandle producer, |
| uint64_t src_offset, |
| uint64_t bytes_to_read, |
| base::OnceCallback<void(int)> callback) override { |
| reader_->Read(src_offset, bytes_to_read, std::move(producer), |
| std::move(callback)); |
| } |
| |
| uint64_t GetSideDataSize() const override { return item_->side_data_size; } |
| |
| void ReadSideData( |
| base::OnceCallback<void(int, mojo_base::BigBuffer)> callback) override { |
| reader_->ReadSideData(std::move(callback)); |
| } |
| |
| void PrintTo(::std::ostream* os) const override { |
| // TODO(enne): this is tricky to implement, as it's synchronous. |
| // PrintTo should ideally be asynchronous. See: http://crbug.com/809821 |
| *os << "<MojoDataItem>"; |
| } |
| |
| protected: |
| ~MojoDataItem() override = default; |
| |
| mojom::BlobDataItemPtr item_; |
| mojo::Remote<mojom::BlobDataItemReader> reader_; |
| }; |
| |
| BlobDataItem::DataHandle::~DataHandle() = default; |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateBytes( |
| base::span<const uint8_t> bytes) { |
| auto item = |
| base::WrapRefCounted(new BlobDataItem(Type::kBytes, 0, bytes.size())); |
| item->bytes_.assign(bytes.begin(), bytes.end()); |
| return item; |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateBytesDescription( |
| size_t length) { |
| return base::WrapRefCounted( |
| new BlobDataItem(Type::kBytesDescription, 0, length)); |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateFile(base::FilePath path) { |
| return CreateFile(path, 0, blink::BlobUtils::kUnknownSize); |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateFile( |
| base::FilePath path, |
| uint64_t offset, |
| uint64_t length, |
| base::Time expected_modification_time, |
| scoped_refptr<ShareableFileReference> file_ref) { |
| auto item = |
| base::WrapRefCounted(new BlobDataItem(Type::kFile, offset, length)); |
| item->path_ = std::move(path); |
| item->expected_modification_time_ = std::move(expected_modification_time); |
| item->file_ref_ = std::move(file_ref); |
| // TODO(mek): DCHECK(!item->IsFutureFileItem()) when BlobDataBuilder has some |
| // other way of slicing a future file. |
| return item; |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateFutureFile(uint64_t offset, |
| uint64_t length, |
| uint64_t file_id) { |
| auto item = |
| base::WrapRefCounted(new BlobDataItem(Type::kFile, offset, length)); |
| std::string file_id_str = base::NumberToString(file_id); |
| item->path_ = base::FilePath(kFutureFileName) |
| .AddExtension(base::FilePath::StringType( |
| file_id_str.begin(), file_id_str.end())); |
| return item; |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateFileFilesystem( |
| const FileSystemURL& url, |
| uint64_t offset, |
| uint64_t length, |
| base::Time expected_modification_time, |
| scoped_refptr<FileSystemContext> file_system_context) { |
| auto item = base::WrapRefCounted( |
| new BlobDataItem(Type::kFileFilesystem, offset, length)); |
| item->filesystem_url_ = url; |
| item->expected_modification_time_ = std::move(expected_modification_time); |
| item->file_system_context_ = std::move(file_system_context); |
| return item; |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateReadableDataHandle( |
| scoped_refptr<DataHandle> data_handle, |
| uint64_t offset, |
| uint64_t length) { |
| DCHECK(data_handle); |
| DCHECK_LE(offset, data_handle->GetSize()); |
| DCHECK_LE(length, (data_handle->GetSize() - offset)); |
| auto item = base::WrapRefCounted( |
| new BlobDataItem(Type::kReadableDataHandle, offset, length)); |
| item->data_handle_ = std::move(data_handle); |
| return item; |
| } |
| |
| // static |
| scoped_refptr<BlobDataItem> BlobDataItem::CreateMojoDataItem( |
| mojom::BlobDataItemPtr item) { |
| auto handle = base::MakeRefCounted<MojoDataItem>(std::move(item)); |
| auto data_item = base::WrapRefCounted( |
| new BlobDataItem(Type::kReadableDataHandle, 0, handle->GetSize())); |
| DCHECK_GT(handle->GetSize(), 0u); |
| data_item->data_handle_ = std::move(handle); |
| return data_item; |
| } |
| |
| bool BlobDataItem::IsFutureFileItem() const { |
| if (type_ != Type::kFile) |
| return false; |
| const base::FilePath::StringType prefix(kFutureFileName); |
| // The prefix shouldn't occur unless the user used "AppendFutureFile". We |
| // DCHECK on AppendFile to make sure no one appends a future file. |
| return base::StartsWith(path().value(), prefix, base::CompareCase::SENSITIVE); |
| } |
| |
| uint64_t BlobDataItem::GetFutureFileID() const { |
| DCHECK(IsFutureFileItem()); |
| uint64_t id = 0; |
| bool success = base::StringToUint64(path().Extension().substr(1), &id); |
| DCHECK(success) << path().Extension(); |
| return id; |
| } |
| |
| BlobDataItem::BlobDataItem(Type type, uint64_t offset, uint64_t length) |
| : type_(type), offset_(offset), length_(length) {} |
| |
| BlobDataItem::~BlobDataItem() = default; |
| |
| void BlobDataItem::AllocateBytes() { |
| DCHECK_EQ(type_, Type::kBytesDescription); |
| bytes_.resize(length_); |
| type_ = Type::kBytes; |
| } |
| |
| void BlobDataItem::PopulateBytes(base::span<const uint8_t> data) { |
| DCHECK_EQ(type_, Type::kBytesDescription); |
| DCHECK_EQ(length_, data.size()); |
| type_ = Type::kBytes; |
| bytes_.assign(data.begin(), data.end()); |
| } |
| |
| void BlobDataItem::ShrinkBytes(size_t new_length) { |
| DCHECK_EQ(type_, Type::kBytes); |
| length_ = new_length; |
| bytes_.resize(length_); |
| bytes_.shrink_to_fit(); |
| } |
| |
| void BlobDataItem::PopulateFile( |
| base::FilePath path, |
| base::Time expected_modification_time, |
| scoped_refptr<ShareableFileReference> file_ref) { |
| DCHECK_EQ(type_, Type::kFile); |
| DCHECK(IsFutureFileItem()); |
| path_ = std::move(path); |
| expected_modification_time_ = std::move(expected_modification_time); |
| file_ref_ = std::move(file_ref); |
| } |
| |
| void BlobDataItem::ShrinkFile(uint64_t new_length) { |
| DCHECK_EQ(type_, Type::kFile); |
| DCHECK_LE(new_length, length_); |
| length_ = new_length; |
| } |
| |
| void BlobDataItem::GrowFile(uint64_t new_length) { |
| DCHECK_EQ(type_, Type::kFile); |
| DCHECK_GE(new_length, length_); |
| length_ = new_length; |
| } |
| |
| // static |
| void BlobDataItem::SetFileModificationTimes( |
| std::vector<scoped_refptr<BlobDataItem>> items, |
| std::vector<base::Time> times) { |
| DCHECK_EQ(items.size(), times.size()); |
| for (size_t i = 0; i < items.size(); ++i) { |
| items[i]->expected_modification_time_ = times[i]; |
| } |
| } |
| |
| void PrintTo(const BlobDataItem& x, ::std::ostream* os) { |
| const uint64_t kMaxDataPrintLength = 40; |
| DCHECK(os); |
| *os << "<BlobDataItem>{type: "; |
| switch (x.type()) { |
| case BlobDataItem::Type::kBytes: { |
| uint64_t length = std::min(x.length(), kMaxDataPrintLength); |
| *os << "kBytes, data: [" |
| << base::HexEncode(x.bytes().data(), static_cast<size_t>(length)); |
| if (length < x.length()) { |
| *os << "<...truncated due to length...>"; |
| } |
| *os << "]"; |
| break; |
| } |
| case BlobDataItem::Type::kBytesDescription: |
| *os << "kBytesDescription"; |
| break; |
| case BlobDataItem::Type::kFile: |
| *os << "kFile, path: " << x.path().AsUTF8Unsafe() |
| << ", expected_modification_time: " << x.expected_modification_time(); |
| break; |
| case BlobDataItem::Type::kFileFilesystem: |
| *os << "kFileFilesystem, url: " << x.filesystem_url().DebugString(); |
| break; |
| case BlobDataItem::Type::kReadableDataHandle: |
| *os << "kReadableDataHandle" |
| << ", data_handle_: "; |
| x.data_handle()->PrintTo(os); |
| break; |
| } |
| *os << ", length: " << x.length() << ", offset: " << x.offset() |
| << ", has_data_handle: " << (x.data_handle_.get() ? "true" : "false"); |
| } |
| |
| bool operator==(const BlobDataItem& a, const BlobDataItem& b) { |
| if (a.type() != b.type() || a.offset() != b.offset() || |
| a.length() != b.length()) |
| return false; |
| switch (a.type()) { |
| case BlobDataItem::Type::kBytes: |
| return std::equal(a.bytes().begin(), a.bytes().end(), b.bytes().begin(), |
| b.bytes().end()); |
| case BlobDataItem::Type::kBytesDescription: |
| return true; |
| case BlobDataItem::Type::kFile: |
| return a.path() == b.path() && |
| a.expected_modification_time() == b.expected_modification_time(); |
| case BlobDataItem::Type::kFileFilesystem: |
| return a.filesystem_url() == b.filesystem_url(); |
| case BlobDataItem::Type::kReadableDataHandle: |
| return a.data_handle() == b.data_handle(); |
| } |
| NOTREACHED(); |
| return false; |
| } |
| |
| bool operator!=(const BlobDataItem& a, const BlobDataItem& b) { |
| return !(a == b); |
| } |
| |
| } // namespace storage |