| // Copyright 2020 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/native_io/native_io_manager.h" |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "base/files/file_enumerator.h" |
| #include "base/files/file_path.h" |
| #include "base/files/file_util.h" |
| #include "base/task/task_traits.h" |
| #include "base/task/thread_pool.h" |
| #include "build/build_config.h" |
| #include "content/browser/native_io/native_io_host.h" |
| #include "content/browser/native_io/native_io_quota_client.h" |
| #include "mojo/public/cpp/bindings/pending_receiver.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/self_owned_receiver.h" |
| #include "services/network/public/cpp/is_potentially_trustworthy.h" |
| #include "storage/browser/quota/quota_client.h" |
| #include "storage/browser/quota/quota_client_type.h" |
| #include "storage/browser/quota/quota_manager_proxy.h" |
| #include "storage/browser/quota/special_storage_policy.h" |
| #include "storage/common/database/database_identifier.h" |
| #include "third_party/blink/public/common/native_io/native_io_utils.h" |
| #include "third_party/blink/public/mojom/native_io/native_io.mojom.h" |
| #include "third_party/blink/public/mojom/quota/quota_types.mojom-shared.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| std::vector<url::Origin> DoGetOrigins(const base::FilePath& native_io_root) { |
| std::vector<url::Origin> result; |
| // If the NativeIO directory wasn't created yet, there's no file to report. |
| if (!base::PathExists(native_io_root)) |
| return result; |
| |
| base::FileEnumerator file_enumerator(native_io_root, /*recursive=*/false, |
| base::FileEnumerator::DIRECTORIES); |
| |
| for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); |
| file_path = file_enumerator.Next()) { |
| // If the directory name has a non-ASCII character, `file_path` will be the |
| // empty string. This indicates corruption as any origin creates an |
| // ASCII-only directory name, so those directories are ignored. |
| std::string directory_name = file_path.BaseName().MaybeAsASCII(); |
| if (directory_name == "") |
| continue; |
| url::Origin origin = storage::GetOriginFromIdentifier(directory_name); |
| result.push_back(std::move(origin)); |
| } |
| return result; |
| } |
| |
| int64_t DoGetOriginUsage(const base::FilePath& origin_root) { |
| // Returns 0 if `origin_root` does not exist. |
| return base::ComputeDirectorySize(origin_root); |
| } |
| |
| std::map<url::Origin, int64_t> DoGetOriginUsageMap( |
| const base::FilePath& native_io_root) { |
| std::map<url::Origin, int64_t> result; |
| |
| // If the NativeIO directory wasn't created yet, there's no file to report. |
| if (!base::PathExists(native_io_root)) |
| return result; |
| |
| base::FileEnumerator file_enumerator(native_io_root, /*recursive=*/false, |
| base::FileEnumerator::DIRECTORIES); |
| |
| for (base::FilePath file_path = file_enumerator.Next(); !file_path.empty(); |
| file_path = file_enumerator.Next()) { |
| // If the directory name has a non-ASCII character, `file_path` will be the |
| // empty string. This indicates corruption as any origin creates an |
| // ASCII-only directory name, so those directories are ignored. |
| std::string directory_name = file_path.BaseName().MaybeAsASCII(); |
| if (directory_name == "") |
| continue; |
| url::Origin origin = storage::GetOriginFromIdentifier(directory_name); |
| int64_t usage = base::ComputeDirectorySize(file_path); |
| auto inserted = result.insert(std::make_pair(origin, usage)); |
| DCHECK(inserted.second) |
| << "Origins in NativeIO's directory should have a unique folder."; |
| } |
| return result; |
| } |
| |
| constexpr base::FilePath::CharType kNativeIODirectoryName[] = |
| FILE_PATH_LITERAL("NativeIO"); |
| } // namespace |
| |
| NativeIOManager::NativeIOManager( |
| const base::FilePath& profile_root, |
| #if defined(OS_MAC) |
| bool allow_set_length_ipc, |
| #endif // defined(OS_MAC) |
| scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy, |
| scoped_refptr<storage::QuotaManagerProxy> quota_manager_proxy) |
| : root_path_(GetNativeIORootPath(profile_root)), |
| #if defined(OS_MAC) |
| allow_set_length_ipc_(allow_set_length_ipc), |
| #endif // defined(OS_MAC) |
| special_storage_policy_(std::move(special_storage_policy)), |
| quota_manager_proxy_(std::move(quota_manager_proxy)), |
| // Using a raw pointer is safe since NativeIOManager be owned by |
| // NativeIOQuotaClient and is guaranteed to outlive it. |
| quota_client_(this), |
| quota_client_receiver_("a_client_) { |
| if (quota_manager_proxy_) { |
| // Quota client assumes all backends have registered. |
| quota_manager_proxy_->RegisterClient( |
| quota_client_receiver_.BindNewPipeAndPassRemote(), |
| storage::QuotaClientType::kNativeIO, |
| {blink::mojom::StorageType::kTemporary}); |
| } |
| } |
| |
| NativeIOManager::~NativeIOManager() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| void NativeIOManager::BindReceiver( |
| const url::Origin& origin, |
| mojo::PendingReceiver<blink::mojom::NativeIOHost> receiver, |
| mojo::ReportBadMessageCallback bad_message_callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| |
| auto it = hosts_.find(origin); |
| if (it == hosts_.end()) { |
| // This feature should only be exposed to potentially trustworthy origins |
| // (https://w3c.github.io/webappsec-secure-contexts/#is-origin-trustworthy). |
| // Notably this includes the https and chrome-extension schemes, among |
| // others. |
| if (!network::IsOriginPotentiallyTrustworthy(origin)) { |
| std::move(bad_message_callback) |
| .Run("Called NativeIO from an insecure context"); |
| return; |
| } |
| |
| base::FilePath origin_root_path = RootPathForOrigin(origin); |
| DCHECK(origin_root_path.empty() || root_path_.IsParent(origin_root_path)) |
| << "Per-origin data should be in a sub-directory of NativeIO/ for " |
| << "non-incognito mode "; |
| |
| bool insert_succeeded; |
| std::tie(it, insert_succeeded) = hosts_.emplace( |
| origin, |
| std::make_unique<NativeIOHost>(origin, std::move(origin_root_path), |
| #if defined(OS_MAC) |
| allow_set_length_ipc_, |
| #endif // defined(OS_MAC) |
| this)); |
| DCHECK(insert_succeeded); |
| } |
| |
| it->second->BindReceiver(std::move(receiver)); |
| } |
| |
| void NativeIOManager::OnHostReceiverDisconnect(NativeIOHost* host) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| MaybeDeleteHost(host); |
| } |
| |
| void NativeIOManager::MaybeDeleteHost(NativeIOHost* host) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| DCHECK(host != nullptr); |
| DCHECK(hosts_.count(host->origin()) > 0); |
| DCHECK_EQ(hosts_[host->origin()].get(), host); |
| |
| if (!host->has_empty_receiver_set() || host->delete_all_data_in_progress()) |
| return; |
| |
| hosts_.erase(host->origin()); |
| } |
| |
| void NativeIOManager::OnDeleteOriginDataCompleted( |
| storage::QuotaClient::DeleteOriginDataCallback callback, |
| base::File::Error result, |
| NativeIOHost* host) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| MaybeDeleteHost(host); |
| blink::mojom::QuotaStatusCode quota_result = |
| result == base::File::FILE_OK ? blink::mojom::QuotaStatusCode::kOk |
| : blink::mojom::QuotaStatusCode::kUnknown; |
| std::move(callback).Run(quota_result); |
| } |
| |
| void NativeIOManager::DeleteOriginData( |
| const url::Origin& origin, |
| storage::QuotaClient::DeleteOriginDataCallback callback) { |
| auto it = hosts_.find(origin); |
| if (it == hosts_.end()) { |
| // TODO(rstz): Consider turning these checks into DCHECKS when NativeIO is |
| // no longer bundled with the Filesystem API during data removal. |
| if (!network::IsOriginPotentiallyTrustworthy(origin)) { |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk); |
| return; |
| } |
| base::FilePath origin_root_path = RootPathForOrigin(origin); |
| if (origin_root_path.empty()) { |
| // NativeIO is not supported for the origin, no data can be deleted. |
| std::move(callback).Run(blink::mojom::QuotaStatusCode::kOk); |
| return; |
| } |
| |
| DCHECK(root_path_.IsParent(origin_root_path)) |
| << "Per-origin data should be in a sub-directory of NativeIO/"; |
| |
| bool insert_succeeded; |
| // Create a NativeIOHost so that future API calls for the origin are queued |
| // behind the data deletion. This should not meaningfully slow down the |
| // removal process. |
| std::tie(it, insert_succeeded) = hosts_.emplace( |
| origin, |
| std::make_unique<NativeIOHost>(origin, std::move(origin_root_path), |
| #if defined(OS_MAC) |
| allow_set_length_ipc_, |
| #endif // defined(OS_MAC) |
| this)); |
| DCHECK(insert_succeeded); |
| } |
| |
| // base::Unretained is safe here because this NativeIOManager owns the |
| // NativeIOHost. So, the unretained NativeIOManager is guaranteed to outlive |
| // the NativeIOHost and the closure that it uses. |
| it->second->DeleteAllData( |
| base::BindOnce(&NativeIOManager::OnDeleteOriginDataCompleted, |
| base::Unretained(this), std::move(callback))); |
| } |
| |
| void NativeIOManager::GetOriginsForType( |
| blink::mojom::StorageType type, |
| storage::QuotaClient::GetOriginsForTypeCallback callback) { |
| if (type != blink::mojom::StorageType::kTemporary) { |
| std::move(callback).Run({}); |
| return; |
| } |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| { |
| // Needed for file I/O. |
| base::MayBlock(), |
| |
| // Reasonable compromise, given that the sitedata UI depends on this |
| // functionality. |
| base::TaskPriority::USER_VISIBLE, |
| |
| // BLOCK_SHUTDOWN is definitely not appropriate. We might be able to |
| // move to CONTINUE_ON_SHUTDOWN after very careful analysis. |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, |
| }, |
| base::BindOnce(&DoGetOrigins, root_path_), |
| base::BindOnce(&NativeIOManager::DidGetOriginsForType, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| void NativeIOManager::GetOriginsForHost( |
| blink::mojom::StorageType type, |
| const std::string& host, |
| storage::QuotaClient::GetOriginsForHostCallback callback) { |
| if (type != blink::mojom::StorageType::kTemporary) { |
| std::move(callback).Run({}); |
| return; |
| } |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| { |
| // Needed for file I/O. |
| base::MayBlock(), |
| |
| // Reasonable compromise, given that the sitedata UI depends on this |
| // functionality. |
| base::TaskPriority::USER_VISIBLE, |
| |
| // BLOCK_SHUTDOWN is definitely not appropriate. We might be able to |
| // move to CONTINUE_ON_SHUTDOWN after very careful analysis. |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, |
| }, |
| base::BindOnce(&DoGetOrigins, root_path_), |
| base::BindOnce(&NativeIOManager::DidGetOriginsForHost, |
| weak_factory_.GetWeakPtr(), std::move(callback), |
| std::move(host))); |
| } |
| |
| void NativeIOManager::GetOriginUsage( |
| const url::Origin& origin, |
| blink::mojom::StorageType type, |
| storage::QuotaClient::GetOriginUsageCallback callback) { |
| if (type != blink::mojom::StorageType::kTemporary) { |
| std::move(callback).Run(0); |
| return; |
| } |
| |
| base::FilePath origin_root = RootPathForOrigin(origin); |
| |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| { |
| // Needed for file I/O. |
| base::MayBlock(), |
| |
| // Reasonable compromise, given that the sitedata UI depends on this |
| // functionality. |
| base::TaskPriority::USER_VISIBLE, |
| |
| // BLOCK_SHUTDOWN is definitely not appropriate. We might be able to |
| // move to CONTINUE_ON_SHUTDOWN after very careful analysis. |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, |
| }, |
| base::BindOnce(&DoGetOriginUsage, origin_root), |
| base::BindOnce(&NativeIOManager::DidGetOriginUsage, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void NativeIOManager::GetOriginUsageMap( |
| base::OnceCallback<void(const std::map<url::Origin, int64_t>)> callback) { |
| base::ThreadPool::PostTaskAndReplyWithResult( |
| FROM_HERE, |
| { |
| // Needed for file I/O. |
| base::MayBlock(), |
| |
| // Site data removal has a visible UI. |
| base::TaskPriority::USER_VISIBLE, |
| |
| // BLOCK_SHUTDOWN is definitely not appropriate. We might be able to |
| // move to CONTINUE_ON_SHUTDOWN after very careful analysis. |
| base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN, |
| }, |
| base::BindOnce(&DoGetOriginUsageMap, root_path_), |
| base::BindOnce(&NativeIOManager::DidGetOriginUsageMap, |
| weak_factory_.GetWeakPtr(), std::move(callback))); |
| } |
| |
| void NativeIOManager::DidGetOriginsForType( |
| storage::QuotaClient::GetOriginsForTypeCallback callback, |
| std::vector<url::Origin> origins) { |
| std::move(callback).Run(origins); |
| } |
| |
| void NativeIOManager::DidGetOriginsForHost( |
| storage::QuotaClient::GetOriginsForTypeCallback callback, |
| const std::string& host, |
| std::vector<url::Origin> origins) { |
| std::vector<url::Origin> out_origins; |
| for (const url::Origin& origin : origins) { |
| if (host == origin.host()) |
| out_origins.push_back(origin); |
| } |
| std::move(callback).Run(std::move(out_origins)); |
| } |
| |
| void NativeIOManager::DidGetOriginUsage( |
| storage::QuotaClient::GetOriginUsageCallback callback, |
| int64_t usage) { |
| std::move(callback).Run(usage); |
| } |
| |
| void NativeIOManager::DidGetOriginUsageMap( |
| base::OnceCallback<void(const std::map<url::Origin, int64_t>)> callback, |
| std::map<url::Origin, int64_t> usage_map) { |
| std::move(callback).Run(usage_map); |
| } |
| |
| base::FilePath NativeIOManager::RootPathForOrigin(const url::Origin& origin) { |
| // TODO(pwnall): Implement in-memory files instead of bouncing in incognito. |
| if (root_path_.empty()) |
| return root_path_; |
| |
| std::string origin_identifier = storage::GetIdentifierFromOrigin(origin); |
| base::FilePath origin_path = root_path_.AppendASCII(origin_identifier); |
| DCHECK(root_path_.IsParent(origin_path)); |
| return origin_path; |
| } |
| |
| // static |
| base::FilePath NativeIOManager::GetNativeIORootPath( |
| const base::FilePath& profile_root) { |
| if (profile_root.empty()) |
| return base::FilePath(); |
| |
| return profile_root.Append(kNativeIODirectoryName); |
| } |
| |
| // static |
| blink::mojom::NativeIOErrorPtr NativeIOManager::FileErrorToNativeIOError( |
| base::File::Error file_error, |
| std::string message) { |
| blink::mojom::NativeIOErrorType native_io_error_type = |
| blink::native_io::FileErrorToNativeIOErrorType(file_error); |
| std::string final_message = |
| message.empty() |
| ? blink::native_io::GetDefaultMessage(native_io_error_type) |
| : message; |
| return blink::mojom::NativeIOError::New(native_io_error_type, final_message); |
| } |
| |
| } // namespace content |