Commit 2e8a62b7 authored by Marijn Kruisselbrink's avatar Marijn Kruisselbrink Committed by Commit Bot

[FileAPI] Change how snapshots are captured by blink::File.

Instead of going through mojom::blink::FileUtilitiesHost and passing a path
from the renderer back to the browser, add a new method to mojom::blink::Blob
to do the same thing. Since the blob already knows what file a blob is
referencing, this both makes things slightly more secure (or at least gets
us closer to a place where we can get rid of things like
ChildProcessSecurityPolicyImpl::CanReadFile) and makes this
work in a couple more cases after a File is for example postMessaged to a
worker or frame in a different process as well.

Additionally this gets us closer to unifying what the renderer and browser
processes consider to be "the" snapshot state of any particular blob. In
a follow-up CL the browser side code will change to return its snapshot
state (if any), rather than always returning the current state on disk.

This is part of a series of changes to improve consistency surrounding how
snapshot state of files/blobs is implemented.

Bug: 844874
Change-Id: Ia05cfd21a3d78955ff70933995e93bbe59e3bb3b
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1970301
Commit-Queue: Marijn Kruisselbrink <mek@chromium.org>
Reviewed-by: default avatarTom Sepez <tsepez@chromium.org>
Reviewed-by: default avatarDaniel Murphy <dmurph@chromium.org>
Cr-Commit-Position: refs/heads/master@{#726995}
parent fc67ce7b
...@@ -77,6 +77,9 @@ class FakeBlob final : public blink::mojom::Blob { ...@@ -77,6 +77,9 @@ class FakeBlob final : public blink::mojom::Blob {
void ReadSideData(ReadSideDataCallback callback) override { void ReadSideData(ReadSideDataCallback callback) override {
std::move(callback).Run(side_data_); std::move(callback).Run(side_data_);
} }
void CaptureSnapshot(CaptureSnapshotCallback callback) override {
std::move(callback).Run(body_.size(), base::nullopt);
}
void GetInternalUUID(GetInternalUUIDCallback callback) override { void GetInternalUUID(GetInternalUUIDCallback callback) override {
NOTREACHED(); NOTREACHED();
} }
......
...@@ -11,6 +11,8 @@ ...@@ -11,6 +11,8 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/containers/span.h" #include "base/containers/span.h"
#include "base/files/file_util.h"
#include "base/task/post_task.h"
#include "mojo/public/cpp/bindings/remote.h" #include "mojo/public/cpp/bindings/remote.h"
#include "net/base/io_buffer.h" #include "net/base/io_buffer.h"
#include "storage/browser/blob/blob_data_handle.h" #include "storage/browser/blob/blob_data_handle.h"
...@@ -128,6 +130,7 @@ void BlobImpl::ReadSideData(ReadSideDataCallback callback) { ...@@ -128,6 +130,7 @@ void BlobImpl::ReadSideData(ReadSideDataCallback callback) {
[](BlobDataHandle handle, ReadSideDataCallback callback, [](BlobDataHandle handle, ReadSideDataCallback callback,
BlobStatus status) { BlobStatus status) {
if (status != BlobStatus::DONE) { if (status != BlobStatus::DONE) {
DCHECK(BlobStatusIsError(status));
std::move(callback).Run(base::nullopt); std::move(callback).Run(base::nullopt);
return; return;
} }
...@@ -165,6 +168,68 @@ void BlobImpl::ReadSideData(ReadSideDataCallback callback) { ...@@ -165,6 +168,68 @@ void BlobImpl::ReadSideData(ReadSideDataCallback callback) {
*handle_, std::move(callback))); *handle_, std::move(callback)));
} }
void BlobImpl::CaptureSnapshot(CaptureSnapshotCallback callback) {
handle_->RunOnConstructionComplete(base::BindOnce(
[](base::WeakPtr<BlobImpl> blob_impl, CaptureSnapshotCallback callback,
BlobStatus status) {
if (!blob_impl) {
// No need to call callback, since blob_impl is only destroyed if the
// mojo pipe is disconnected.
return;
}
auto* handle = blob_impl->handle_.get();
if (status != BlobStatus::DONE) {
DCHECK(BlobStatusIsError(status));
std::move(callback).Run(0, base::nullopt);
return;
}
auto snapshot = handle->CreateSnapshot();
// Only blobs consisting of a single file can have a modification
// time.
const auto& items = snapshot->items();
if (items.size() != 1) {
std::move(callback).Run(handle->size(), base::nullopt);
return;
}
const auto& item = items[0];
if (item->type() != BlobDataItem::Type::kFile) {
std::move(callback).Run(handle->size(), base::nullopt);
return;
}
// TODO(mek): Use cached snapshot state, rather than always looking up
// the current size and modification time.
struct SizeAndTime {
uint64_t size;
base::Optional<base::Time> time;
};
base::PostTaskAndReplyWithResult(
FROM_HERE,
{base::ThreadPool(), base::MayBlock(),
base::TaskPriority::USER_VISIBLE},
base::BindOnce(
[](const base::FilePath& path) {
base::File::Info info;
if (!base::GetFileInfo(path, &info))
return SizeAndTime{0, base::nullopt};
return SizeAndTime{info.size, info.last_modified};
},
item->path()),
base::BindOnce(
[](CaptureSnapshotCallback callback,
const SizeAndTime& result) {
std::move(callback).Run(result.size, result.time);
},
std::move(callback)));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void BlobImpl::GetInternalUUID(GetInternalUUIDCallback callback) { void BlobImpl::GetInternalUUID(GetInternalUUIDCallback callback) {
std::move(callback).Run(handle_->uuid()); std::move(callback).Run(handle_->uuid());
} }
......
...@@ -43,6 +43,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobImpl ...@@ -43,6 +43,7 @@ class COMPONENT_EXPORT(STORAGE_BROWSER) BlobImpl
mojo::ScopedDataPipeProducerHandle handle, mojo::ScopedDataPipeProducerHandle handle,
mojo::PendingRemote<blink::mojom::BlobReaderClient> client) override; mojo::PendingRemote<blink::mojom::BlobReaderClient> client) override;
void ReadSideData(ReadSideDataCallback callback) override; void ReadSideData(ReadSideDataCallback callback) override;
void CaptureSnapshot(CaptureSnapshotCallback callback) override;
void GetInternalUUID(GetInternalUUIDCallback callback) override; void GetInternalUUID(GetInternalUUIDCallback callback) override;
// network::mojom::DataPipeGetter: // network::mojom::DataPipeGetter:
......
...@@ -41,6 +41,10 @@ void FakeBlob::ReadSideData(ReadSideDataCallback) { ...@@ -41,6 +41,10 @@ void FakeBlob::ReadSideData(ReadSideDataCallback) {
NOTREACHED(); NOTREACHED();
} }
void FakeBlob::CaptureSnapshot(CaptureSnapshotCallback) {
NOTREACHED();
}
void FakeBlob::GetInternalUUID(GetInternalUUIDCallback callback) { void FakeBlob::GetInternalUUID(GetInternalUUIDCallback callback) {
std::move(callback).Run(uuid_); std::move(callback).Run(uuid_);
} }
......
...@@ -27,6 +27,7 @@ class FakeBlob : public blink::mojom::Blob { ...@@ -27,6 +27,7 @@ class FakeBlob : public blink::mojom::Blob {
void ReadAll(mojo::ScopedDataPipeProducerHandle, void ReadAll(mojo::ScopedDataPipeProducerHandle,
mojo::PendingRemote<blink::mojom::BlobReaderClient>) override; mojo::PendingRemote<blink::mojom::BlobReaderClient>) override;
void ReadSideData(ReadSideDataCallback) override; void ReadSideData(ReadSideDataCallback) override;
void CaptureSnapshot(CaptureSnapshotCallback) override;
void GetInternalUUID(GetInternalUUIDCallback callback) override; void GetInternalUUID(GetInternalUUIDCallback callback) override;
private: private:
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
module blink.mojom; module blink.mojom;
import "mojo/public/mojom/base/big_buffer.mojom"; import "mojo/public/mojom/base/big_buffer.mojom";
import "mojo/public/mojom/base/time.mojom";
import "services/network/public/mojom/data_pipe_getter.mojom"; import "services/network/public/mojom/data_pipe_getter.mojom";
import "services/network/public/mojom/http_request_headers.mojom"; import "services/network/public/mojom/http_request_headers.mojom";
...@@ -53,6 +54,10 @@ interface Blob { ...@@ -53,6 +54,10 @@ interface Blob {
// this blob through a blob URL. // this blob through a blob URL.
ReadSideData() => (mojo_base.mojom.BigBuffer? data); ReadSideData() => (mojo_base.mojom.BigBuffer? data);
// If this blob represents a single file on disk, this will capture the
// current snapshot state of that file.
[Sync] CaptureSnapshot() => (uint64 length, mojo_base.mojom.Time? modification_time);
// This method is an implementation detail of the blob system. You should not // This method is an implementation detail of the blob system. You should not
// ever need to call it directly. // ever need to call it directly.
// This returns the internal UUID of the blob, used by the blob system to // This returns the internal UUID of the blob, used by the blob system to
......
...@@ -298,10 +298,13 @@ base::Time File::LastModifiedTime() const { ...@@ -298,10 +298,13 @@ base::Time File::LastModifiedTime() const {
if (HasValidSnapshotMetadata() && snapshot_modification_time_) if (HasValidSnapshotMetadata() && snapshot_modification_time_)
return *snapshot_modification_time_; return *snapshot_modification_time_;
uint64_t size;
base::Optional<base::Time> modification_time; base::Optional<base::Time> modification_time;
if (HasBackingFile() && GetFileModificationTime(path_, modification_time) && if (HasBackingFile() &&
modification_time) GetBlobDataHandle()->CaptureSnapshot(&size, &modification_time) &&
modification_time) {
return *modification_time; return *modification_time;
}
// lastModified / lastModifiedDate getters should return the current time // lastModified / lastModifiedDate getters should return the current time
// when the last modification time isn't known. // when the last modification time isn't known.
...@@ -327,10 +330,13 @@ uint64_t File::size() const { ...@@ -327,10 +330,13 @@ uint64_t File::size() const {
// FIXME: JavaScript cannot represent sizes as large as uint64_t, we need // FIXME: JavaScript cannot represent sizes as large as uint64_t, we need
// to come up with an exception to throw if file size is not representable. // to come up with an exception to throw if file size is not representable.
int64_t size; uint64_t size;
if (!HasBackingFile() || !GetFileSize(path_, size)) base::Optional<base::Time> modification_time;
if (!HasBackingFile() ||
!GetBlobDataHandle()->CaptureSnapshot(&size, &modification_time)) {
return 0; return 0;
return static_cast<uint64_t>(size); }
return size;
} }
Blob* File::slice(int64_t start, Blob* File::slice(int64_t start,
...@@ -370,14 +376,12 @@ void File::CaptureSnapshot( ...@@ -370,14 +376,12 @@ void File::CaptureSnapshot(
// If we fail to retrieve the size or modification time, probably due to that // If we fail to retrieve the size or modification time, probably due to that
// the file has been deleted, 0 size is returned. // the file has been deleted, 0 size is returned.
FileMetadata metadata; FileMetadata metadata;
if (!HasBackingFile() || !GetFileMetadata(path_, metadata)) { if (!HasBackingFile() || !GetBlobDataHandle()->CaptureSnapshot(
&snapshot_size, &snapshot_modification_time)) {
snapshot_size = 0; snapshot_size = 0;
snapshot_modification_time = base::nullopt; snapshot_modification_time = base::nullopt;
return; return;
} }
snapshot_size = static_cast<uint64_t>(metadata.length);
snapshot_modification_time = metadata.modification_time;
} }
void File::AppendTo(BlobData& blob_data) const { void File::AppendTo(BlobData& blob_data) const {
......
...@@ -4,53 +4,57 @@ ...@@ -4,53 +4,57 @@
#include "third_party/blink/renderer/core/fileapi/file.h" #include "third_party/blink/renderer/core/fileapi/file.h"
#include "base/task/post_task.h"
#include "mojo/public/cpp/bindings/receiver_set.h" #include "mojo/public/cpp/bindings/receiver_set.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h" #include "third_party/blink/public/common/thread_safe_browser_interface_broker_proxy.h"
#include "third_party/blink/public/mojom/file/file_utilities.mojom-blink.h" #include "third_party/blink/public/mojom/file/file_utilities.mojom-blink.h"
#include "third_party/blink/public/platform/platform.h" #include "third_party/blink/public/platform/platform.h"
#include "third_party/blink/renderer/platform/blob/testing/fake_blob.h"
#include "third_party/blink/renderer/platform/file_metadata.h" #include "third_party/blink/renderer/platform/file_metadata.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/scheduler/public/post_cross_thread_task.h"
#include "third_party/blink/renderer/platform/wtf/cross_thread_functional.h"
namespace blink { namespace blink {
namespace { namespace {
class MockFileUtilitiesHost : public mojom::blink::FileUtilitiesHost { class MockBlob : public FakeBlob {
public: public:
MockFileUtilitiesHost() static void Create(File* file, base::Time modified_time) {
: broker_(*Platform::Current()->GetBrowserInterfaceBroker()) { mojo::PendingRemote<mojom::blink::Blob> remote;
broker_.SetBinderForTesting( PostCrossThreadTask(
FileUtilitiesHost::Name_, *base::CreateSequencedTaskRunner({base::ThreadPool()}), FROM_HERE,
WTF::BindRepeating(&MockFileUtilitiesHost::BindReceiver, CrossThreadBindOnce(
WTF::Unretained(this))); [](const String& uuid,
RebindFileUtilitiesForTesting(); mojo::PendingReceiver<mojom::blink::Blob> receiver,
base::Time modified_time) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<MockBlob>(uuid, modified_time),
std::move(receiver));
},
file->Uuid(), remote.InitWithNewPipeAndPassReceiver(),
modified_time));
file->GetBlobDataHandle()->SetBlobRemoteForTesting(std::move(remote));
} }
~MockFileUtilitiesHost() override { MockBlob(const String& uuid, base::Time modified_time)
broker_.SetBinderForTesting(FileUtilitiesHost::Name_, {}); : FakeBlob(uuid), modified_time_(modified_time) {}
RebindFileUtilitiesForTesting();
}
void SetFileInfoToBeReturned(const base::File::Info info) {
file_info_ = info;
}
private: void Clone(mojo::PendingReceiver<mojom::blink::Blob> receiver) override {
void BindReceiver(mojo::ScopedMessagePipeHandle handle) { mojo::MakeSelfOwnedReceiver(
receivers_.Add(this, std::make_unique<MockBlob>(uuid_, modified_time_), std::move(receiver));
mojo::PendingReceiver<FileUtilitiesHost>(std::move(handle)));
} }
// FileUtilitiesHost function: void CaptureSnapshot(CaptureSnapshotCallback callback) override {
void GetFileInfo(const base::FilePath& path, std::move(callback).Run(
GetFileInfoCallback callback) override { /*size=*/0, NullableTimeToOptionalTime(modified_time_));
std::move(callback).Run(file_info_);
} }
ThreadSafeBrowserInterfaceBrokerProxy& broker_; private:
mojo::ReceiverSet<FileUtilitiesHost> receivers_; base::Time modified_time_;
base::File::Info file_info_;
}; };
void ExpectTimestampIsNow(const File& file) { void ExpectTimestampIsNow(const File& file) {
...@@ -65,12 +69,9 @@ void ExpectTimestampIsNow(const File& file) { ...@@ -65,12 +69,9 @@ void ExpectTimestampIsNow(const File& file) {
} // namespace } // namespace
TEST(FileTest, NativeFileWithoutTimestamp) { TEST(FileTest, NativeFileWithoutTimestamp) {
MockFileUtilitiesHost host;
base::File::Info info;
info.last_modified = base::Time();
host.SetFileInfoToBeReturned(info);
auto* const file = MakeGarbageCollected<File>("/native/path"); auto* const file = MakeGarbageCollected<File>("/native/path");
MockBlob::Create(file, base::Time());
EXPECT_TRUE(file->HasBackingFile()); EXPECT_TRUE(file->HasBackingFile());
EXPECT_EQ("/native/path", file->GetPath()); EXPECT_EQ("/native/path", file->GetPath());
EXPECT_TRUE(file->FileSystemURL().IsEmpty()); EXPECT_TRUE(file->FileSystemURL().IsEmpty());
...@@ -78,24 +79,18 @@ TEST(FileTest, NativeFileWithoutTimestamp) { ...@@ -78,24 +79,18 @@ TEST(FileTest, NativeFileWithoutTimestamp) {
} }
TEST(FileTest, NativeFileWithUnixEpochTimestamp) { TEST(FileTest, NativeFileWithUnixEpochTimestamp) {
MockFileUtilitiesHost host;
base::File::Info info;
info.last_modified = base::Time::UnixEpoch();
host.SetFileInfoToBeReturned(info);
auto* const file = MakeGarbageCollected<File>("/native/path"); auto* const file = MakeGarbageCollected<File>("/native/path");
MockBlob::Create(file, base::Time::UnixEpoch());
EXPECT_TRUE(file->HasBackingFile()); EXPECT_TRUE(file->HasBackingFile());
EXPECT_EQ(0, file->lastModified()); EXPECT_EQ(0, file->lastModified());
EXPECT_EQ(base::Time::UnixEpoch(), file->LastModifiedTime()); EXPECT_EQ(base::Time::UnixEpoch(), file->LastModifiedTime());
} }
TEST(FileTest, NativeFileWithApocalypseTimestamp) { TEST(FileTest, NativeFileWithApocalypseTimestamp) {
MockFileUtilitiesHost host;
base::File::Info info;
info.last_modified = base::Time::Max();
host.SetFileInfoToBeReturned(info);
auto* const file = MakeGarbageCollected<File>("/native/path"); auto* const file = MakeGarbageCollected<File>("/native/path");
MockBlob::Create(file, base::Time::Max());
EXPECT_TRUE(file->HasBackingFile()); EXPECT_TRUE(file->HasBackingFile());
EXPECT_EQ((base::Time::Max() - base::Time::UnixEpoch()).InMilliseconds(), EXPECT_EQ((base::Time::Max() - base::Time::UnixEpoch()).InMilliseconds(),
......
...@@ -415,6 +415,16 @@ void BlobDataHandle::ReadRange( ...@@ -415,6 +415,16 @@ void BlobDataHandle::ReadRange(
blob_remote_ = blob.Unbind(); blob_remote_ = blob.Unbind();
} }
bool BlobDataHandle::CaptureSnapshot(
uint64_t* snapshot_size,
base::Optional<base::Time>* snapshot_modification_time) {
// This method operates on a cloned blob remote; this lets us avoid holding
// the |blob_remote_mutex_| locked during the duration of the (synchronous)
// CaptureSnapshot call.
mojo::Remote<mojom::blink::Blob> remote(CloneBlobRemote());
return remote->CaptureSnapshot(snapshot_size, snapshot_modification_time);
}
// static // static
mojom::blink::BlobRegistry* BlobDataHandle::GetBlobRegistry() { mojom::blink::BlobRegistry* BlobDataHandle::GetBlobRegistry() {
return GetThreadSpecificRegistry(); return GetThreadSpecificRegistry();
......
...@@ -219,6 +219,16 @@ class PLATFORM_EXPORT BlobDataHandle ...@@ -219,6 +219,16 @@ class PLATFORM_EXPORT BlobDataHandle
mojo::ScopedDataPipeProducerHandle, mojo::ScopedDataPipeProducerHandle,
mojo::PendingRemote<mojom::blink::BlobReaderClient>); mojo::PendingRemote<mojom::blink::BlobReaderClient>);
// This does synchronous IPC, and possibly synchronous file operations. Think
// twice before calling this function.
bool CaptureSnapshot(uint64_t* snapshot_size,
base::Optional<base::Time>* snapshot_modification_time);
void SetBlobRemoteForTesting(mojo::PendingRemote<mojom::blink::Blob> remote) {
MutexLocker locker(blob_remote_mutex_);
blob_remote_ = std::move(remote);
}
static mojom::blink::BlobRegistry* GetBlobRegistry(); static mojom::blink::BlobRegistry* GetBlobRegistry();
static void SetBlobRegistryForTesting(mojom::blink::BlobRegistry*); static void SetBlobRegistryForTesting(mojom::blink::BlobRegistry*);
......
...@@ -80,6 +80,10 @@ void FakeBlob::ReadSideData(ReadSideDataCallback callback) { ...@@ -80,6 +80,10 @@ void FakeBlob::ReadSideData(ReadSideDataCallback callback) {
NOTREACHED(); NOTREACHED();
} }
void FakeBlob::CaptureSnapshot(CaptureSnapshotCallback callback) {
std::move(callback).Run(body_.length(), base::nullopt);
}
void FakeBlob::GetInternalUUID(GetInternalUUIDCallback callback) { void FakeBlob::GetInternalUUID(GetInternalUUIDCallback callback) {
std::move(callback).Run(uuid_); std::move(callback).Run(uuid_);
} }
......
...@@ -32,8 +32,10 @@ class FakeBlob : public mojom::blink::Blob { ...@@ -32,8 +32,10 @@ class FakeBlob : public mojom::blink::Blob {
void ReadAll(mojo::ScopedDataPipeProducerHandle, void ReadAll(mojo::ScopedDataPipeProducerHandle,
mojo::PendingRemote<mojom::blink::BlobReaderClient>) override; mojo::PendingRemote<mojom::blink::BlobReaderClient>) override;
void ReadSideData(ReadSideDataCallback) override; void ReadSideData(ReadSideDataCallback) override;
void CaptureSnapshot(CaptureSnapshotCallback) override;
void GetInternalUUID(GetInternalUUIDCallback) override; void GetInternalUUID(GetInternalUUIDCallback) override;
private:
protected:
String uuid_; String uuid_;
String body_; String body_;
State* state_; State* state_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment