blob: 11c3863f8ea051dd9c789e4bfb601cf74f850a82 [file] [log] [blame]
// 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 "content/browser/fileapi/fileapi_message_filter.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/files/file_path.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/metrics/user_metrics.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "components/services/filesystem/public/interfaces/types.mojom.h"
#include "content/browser/bad_message.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/fileapi/browser_file_system_helper.h"
#include "content/common/fileapi/file_system_messages.h"
#include "content/common/fileapi/webblob_messages.h"
#include "content/public/browser/browser_thread.h"
#include "ipc/ipc_platform_file.h"
#include "net/base/mime_util.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/blob/shareable_file_reference.h"
#include "storage/browser/fileapi/file_observers.h"
#include "storage/browser/fileapi/file_permission_policy.h"
#include "storage/browser/fileapi/file_system_context.h"
#include "storage/browser/fileapi/isolated_context.h"
#include "storage/common/fileapi/file_system_info.h"
#include "storage/common/fileapi/file_system_types.h"
#include "storage/common/fileapi/file_system_util.h"
#include "url/gurl.h"
using storage::FileSystemFileUtil;
using storage::FileSystemBackend;
using storage::FileSystemOperation;
using storage::FileSystemURL;
using storage::BlobDataBuilder;
using storage::BlobStorageContext;
namespace content {
namespace {
const uint32_t kFileApiFilteredMessageClasses[] = {FileSystemMsgStart,
BlobMsgStart};
void RevokeFilePermission(int child_id, const base::FilePath& path) {
ChildProcessSecurityPolicyImpl::GetInstance()->RevokeAllPermissionsForFile(
child_id, path);
}
} // namespace
FileAPIMessageFilter::FileAPIMessageFilter(
int process_id,
storage::FileSystemContext* file_system_context,
ChromeBlobStorageContext* blob_storage_context)
: BrowserMessageFilter(kFileApiFilteredMessageClasses,
arraysize(kFileApiFilteredMessageClasses)),
process_id_(process_id),
context_(file_system_context),
security_policy_(ChildProcessSecurityPolicyImpl::GetInstance()),
blob_storage_context_(blob_storage_context) {
DCHECK(context_);
DCHECK(blob_storage_context);
}
void FileAPIMessageFilter::OnChannelConnected(int32_t peer_pid) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
operation_runner_ = context_->CreateFileSystemOperationRunner();
}
void FileAPIMessageFilter::OnChannelClosing() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
in_transit_snapshot_files_.clear();
operation_runner_.reset();
operations_.clear();
}
base::TaskRunner* FileAPIMessageFilter::OverrideTaskRunnerForMessage(
const IPC::Message& message) {
if (message.type() == FileSystemHostMsg_SyncGetPlatformPath::ID)
return context_->default_file_task_runner();
return nullptr;
}
bool FileAPIMessageFilter::OnMessageReceived(const IPC::Message& message) {
bool handled = true;
IPC_BEGIN_MESSAGE_MAP(FileAPIMessageFilter, message)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_OpenFileSystem, OnOpenFileSystem)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_ResolveURL, OnResolveURL)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Move, OnMove)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Copy, OnCopy)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Remove, OnRemove)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadMetadata, OnReadMetadata)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Create, OnCreate)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Exists, OnExists)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_ReadDirectory, OnReadDirectory)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Write, OnWrite)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_Truncate, OnTruncate)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_TouchFile, OnTouchFile)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_CancelWrite, OnCancel)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_CreateSnapshotFile,
OnCreateSnapshotFile)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_DidReceiveSnapshotFile,
OnDidReceiveSnapshotFile)
IPC_MESSAGE_HANDLER(FileSystemHostMsg_SyncGetPlatformPath,
OnSyncGetPlatformPath)
IPC_MESSAGE_UNHANDLED(handled = false)
IPC_END_MESSAGE_MAP()
return handled;
}
FileAPIMessageFilter::~FileAPIMessageFilter() {}
void FileAPIMessageFilter::OnOpenFileSystem(int request_id,
const GURL& origin_url,
storage::FileSystemType type) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (type == storage::kFileSystemTypeTemporary) {
RecordAction(base::UserMetricsAction("OpenFileSystemTemporary"));
} else if (type == storage::kFileSystemTypePersistent) {
RecordAction(base::UserMetricsAction("OpenFileSystemPersistent"));
}
storage::OpenFileSystemMode mode =
storage::OPEN_FILE_SYSTEM_CREATE_IF_NONEXISTENT;
context_->OpenFileSystem(
origin_url, type, mode,
base::BindOnce(&FileAPIMessageFilter::DidOpenFileSystem, this,
request_id));
}
void FileAPIMessageFilter::OnResolveURL(
int request_id,
const GURL& filesystem_url) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(filesystem_url));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
context_->ResolveURL(url, base::BindOnce(&FileAPIMessageFilter::DidResolveURL,
this, request_id));
}
void FileAPIMessageFilter::OnMove(
int request_id, const GURL& src_path, const GURL& dest_path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL src_url(context_->CrackURL(src_path));
FileSystemURL dest_url(context_->CrackURL(dest_path));
if (!ValidateFileSystemURL(request_id, src_url) ||
!ValidateFileSystemURL(request_id, dest_url)) {
return;
}
if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) ||
!security_policy_->CanDeleteFileSystemFile(process_id_, src_url) ||
!security_policy_->CanCreateFileSystemFile(process_id_, dest_url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->Move(
src_url,
dest_url,
storage::FileSystemOperation::OPTION_NONE,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
void FileAPIMessageFilter::OnCopy(
int request_id, const GURL& src_path, const GURL& dest_path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL src_url(context_->CrackURL(src_path));
FileSystemURL dest_url(context_->CrackURL(dest_path));
if (!ValidateFileSystemURL(request_id, src_url) ||
!ValidateFileSystemURL(request_id, dest_url)) {
return;
}
if (!security_policy_->CanReadFileSystemFile(process_id_, src_url) ||
!security_policy_->CanCopyIntoFileSystemFile(process_id_, dest_url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->Copy(
src_url, dest_url, storage::FileSystemOperation::OPTION_NONE,
FileSystemOperation::ERROR_BEHAVIOR_ABORT,
storage::FileSystemOperationRunner::CopyProgressCallback(),
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
void FileAPIMessageFilter::OnRemove(
int request_id, const GURL& path, bool recursive) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanDeleteFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->Remove(
url, recursive,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
void FileAPIMessageFilter::OnReadMetadata(
int request_id, const GURL& path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->GetMetadata(
url, FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
FileSystemOperation::GET_METADATA_FIELD_SIZE |
FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::Bind(&FileAPIMessageFilter::DidGetMetadata, this, request_id));
}
void FileAPIMessageFilter::OnCreate(
int request_id, const GURL& path, bool exclusive,
bool is_directory, bool recursive) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanCreateFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
if (is_directory) {
operations_[request_id] = operation_runner()->CreateDirectory(
url, exclusive, recursive,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
} else {
operations_[request_id] = operation_runner()->CreateFile(
url, exclusive,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
}
void FileAPIMessageFilter::OnExists(
int request_id, const GURL& path, bool is_directory) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
if (is_directory) {
operations_[request_id] = operation_runner()->DirectoryExists(
url,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
} else {
operations_[request_id] = operation_runner()->FileExists(
url,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
}
void FileAPIMessageFilter::OnReadDirectory(
int request_id, const GURL& path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->ReadDirectory(
url, base::BindRepeating(&FileAPIMessageFilter::DidReadDirectory, this,
request_id));
}
void FileAPIMessageFilter::OnWrite(int request_id,
const GURL& path,
const std::string& blob_uuid,
int64_t offset) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
std::unique_ptr<storage::BlobDataHandle> blob =
blob_storage_context_->context()->GetBlobDataFromUUID(blob_uuid);
operations_[request_id] = operation_runner()->Write(
url, std::move(blob), offset,
base::Bind(&FileAPIMessageFilter::DidWrite, this, request_id));
}
void FileAPIMessageFilter::OnTruncate(int request_id,
const GURL& path,
int64_t length) {
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanWriteFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->Truncate(
url, length,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
void FileAPIMessageFilter::OnTouchFile(
int request_id,
const GURL& path,
const base::Time& last_access_time,
const base::Time& last_modified_time) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanCreateFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
operations_[request_id] = operation_runner()->TouchFile(
url, last_access_time, last_modified_time,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
}
void FileAPIMessageFilter::OnCancel(
int request_id,
int request_id_to_cancel) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
OperationsMap::iterator found = operations_.find(request_id_to_cancel);
if (found != operations_.end()) {
// The cancel will eventually send both the write failure and the cancel
// success.
operation_runner()->Cancel(
found->second,
base::Bind(&FileAPIMessageFilter::DidFinish, this, request_id));
} else {
// The write already finished; report that we failed to stop it.
Send(new FileSystemMsg_DidFail(
request_id, base::File::FILE_ERROR_INVALID_OPERATION));
}
}
void FileAPIMessageFilter::OnSyncGetPlatformPath(
const GURL& path, base::FilePath* platform_path) {
SyncGetPlatformPath(context_, process_id_, path, platform_path);
}
void FileAPIMessageFilter::OnCreateSnapshotFile(
int request_id, const GURL& path) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
FileSystemURL url(context_->CrackURL(path));
// Make sure if this file can be read by the renderer as this is
// called when the renderer is about to create a new File object
// (for reading the file).
if (!ValidateFileSystemURL(request_id, url))
return;
if (!security_policy_->CanReadFileSystemFile(process_id_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return;
}
FileSystemBackend* backend = context_->GetFileSystemBackend(url.type());
if (backend->SupportsStreaming(url)) {
operations_[request_id] = operation_runner()->GetMetadata(
url, FileSystemOperation::GET_METADATA_FIELD_IS_DIRECTORY |
FileSystemOperation::GET_METADATA_FIELD_SIZE |
FileSystemOperation::GET_METADATA_FIELD_LAST_MODIFIED,
base::Bind(&FileAPIMessageFilter::DidGetMetadataForStreaming, this,
request_id));
} else {
operations_[request_id] = operation_runner()->CreateSnapshotFile(
url,
base::Bind(&FileAPIMessageFilter::DidCreateSnapshot,
this, request_id, url));
}
}
void FileAPIMessageFilter::OnDidReceiveSnapshotFile(int request_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
in_transit_snapshot_files_.erase(request_id);
}
void FileAPIMessageFilter::DidFinish(int request_id,
base::File::Error result) {
if (result == base::File::FILE_OK)
Send(new FileSystemMsg_DidSucceed(request_id));
else
Send(new FileSystemMsg_DidFail(request_id, result));
operations_.erase(request_id);
}
void FileAPIMessageFilter::DidGetMetadata(
int request_id,
base::File::Error result,
const base::File::Info& info) {
if (result == base::File::FILE_OK)
Send(new FileSystemMsg_DidReadMetadata(request_id, info));
else
Send(new FileSystemMsg_DidFail(request_id, result));
operations_.erase(request_id);
}
void FileAPIMessageFilter::DidGetMetadataForStreaming(
int request_id,
base::File::Error result,
const base::File::Info& info) {
if (result == base::File::FILE_OK) {
// For now, streaming Blobs are implemented as a successful snapshot file
// creation with an empty path.
Send(new FileSystemMsg_DidCreateSnapshotFile(request_id, info,
base::FilePath()));
} else {
Send(new FileSystemMsg_DidFail(request_id, result));
}
operations_.erase(request_id);
}
void FileAPIMessageFilter::DidReadDirectory(
int request_id,
base::File::Error result,
std::vector<filesystem::mojom::DirectoryEntry> entries,
bool has_more) {
if (result == base::File::FILE_OK) {
if (!entries.empty() || !has_more)
Send(new FileSystemMsg_DidReadDirectory(request_id, std::move(entries),
has_more));
} else {
DCHECK(!has_more);
Send(new FileSystemMsg_DidFail(request_id, result));
}
if (!has_more)
operations_.erase(request_id);
}
void FileAPIMessageFilter::DidWrite(int request_id,
base::File::Error result,
int64_t bytes,
bool complete) {
if (result == base::File::FILE_OK) {
Send(new FileSystemMsg_DidWrite(request_id, bytes, complete));
if (complete)
operations_.erase(request_id);
} else {
Send(new FileSystemMsg_DidFail(request_id, result));
operations_.erase(request_id);
}
}
void FileAPIMessageFilter::DidOpenFileSystem(int request_id,
const GURL& root,
const std::string& filesystem_name,
base::File::Error result) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result == base::File::FILE_OK) {
DCHECK(root.is_valid());
Send(new FileSystemMsg_DidOpenFileSystem(
request_id, filesystem_name, root));
} else {
Send(new FileSystemMsg_DidFail(request_id, result));
}
// For OpenFileSystem we do not create a new operation, so no unregister here.
}
void FileAPIMessageFilter::DidResolveURL(
int request_id,
base::File::Error result,
const storage::FileSystemInfo& info,
const base::FilePath& file_path,
storage::FileSystemContext::ResolvedEntryType type) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (result == base::File::FILE_OK &&
type == storage::FileSystemContext::RESOLVED_ENTRY_NOT_FOUND)
result = base::File::FILE_ERROR_NOT_FOUND;
if (result == base::File::FILE_OK) {
DCHECK(info.root_url.is_valid());
Send(new FileSystemMsg_DidResolveURL(
request_id,
info,
file_path,
type == storage::FileSystemContext::RESOLVED_ENTRY_DIRECTORY));
} else {
Send(new FileSystemMsg_DidFail(request_id, result));
}
// For ResolveURL we do not create a new operation, so no unregister here.
}
void FileAPIMessageFilter::DidCreateSnapshot(
int request_id,
const storage::FileSystemURL& url,
base::File::Error result,
const base::File::Info& info,
const base::FilePath& platform_path,
scoped_refptr<storage::ShareableFileReference> /* unused */) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
operations_.erase(request_id);
if (result != base::File::FILE_OK) {
Send(new FileSystemMsg_DidFail(request_id, result));
return;
}
scoped_refptr<storage::ShareableFileReference> file_ref =
storage::ShareableFileReference::Get(platform_path);
if (!security_policy_->CanReadFile(process_id_, platform_path)) {
// Give per-file read permission to the snapshot file if it hasn't it yet.
// In order for the renderer to be able to read the file via File object,
// it must be granted per-file read permission for the file's platform
// path. By now, it has already been verified that the renderer has
// sufficient permissions to read the file, so giving per-file permission
// here must be safe.
security_policy_->GrantReadFile(process_id_, platform_path);
// Revoke all permissions for the file when the last ref of the file
// is dropped.
if (!file_ref.get()) {
// Create a reference for temporary permission handling.
file_ref = storage::ShareableFileReference::GetOrCreate(
platform_path,
storage::ShareableFileReference::DONT_DELETE_ON_FINAL_RELEASE,
context_->default_file_task_runner());
}
file_ref->AddFinalReleaseCallback(
base::BindOnce(&RevokeFilePermission, process_id_));
}
if (file_ref.get()) {
// This ref is held until OnDidReceiveSnapshotFile is called.
in_transit_snapshot_files_[request_id] = file_ref;
}
// Return the file info and platform_path.
Send(new FileSystemMsg_DidCreateSnapshotFile(
request_id, info, platform_path));
}
bool FileAPIMessageFilter::ValidateFileSystemURL(
int request_id,
const storage::FileSystemURL& url) {
if (!FileSystemURLIsValid(context_, url)) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_INVALID_URL));
return false;
}
// Deny access to files in PluginPrivate FileSystem from JavaScript.
// TODO(nhiroki): Move this filter somewhere else since this is not for
// validation.
if (url.type() == storage::kFileSystemTypePluginPrivate) {
Send(new FileSystemMsg_DidFail(request_id,
base::File::FILE_ERROR_SECURITY));
return false;
}
return true;
}
} // namespace content