| // Copyright 2015 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_data_builder.h" |
| |
| #include <stddef.h> |
| #include <stdint.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/files/file.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/numerics/safe_conversions.h" |
| #include "base/numerics/safe_math.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/time/time.h" |
| #include "storage/browser/blob/blob_entry.h" |
| #include "storage/browser/blob/blob_storage_registry.h" |
| #include "storage/browser/blob/shareable_blob_data_item.h" |
| #include "third_party/blink/public/common/blob/blob_utils.h" |
| |
| using base::FilePath; |
| |
| namespace storage { |
| |
| BlobDataBuilder::FutureData::FutureData(FutureData&&) = default; |
| BlobDataBuilder::FutureData& BlobDataBuilder::FutureData::operator=( |
| FutureData&&) = default; |
| BlobDataBuilder::FutureData::~FutureData() = default; |
| |
| bool BlobDataBuilder::FutureData::Populate(base::span<const uint8_t> data, |
| size_t offset) const { |
| DCHECK(data.data()); |
| base::span<uint8_t> target = GetDataToPopulate(offset, data.size()); |
| if (!target.data()) |
| return false; |
| DCHECK_EQ(target.size(), data.size()); |
| std::memcpy(target.data(), data.data(), data.size()); |
| return true; |
| } |
| |
| base::span<uint8_t> BlobDataBuilder::FutureData::GetDataToPopulate( |
| size_t offset, |
| size_t length) const { |
| // We lazily allocate our data buffer by waiting until the first |
| // PopulateFutureData call. |
| // Why? The reason we have the AppendFutureData method is to create our Blob |
| // record when the Renderer tells us about the blob without actually |
| // allocating the memory yet, as we might not have the quota yet. So we don't |
| // want to allocate the memory until we're actually receiving the data (which |
| // the browser process only does when it has quota). |
| if (item_->type() == BlobDataItem::Type::kBytesDescription) { |
| item_->AllocateBytes(); |
| } |
| DCHECK_EQ(item_->type(), BlobDataItem::Type::kBytes); |
| base::CheckedNumeric<size_t> checked_end = offset; |
| checked_end += length; |
| if (!checked_end.IsValid() || checked_end.ValueOrDie() > item_->length()) { |
| DVLOG(1) << "Invalid offset or length."; |
| return base::span<uint8_t>(); |
| } |
| return item_->mutable_bytes().subspan(offset, length); |
| } |
| |
| BlobDataBuilder::FutureData::FutureData(scoped_refptr<BlobDataItem> item) |
| : item_(item) {} |
| |
| BlobDataBuilder::FutureFile::FutureFile(FutureFile&&) = default; |
| BlobDataBuilder::FutureFile& BlobDataBuilder::FutureFile::operator=( |
| FutureFile&&) = default; |
| BlobDataBuilder::FutureFile::~FutureFile() = default; |
| |
| bool BlobDataBuilder::FutureFile::Populate( |
| scoped_refptr<ShareableFileReference> file_reference, |
| const base::Time& expected_modification_time) { |
| if (!item_) { |
| DVLOG(1) << "File item already populated"; |
| return false; |
| } |
| DCHECK_EQ(item_->type(), BlobDataItem::Type::kFile); |
| item_->PopulateFile(file_reference->path(), expected_modification_time, |
| file_reference); |
| item_ = nullptr; |
| return true; |
| } |
| |
| BlobDataBuilder::FutureFile::FutureFile(scoped_refptr<BlobDataItem> item) |
| : item_(item) {} |
| |
| BlobDataBuilder::BlobDataBuilder(const std::string& uuid) : uuid_(uuid) {} |
| BlobDataBuilder::~BlobDataBuilder() = default; |
| |
| void BlobDataBuilder::AppendData(base::span<const uint8_t> data) { |
| if (!data.size()) |
| return; |
| auto item = BlobDataItem::CreateBytes(data); |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| std::move(item), ShareableBlobDataItem::QUOTA_NEEDED); |
| // Even though we already prepopulate this data, we treat it as needing |
| // transport anyway since we do need to allocate memory quota. |
| pending_transport_items_.push_back(shareable_item); |
| items_.push_back(std::move(shareable_item)); |
| |
| total_size_ += data.size(); |
| total_memory_size_ += data.size(); |
| transport_quota_needed_ += data.size(); |
| found_memory_transport_ = true; |
| } |
| |
| BlobDataBuilder::FutureData BlobDataBuilder::AppendFutureData(size_t length) { |
| CHECK_NE(length, 0u); |
| auto item = BlobDataItem::CreateBytesDescription(length); |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| item, ShareableBlobDataItem::QUOTA_NEEDED); |
| pending_transport_items_.push_back(shareable_item); |
| items_.push_back(std::move(shareable_item)); |
| |
| total_size_ += length; |
| total_memory_size_ += length; |
| transport_quota_needed_ += length; |
| found_memory_transport_ = true; |
| |
| return FutureData(std::move(item)); |
| } |
| |
| BlobDataBuilder::FutureFile BlobDataBuilder::AppendFutureFile( |
| uint64_t offset, |
| uint64_t length, |
| uint64_t file_id) { |
| CHECK_NE(length, 0ull); |
| DCHECK_NE(length, blink::BlobUtils::kUnknownSize); |
| auto item = BlobDataItem::CreateFutureFile(offset, length, file_id); |
| |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| item, ShareableBlobDataItem::QUOTA_NEEDED); |
| pending_transport_items_.push_back(shareable_item); |
| items_.push_back(std::move(shareable_item)); |
| |
| total_size_ += length; |
| transport_quota_needed_ += length; |
| found_file_transport_ = true; |
| |
| return FutureFile(std::move(item)); |
| } |
| |
| void BlobDataBuilder::AppendFile(const FilePath& file_path, |
| uint64_t offset, |
| uint64_t length, |
| const base::Time& expected_modification_time) { |
| auto item = BlobDataItem::CreateFile(file_path, offset, length, |
| expected_modification_time, |
| ShareableFileReference::Get(file_path)); |
| DCHECK(!item->IsFutureFileItem()) << file_path.value(); |
| |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| std::move(item), ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); |
| items_.push_back(std::move(shareable_item)); |
| |
| total_size_ += length; |
| } |
| |
| void BlobDataBuilder::AppendBlob(const std::string& uuid, |
| uint64_t offset, |
| uint64_t length, |
| const BlobStorageRegistry& blob_registry) { |
| DCHECK_GT(length, 0ul); |
| const BlobEntry* ref_entry = blob_registry.GetEntry(uuid); |
| |
| // Self-references or non-existing blob references are invalid. |
| if (!ref_entry || uuid == uuid_) { |
| has_blob_errors_ = true; |
| return; |
| } |
| |
| dependent_blob_uuids_.insert(uuid); |
| |
| if (BlobStatusIsError(ref_entry->status())) { |
| has_blob_errors_ = true; |
| return; |
| } |
| |
| // We can't reference a blob with unknown size. |
| if (ref_entry->total_size() == blink::BlobUtils::kUnknownSize) { |
| has_blob_errors_ = true; |
| return; |
| } |
| |
| if (length == blink::BlobUtils::kUnknownSize) |
| length = ref_entry->total_size() - offset; |
| |
| total_size_ += length; |
| |
| // If we're referencing the whole blob, then we don't need to slice. |
| if (offset == 0 && length == ref_entry->total_size()) { |
| for (const auto& shareable_item : ref_entry->items()) { |
| if (shareable_item->item()->type() == BlobDataItem::Type::kBytes || |
| shareable_item->item()->type() == |
| BlobDataItem::Type::kBytesDescription) { |
| total_memory_size_ += shareable_item->item()->length(); |
| } |
| items_.push_back(shareable_item); |
| } |
| return; |
| } |
| |
| // Validate our reference has good offset & length. |
| uint64_t end_byte; |
| if (!base::CheckAdd(offset, length).AssignIfValid(&end_byte) || |
| end_byte > ref_entry->total_size()) { |
| has_blob_errors_ = true; |
| return; |
| } |
| |
| SliceBlob(ref_entry, offset, length); |
| } |
| |
| void BlobDataBuilder::SliceBlob(const BlobEntry* source, |
| uint64_t slice_offset, |
| uint64_t slice_size) { |
| const auto& source_items = source->items(); |
| const auto& offsets = source->offsets(); |
| DCHECK_LE(slice_offset + slice_size, source->total_size()); |
| size_t item_index = |
| std::upper_bound(offsets.begin(), offsets.end(), slice_offset) - |
| offsets.begin(); |
| uint64_t item_offset = |
| item_index == 0 ? slice_offset : slice_offset - offsets[item_index - 1]; |
| size_t num_items = source_items.size(); |
| |
| // Read starting from 'first_item_index' and 'item_offset'. |
| for (uint64_t total_sliced = 0; |
| item_index < num_items && total_sliced < slice_size; item_index++) { |
| const scoped_refptr<BlobDataItem>& source_item = |
| source_items[item_index]->item(); |
| uint64_t source_length = source_item->length(); |
| BlobDataItem::Type type = source_item->type(); |
| DCHECK_NE(source_length, std::numeric_limits<uint64_t>::max()); |
| DCHECK_NE(source_length, 0ull); |
| |
| uint64_t read_size = |
| std::min(source_length - item_offset, slice_size - total_sliced); |
| total_sliced += read_size; |
| |
| bool reusing_blob_item = (read_size == source_length); |
| if (reusing_blob_item) { |
| // We can share the entire item. |
| items_.push_back(source_items[item_index]); |
| if (type == BlobDataItem::Type::kBytes || |
| type == BlobDataItem::Type::kBytesDescription) { |
| total_memory_size_ += source_length; |
| } |
| continue; |
| } |
| |
| bool need_copy = false; |
| scoped_refptr<BlobDataItem> data_item; |
| ShareableBlobDataItem::State state = |
| ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA; |
| switch (type) { |
| case BlobDataItem::Type::kBytesDescription: |
| case BlobDataItem::Type::kBytes: { |
| need_copy = true; |
| copy_quota_needed_ += read_size; |
| total_memory_size_ += read_size; |
| // Since we don't have quota yet for memory, we create temporary items |
| // for this data. When our blob is finished constructing, all dependent |
| // blobs are done, and we have enough memory quota, we'll copy the data |
| // over. |
| data_item = BlobDataItem::CreateBytesDescription( |
| base::checked_cast<size_t>(read_size)); |
| state = ShareableBlobDataItem::QUOTA_NEEDED; |
| break; |
| } |
| case BlobDataItem::Type::kFile: { |
| data_item = BlobDataItem::CreateFile( |
| source_item->path(), source_item->offset() + item_offset, read_size, |
| source_item->expected_modification_time(), source_item->file_ref_); |
| |
| if (source_item->IsFutureFileItem()) { |
| // The source file isn't a real file yet (path is fake), so store the |
| // items we need to copy from later. |
| need_copy = true; |
| } |
| break; |
| } |
| case BlobDataItem::Type::kFileFilesystem: { |
| data_item = BlobDataItem::CreateFileFilesystem( |
| source_item->filesystem_url(), source_item->offset() + item_offset, |
| read_size, source_item->expected_modification_time(), |
| source_item->file_system_context()); |
| break; |
| } |
| case BlobDataItem::Type::kReadableDataHandle: { |
| data_item = BlobDataItem::CreateReadableDataHandle( |
| source_item->data_handle_, source_item->offset() + item_offset, |
| read_size); |
| break; |
| } |
| } |
| |
| items_.push_back(new ShareableBlobDataItem(std::move(data_item), state)); |
| if (need_copy) { |
| copies_.push_back( |
| ItemCopyEntry(source_items[item_index], item_offset, items_.back())); |
| } |
| item_offset = 0; |
| } |
| } |
| |
| void BlobDataBuilder::AppendBlob(const std::string& uuid, |
| const BlobStorageRegistry& blob_registry) { |
| AppendBlob(uuid, 0, blink::BlobUtils::kUnknownSize, blob_registry); |
| } |
| |
| void BlobDataBuilder::AppendFileSystemFile( |
| const FileSystemURL& url, |
| uint64_t offset, |
| uint64_t length, |
| const base::Time& expected_modification_time, |
| scoped_refptr<FileSystemContext> file_system_context) { |
| DCHECK_GT(length, 0ul); |
| auto item = BlobDataItem::CreateFileFilesystem( |
| url, offset, length, expected_modification_time, |
| std::move(file_system_context)); |
| |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| std::move(item), ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); |
| items_.push_back(std::move(shareable_item)); |
| |
| total_size_ += length; |
| } |
| |
| void BlobDataBuilder::AppendReadableDataHandle( |
| scoped_refptr<DataHandle> data_handle, |
| uint64_t offset, |
| uint64_t length) { |
| if (length == 0ul) |
| return; |
| auto item = BlobDataItem::CreateReadableDataHandle(std::move(data_handle), |
| offset, length); |
| |
| total_size_ += item->length(); |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| std::move(item), ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); |
| items_.push_back(std::move(shareable_item)); |
| } |
| |
| void BlobDataBuilder::AppendMojoDataItem(mojom::BlobDataItemPtr item_ptr) { |
| if (item_ptr->size == 0ul) |
| return; |
| auto item = BlobDataItem::CreateMojoDataItem(std::move(item_ptr)); |
| |
| total_size_ += item->length(); |
| auto shareable_item = base::MakeRefCounted<ShareableBlobDataItem>( |
| std::move(item), ShareableBlobDataItem::POPULATED_WITHOUT_QUOTA); |
| items_.push_back(std::move(shareable_item)); |
| } |
| |
| std::unique_ptr<BlobDataSnapshot> BlobDataBuilder::CreateSnapshot() const { |
| std::vector<scoped_refptr<BlobDataItem>> items; |
| items.reserve(items_.size()); |
| for (const auto& item : items_) |
| items.push_back(item->item()); |
| return base::WrapUnique(new BlobDataSnapshot( |
| uuid_, content_type_, content_disposition_, std::move(items))); |
| } |
| |
| void PrintTo(const BlobDataBuilder& x, std::ostream* os) { |
| DCHECK(os); |
| *os << "<BlobDataBuilder>{uuid: " << x.uuid() |
| << ", content_type: " << x.content_type_ |
| << ", content_disposition: " << x.content_disposition_ << ", items: ["; |
| for (const auto& item : x.items_) { |
| PrintTo(*item->item(), os); |
| *os << ", "; |
| } |
| *os << "]}"; |
| } |
| |
| } // namespace storage |