blob: 60a862d6f69bef9cdc0d7178ddb2b292530f2b12 [file] [log] [blame]
// Copyright 2012 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/file_system/file_system_url.h"
#include <compare>
#include <sstream>
#include "base/check.h"
#include "base/files/safe_base_name.h"
#include "base/strings/escape.h"
#include "base/strings/string_util.h"
#include "storage/common/file_system/file_system_types.h"
#include "storage/common/file_system/file_system_util.h"
#include "third_party/blink/public/common/storage_key/storage_key.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace storage {
namespace {
bool AreSameStorageKey(const FileSystemURL& a, const FileSystemURL& b) {
// TODO(crbug.com/40249324): Make the `storage_key_` member optional.
// This class improperly uses a StorageKey with an opaque origin to indicate a
// lack of origin for FileSystemURLs corresponding to non-sandboxed file
// systems. This leads to unexpected behavior when comparing two non-sandboxed
// FileSystemURLs which differ only in the nonce of their default-constructed
// StorageKey.
return a.storage_key() == b.storage_key() ||
(a.type() == b.type() &&
(a.type() == storage::kFileSystemTypeExternal ||
a.type() == storage::kFileSystemTypeLocal) &&
a.storage_key().origin().opaque() &&
b.storage_key().origin().opaque());
}
} // namespace
FileSystemURL::FileSystemURL()
: is_null_(true),
is_valid_(false),
mount_type_(kFileSystemTypeUnknown),
type_(kFileSystemTypeUnknown),
mount_option_(FlushPolicy::NO_FLUSH_ON_COMPLETION) {}
FileSystemURL::FileSystemURL(const FileSystemURL&) = default;
FileSystemURL::FileSystemURL(FileSystemURL&&) noexcept = default;
FileSystemURL& FileSystemURL::operator=(const FileSystemURL&) = default;
FileSystemURL& FileSystemURL::operator=(FileSystemURL&&) noexcept = default;
FileSystemURL::~FileSystemURL() = default;
FileSystemURL FileSystemURL::CreateSibling(
const base::SafeBaseName& sibling_name) const {
const base::FilePath& new_base_name = sibling_name.path();
if (!is_valid_ ||
new_base_name.empty()
#if BUILDFLAG(IS_ANDROID)
// Android content-URIs do not support siblings.
|| path().IsContentUri()
#endif
) {
return FileSystemURL();
}
const base::FilePath old_base_name = VirtualPath::BaseName(virtual_path_);
if (!path_.empty() && (path_.BaseName() != old_base_name)) {
return FileSystemURL();
}
FileSystemURL sibling(*this);
sibling.virtual_path_ =
VirtualPath::DirName(virtual_path_).Append(new_base_name);
if (!path_.empty()) {
sibling.path_ = path_.DirName().Append(new_base_name);
}
return sibling;
}
// static
FileSystemURL FileSystemURL::CreateForTest(const GURL& url) {
return FileSystemURL(
url, blink::StorageKey::CreateFirstParty(url::Origin::Create(url)));
}
// static
FileSystemURL FileSystemURL::CreateForTest(const blink::StorageKey& storage_key,
FileSystemType mount_type,
const base::FilePath& virtual_path) {
return FileSystemURL(storage_key, mount_type, virtual_path);
}
// static
FileSystemURL FileSystemURL::CreateForTest(
const blink::StorageKey& storage_key,
FileSystemType mount_type,
const base::FilePath& virtual_path,
const std::string& mount_filesystem_id,
FileSystemType cracked_type,
const base::FilePath& cracked_path,
const std::string& filesystem_id,
const FileSystemMountOption& mount_option) {
return FileSystemURL(storage_key, mount_type, virtual_path,
mount_filesystem_id, cracked_type, cracked_path,
filesystem_id, mount_option);
}
// static
bool FileSystemURL::TypeImpliesPathIsReal(FileSystemType type) {
switch (type) {
// Public enum values, also exposed to JavaScript.
case kFileSystemTypeTemporary:
case kFileSystemTypePersistent:
case kFileSystemTypeIsolated:
case kFileSystemTypeExternal:
break;
// Everything else is a private (also known as internal) enum value.
case kFileSystemInternalTypeEnumStart:
case kFileSystemInternalTypeEnumEnd:
NOTREACHED();
case kFileSystemTypeLocal:
case kFileSystemTypeLocalMedia:
case kFileSystemTypeLocalForPlatformApp:
case kFileSystemTypeDriveFs:
case kFileSystemTypeSmbFs:
case kFileSystemTypeFuseBox:
return true;
case kFileSystemTypeUnknown:
case kFileSystemTypeTest:
case kFileSystemTypeDragged:
case kFileSystemTypeDeviceMedia:
case kFileSystemTypeSyncable:
case kFileSystemTypeSyncableForInternalSync:
case kFileSystemTypeForTransientFile:
case kFileSystemTypeProvided:
case kFileSystemTypeDeviceMediaAsFileStorage:
case kFileSystemTypeArcContent:
case kFileSystemTypeArcDocumentsProvider:
break;
// We don't use a "default:" case. Whenever `FileSystemType` gains a
// new enum value, raise a compiler error (with -Werror,-Wswitch) unless
// this switch statement is also updated.
}
return false;
}
FileSystemURL::FileSystemURL(const GURL& url,
const blink::StorageKey& storage_key)
: is_null_(false),
mount_type_(kFileSystemTypeUnknown),
type_(kFileSystemTypeUnknown),
mount_option_(FlushPolicy::NO_FLUSH_ON_COMPLETION) {
GURL origin_url;
// URL should be able to be parsed and the parsed origin should match the
// StorageKey's origin member.
is_valid_ = ParseFileSystemSchemeURL(url, &origin_url, &mount_type_,
&virtual_path_) &&
storage_key.origin().IsSameOriginWith(origin_url);
storage_key_ = storage_key;
path_ = virtual_path_;
type_ = mount_type_;
}
FileSystemURL::FileSystemURL(const blink::StorageKey& storage_key,
FileSystemType mount_type,
const base::FilePath& virtual_path)
: is_null_(false),
is_valid_(true),
storage_key_(storage_key),
mount_type_(mount_type),
virtual_path_(virtual_path.NormalizePathSeparators()),
type_(mount_type),
path_(virtual_path.NormalizePathSeparators()),
mount_option_(FlushPolicy::NO_FLUSH_ON_COMPLETION) {}
FileSystemURL::FileSystemURL(const blink::StorageKey& storage_key,
FileSystemType mount_type,
const base::FilePath& virtual_path,
const std::string& mount_filesystem_id,
FileSystemType cracked_type,
const base::FilePath& cracked_path,
const std::string& filesystem_id,
const FileSystemMountOption& mount_option)
: is_null_(false),
is_valid_(true),
storage_key_(storage_key),
mount_type_(mount_type),
virtual_path_(virtual_path.NormalizePathSeparators()),
mount_filesystem_id_(mount_filesystem_id),
type_(cracked_type),
path_(cracked_path.NormalizePathSeparators()),
filesystem_id_(filesystem_id),
mount_option_(mount_option) {}
GURL FileSystemURL::ToGURL() const {
if (!is_valid_) {
return GURL();
}
GURL url = GetFileSystemRootURI(storage_key_.origin().GetURL(), mount_type_);
if (!url.is_valid()) {
return GURL();
}
std::string url_string = url.spec();
// Exactly match with DOMFileSystemBase::createFileSystemURL()'s encoding
// behavior, where the path is escaped by KURL::encodeWithURLEscapeSequences
// which is essentially encodeURIComponent except '/'.
std::string escaped = base::EscapeQueryParamValue(
virtual_path_.NormalizePathSeparatorsTo('/').AsUTF8Unsafe(),
false /* use_plus */);
base::ReplaceSubstringsAfterOffset(&escaped, 0, "%2F", "/");
url_string.append(escaped);
// Build nested GURL.
return GURL(url_string);
}
std::string FileSystemURL::DebugString() const {
if (!is_valid_) {
return "invalid filesystem: URL";
}
std::ostringstream ss;
switch (mount_type_) {
// Include GURL if GURL serialization is possible.
case kFileSystemTypeTemporary:
case kFileSystemTypePersistent:
case kFileSystemTypeExternal:
case kFileSystemTypeIsolated:
case kFileSystemTypeTest:
ss << "{ uri: ";
ss << GetFileSystemRootURI(storage_key_.origin().GetURL(), mount_type_);
break;
// Otherwise list the origin and path separately.
default:
ss << "{ path: ";
}
// filesystem_id_ will be non empty for (and only for) cracked URLs.
if (!filesystem_id_.empty()) {
ss << virtual_path_.value();
ss << " (";
ss << GetFileSystemTypeString(type_) << "@" << filesystem_id_ << ":";
ss << path_.value();
ss << ")";
} else {
ss << path_.value();
}
ss << ", storage key: " << storage_key_.GetDebugString();
if (bucket_.has_value()) {
ss << ", bucket id: " << bucket_->id;
}
ss << " }";
return ss.str();
}
BucketLocator FileSystemURL::GetBucket() const {
if (bucket()) {
return *bucket_;
}
auto bucket = storage::BucketLocator::ForDefaultBucket(storage_key());
return bucket;
}
bool FileSystemURL::IsParent(const FileSystemURL& child) const {
return IsInSameFileSystem(child) &&
(path().IsParent(child.path()) ||
(VirtualPath::IsRootPath(path()) &&
!VirtualPath::IsRootPath(child.path())));
}
bool FileSystemURL::IsInSameFileSystem(const FileSystemURL& other) const {
// Invalid FileSystemURLs should never be considered of the same file system.
return AreSameStorageKey(*this, other) && is_valid() && other.is_valid() &&
type() == other.type() && filesystem_id() == other.filesystem_id() &&
bucket() == other.bucket()
#if BUILDFLAG(IS_ANDROID)
// Android content-URIs do not support same-FS ops such as rename().
&& !path().IsContentUri() && !other.path().IsContentUri()
#endif
;
}
bool FileSystemURL::operator==(const FileSystemURL& that) const {
if (is_null_ && that.is_null_) {
return true;
}
return AreSameStorageKey(*this, that) && type_ == that.type_ &&
path_ == that.path_ && filesystem_id_ == that.filesystem_id_ &&
is_valid_ == that.is_valid_ && bucket_ == that.bucket_;
}
std::weak_ordering FileSystemURL::operator<=>(const FileSystemURL& that) const {
if (is_null_ && that.is_null_) {
return std::weak_ordering::equivalent;
}
if (!AreSameStorageKey(*this, that)) {
return storage_key_ < that.storage_key_ ? std::weak_ordering::less
: std::weak_ordering::greater;
}
return std::tie(type_, path_, filesystem_id_, is_valid_, bucket_) <=>
std::tie(that.type_, that.path_, that.filesystem_id_, that.is_valid_,
that.bucket_);
}
bool FileSystemURL::Comparator::operator()(const FileSystemURL& lhs,
const FileSystemURL& rhs) const {
DCHECK(lhs.is_valid_ && rhs.is_valid_);
if (!AreSameStorageKey(lhs, rhs)) {
return lhs.storage_key() < rhs.storage_key();
}
if (lhs.type_ != rhs.type_) {
return lhs.type_ < rhs.type_;
}
if (lhs.filesystem_id_ != rhs.filesystem_id_) {
return lhs.filesystem_id_ < rhs.filesystem_id_;
}
if (lhs.bucket_ != rhs.bucket_) {
return lhs.bucket_ < rhs.bucket_;
}
return lhs.path_ < rhs.path_;
}
} // namespace storage