fix: File System Access API should remember last picked directory (#42892)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
This commit is contained in:
parent
3bcb2f278f
commit
e1a4d90c7a
2 changed files with 116 additions and 8 deletions
|
@ -12,6 +12,8 @@
|
||||||
#include "base/json/values_util.h"
|
#include "base/json/values_util.h"
|
||||||
#include "base/path_service.h"
|
#include "base/path_service.h"
|
||||||
#include "base/task/thread_pool.h"
|
#include "base/task/thread_pool.h"
|
||||||
|
#include "base/time/time.h"
|
||||||
|
#include "base/timer/timer.h"
|
||||||
#include "base/values.h"
|
#include "base/values.h"
|
||||||
#include "chrome/browser/browser_process.h"
|
#include "chrome/browser/browser_process.h"
|
||||||
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h" // nogncheck
|
#include "chrome/browser/file_system_access/chrome_file_system_access_permission_context.h" // nogncheck
|
||||||
|
@ -38,6 +40,24 @@ using HandleType = content::FileSystemAccessPermissionContext::HandleType;
|
||||||
using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
|
using GrantType = electron::FileSystemAccessPermissionContext::GrantType;
|
||||||
using blink::mojom::PermissionStatus;
|
using blink::mojom::PermissionStatus;
|
||||||
|
|
||||||
|
// Dictionary keys for the FILE_SYSTEM_LAST_PICKED_DIRECTORY website setting.
|
||||||
|
// Schema (per origin):
|
||||||
|
// {
|
||||||
|
// ...
|
||||||
|
// {
|
||||||
|
// "default-id" : { "path" : <path> , "path-type" : <type>}
|
||||||
|
// "custom-id-fruit" : { "path" : <path> , "path-type" : <type> }
|
||||||
|
// "custom-id-flower" : { "path" : <path> , "path-type" : <type> }
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
// ...
|
||||||
|
// }
|
||||||
|
const char kDefaultLastPickedDirectoryKey[] = "default-id";
|
||||||
|
const char kCustomLastPickedDirectoryKey[] = "custom-id";
|
||||||
|
const char kPathKey[] = "path";
|
||||||
|
const char kPathTypeKey[] = "path-type";
|
||||||
|
const char kTimestampKey[] = "timestamp";
|
||||||
|
|
||||||
#if BUILDFLAG(IS_WIN)
|
#if BUILDFLAG(IS_WIN)
|
||||||
[[nodiscard]] constexpr bool ContainsInvalidDNSCharacter(
|
[[nodiscard]] constexpr bool ContainsInvalidDNSCharacter(
|
||||||
base::FilePath::StringType hostname) {
|
base::FilePath::StringType hostname) {
|
||||||
|
@ -79,7 +99,7 @@ bool MaybeIsLocalUNCPath(const base::FilePath& path) {
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
#endif
|
#endif // BUILDFLAG(IS_WIN)
|
||||||
|
|
||||||
// Describes a rule for blocking a directory, which can be constructed
|
// Describes a rule for blocking a directory, which can be constructed
|
||||||
// dynamically (based on state) or statically (from kBlockedPaths).
|
// dynamically (based on state) or statically (from kBlockedPaths).
|
||||||
|
@ -102,7 +122,7 @@ bool ShouldBlockAccessToPath(const base::FilePath& path,
|
||||||
MaybeIsLocalUNCPath(path)) {
|
MaybeIsLocalUNCPath(path)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif // BUILDFLAG(IS_WIN)
|
||||||
|
|
||||||
// Add the hard-coded rules to the dynamic rules.
|
// Add the hard-coded rules to the dynamic rules.
|
||||||
for (auto const& [key, rule_path, type] :
|
for (auto const& [key, rule_path, type] :
|
||||||
|
@ -150,6 +170,11 @@ bool ShouldBlockAccessToPath(const base::FilePath& path,
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string GenerateLastPickedDirectoryKey(const std::string& id) {
|
||||||
|
return id.empty() ? kDefaultLastPickedDirectoryKey
|
||||||
|
: base::StrCat({kCustomLastPickedDirectoryKey, "-", id});
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
@ -367,8 +392,9 @@ struct FileSystemAccessPermissionContext::OriginState {
|
||||||
};
|
};
|
||||||
|
|
||||||
FileSystemAccessPermissionContext::FileSystemAccessPermissionContext(
|
FileSystemAccessPermissionContext::FileSystemAccessPermissionContext(
|
||||||
content::BrowserContext* browser_context)
|
content::BrowserContext* browser_context,
|
||||||
: browser_context_(browser_context) {
|
const base::Clock* clock)
|
||||||
|
: browser_context_(browser_context), clock_(clock) {
|
||||||
DETACH_FROM_SEQUENCE(sequence_checker_);
|
DETACH_FROM_SEQUENCE(sequence_checker_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -571,13 +597,63 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
|
||||||
std::move(callback).Run(SensitiveEntryResult::kAllowed);
|
std::move(callback).Run(SensitiveEntryResult::kAllowed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void FileSystemAccessPermissionContext::MaybeEvictEntries(
|
||||||
|
base::Value::Dict& dict) {
|
||||||
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||||
|
|
||||||
|
std::vector<std::pair<base::Time, std::string>> entries;
|
||||||
|
entries.reserve(dict.size());
|
||||||
|
for (auto entry : dict) {
|
||||||
|
// Don't evict the default ID.
|
||||||
|
if (entry.first == kDefaultLastPickedDirectoryKey) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// If the data is corrupted and `entry.second` is for some reason not a
|
||||||
|
// dict, it should be first in line for eviction.
|
||||||
|
auto timestamp = base::Time::Min();
|
||||||
|
if (entry.second.is_dict()) {
|
||||||
|
timestamp = base::ValueToTime(entry.second.GetDict().Find(kTimestampKey))
|
||||||
|
.value_or(base::Time::Min());
|
||||||
|
}
|
||||||
|
entries.emplace_back(timestamp, entry.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.size() <= max_ids_per_origin_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
base::ranges::sort(entries);
|
||||||
|
size_t entries_to_remove = entries.size() - max_ids_per_origin_;
|
||||||
|
for (size_t i = 0; i < entries_to_remove; ++i) {
|
||||||
|
bool did_remove_entry = dict.Remove(entries[i].second);
|
||||||
|
DCHECK(did_remove_entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void FileSystemAccessPermissionContext::SetLastPickedDirectory(
|
void FileSystemAccessPermissionContext::SetLastPickedDirectory(
|
||||||
const url::Origin& origin,
|
const url::Origin& origin,
|
||||||
const std::string& id,
|
const std::string& id,
|
||||||
const base::FilePath& path,
|
const base::FilePath& path,
|
||||||
const PathType type) {
|
const PathType type) {
|
||||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||||
LOG(INFO) << "NOTIMPLEMENTED SetLastPickedDirectory: " << path.value();
|
|
||||||
|
// Create an entry into the nested dictionary.
|
||||||
|
base::Value::Dict entry;
|
||||||
|
entry.Set(kPathKey, base::FilePathToValue(path));
|
||||||
|
entry.Set(kPathTypeKey, static_cast<int>(type));
|
||||||
|
entry.Set(kTimestampKey, base::TimeToValue(clock_->Now()));
|
||||||
|
|
||||||
|
auto it = id_pathinfo_map_.find(origin);
|
||||||
|
if (it != id_pathinfo_map_.end()) {
|
||||||
|
base::Value::Dict& dict = it->second;
|
||||||
|
dict.Set(GenerateLastPickedDirectoryKey(id), std::move(entry));
|
||||||
|
MaybeEvictEntries(dict);
|
||||||
|
} else {
|
||||||
|
base::Value::Dict dict;
|
||||||
|
dict.Set(GenerateLastPickedDirectoryKey(id), std::move(entry));
|
||||||
|
MaybeEvictEntries(dict);
|
||||||
|
id_pathinfo_map_.insert(std::make_pair(origin, std::move(dict)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileSystemAccessPermissionContext::PathInfo
|
FileSystemAccessPermissionContext::PathInfo
|
||||||
|
@ -585,8 +661,27 @@ FileSystemAccessPermissionContext::GetLastPickedDirectory(
|
||||||
const url::Origin& origin,
|
const url::Origin& origin,
|
||||||
const std::string& id) {
|
const std::string& id) {
|
||||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||||
LOG(INFO) << "NOTIMPLEMENTED GetLastPickedDirectory";
|
|
||||||
return PathInfo();
|
auto it = id_pathinfo_map_.find(origin);
|
||||||
|
|
||||||
|
PathInfo path_info;
|
||||||
|
if (it == id_pathinfo_map_.end()) {
|
||||||
|
return path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* entry = it->second.FindDict(GenerateLastPickedDirectoryKey(id));
|
||||||
|
if (!entry) {
|
||||||
|
return path_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto type_int =
|
||||||
|
entry->FindInt(kPathTypeKey).value_or(static_cast<int>(PathType::kLocal));
|
||||||
|
path_info.type = type_int == static_cast<int>(PathType::kExternal)
|
||||||
|
? PathType::kExternal
|
||||||
|
: PathType::kLocal;
|
||||||
|
path_info.path =
|
||||||
|
base::ValueToFilePath(entry->Find(kPathKey)).value_or(base::FilePath());
|
||||||
|
return path_info;
|
||||||
}
|
}
|
||||||
|
|
||||||
base::FilePath FileSystemAccessPermissionContext::GetWellKnownDirectoryPath(
|
base::FilePath FileSystemAccessPermissionContext::GetWellKnownDirectoryPath(
|
||||||
|
|
|
@ -13,6 +13,9 @@
|
||||||
|
|
||||||
#include "base/functional/callback.h"
|
#include "base/functional/callback.h"
|
||||||
#include "base/memory/weak_ptr.h"
|
#include "base/memory/weak_ptr.h"
|
||||||
|
#include "base/time/clock.h"
|
||||||
|
#include "base/time/default_clock.h"
|
||||||
|
#include "base/values.h"
|
||||||
#include "components/keyed_service/core/keyed_service.h"
|
#include "components/keyed_service/core/keyed_service.h"
|
||||||
#include "content/public/browser/file_system_access_permission_context.h"
|
#include "content/public/browser/file_system_access_permission_context.h"
|
||||||
|
|
||||||
|
@ -35,7 +38,8 @@ class FileSystemAccessPermissionContext
|
||||||
enum class GrantType { kRead, kWrite };
|
enum class GrantType { kRead, kWrite };
|
||||||
|
|
||||||
explicit FileSystemAccessPermissionContext(
|
explicit FileSystemAccessPermissionContext(
|
||||||
content::BrowserContext* browser_context);
|
content::BrowserContext* browser_context,
|
||||||
|
const base::Clock* clock = base::DefaultClock::GetInstance());
|
||||||
FileSystemAccessPermissionContext(const FileSystemAccessPermissionContext&) =
|
FileSystemAccessPermissionContext(const FileSystemAccessPermissionContext&) =
|
||||||
delete;
|
delete;
|
||||||
FileSystemAccessPermissionContext& operator=(
|
FileSystemAccessPermissionContext& operator=(
|
||||||
|
@ -133,6 +137,8 @@ class FileSystemAccessPermissionContext
|
||||||
base::OnceCallback<void(SensitiveEntryResult)> callback,
|
base::OnceCallback<void(SensitiveEntryResult)> callback,
|
||||||
bool should_block);
|
bool should_block);
|
||||||
|
|
||||||
|
void MaybeEvictEntries(base::Value::Dict& dict);
|
||||||
|
|
||||||
void CleanupPermissions(const url::Origin& origin);
|
void CleanupPermissions(const url::Origin& origin);
|
||||||
|
|
||||||
bool AncestorHasActivePermission(const url::Origin& origin,
|
bool AncestorHasActivePermission(const url::Origin& origin,
|
||||||
|
@ -146,6 +152,13 @@ class FileSystemAccessPermissionContext
|
||||||
struct OriginState;
|
struct OriginState;
|
||||||
std::map<url::Origin, OriginState> active_permissions_map_;
|
std::map<url::Origin, OriginState> active_permissions_map_;
|
||||||
|
|
||||||
|
// Number of custom IDs an origin can specify.
|
||||||
|
size_t max_ids_per_origin_ = 32u;
|
||||||
|
|
||||||
|
const raw_ptr<const base::Clock> clock_;
|
||||||
|
|
||||||
|
std::map<url::Origin, base::Value::Dict> id_pathinfo_map_;
|
||||||
|
|
||||||
base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
|
base::WeakPtrFactory<FileSystemAccessPermissionContext> weak_factory_{this};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue