blob: 9e9c02e3782ec59213c0907c10cbf6df1a507e48 [file] [log] [blame]
// Copyright 2018 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/dom_storage/session_storage_metadata.h"
#include "base/macros.h"
#include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "components/services/leveldb/public/cpp/util.h"
#include "third_party/blink/public/common/dom_storage/session_storage_namespace_id.h"
#include "url/gurl.h"
namespace content {
namespace {
using leveldb::mojom::BatchedOperation;
using leveldb::mojom::BatchOperationType;
using leveldb::mojom::BatchedOperationPtr;
// Example layout of the database:
// | key | value |
// |----------------------------------------|--------------------|
// | map-1-a | b (a = b in map 1) |
// | ... | |
// | namespace-<36 char guid 1>-origin1 | 1 (mapid) |
// | namespace-<36 char guid 1>-origin2 | 2 |
// | namespace-<36 char guid 2>-origin1 | 1 (shallow copy) |
// | namespace-<36 char guid 2>-origin2 | 2 (shallow copy) |
// | namespace-<36 char guid 3>-origin1 | 3 (deep copy) |
// | namespace-<36 char guid 3>-origin2 | 2 (shallow copy) |
// | next-map-id | 4 |
// | version | 1 |
// Example area key: namespace-dabc53e1_8291_4de5_824f_dab8aa69c846-origin2
//
// All number values (map numbers and the version) are string conversions of
// numbers. Map keys are converted to UTF-8 and the values stay as UTF-16.
// This is "map-" (without the quotes).
constexpr const uint8_t kMapIdPrefixBytes[] = {'m', 'a', 'p', '-'};
constexpr const size_t kNamespacePrefixLength =
base::size(SessionStorageMetadata::kNamespacePrefixBytes);
constexpr const uint8_t kNamespaceOriginSeperatorByte = '-';
constexpr const size_t kNamespaceOriginSeperatorLength = 1;
constexpr const size_t kPrefixBeforeOriginLength =
kNamespacePrefixLength + blink::kSessionStorageNamespaceIdLength +
kNamespaceOriginSeperatorLength;
bool ValueToNumber(const std::vector<uint8_t>& value, int64_t* out) {
return base::StringToInt64(leveldb::Uint8VectorToStringPiece(value), out);
}
std::vector<uint8_t> NumberToValue(int64_t map_number) {
return leveldb::StdStringToUint8Vector(base::NumberToString(map_number));
}
} // namespace
constexpr const int64_t SessionStorageMetadata::kMinSessionStorageSchemaVersion;
constexpr const int64_t
SessionStorageMetadata::kLatestSessionStorageSchemaVersion;
constexpr const int64_t SessionStorageMetadata::kInvalidDatabaseVersion;
constexpr const int64_t SessionStorageMetadata::kInvalidMapId;
constexpr const uint8_t SessionStorageMetadata::kDatabaseVersionBytes[];
constexpr const uint8_t SessionStorageMetadata::kNamespacePrefixBytes[];
constexpr const uint8_t SessionStorageMetadata::kNextMapIdKeyBytes[];
SessionStorageMetadata::MapData::MapData(int64_t map_number, url::Origin origin)
: number_as_bytes_(NumberToValue(map_number)),
key_prefix_(SessionStorageMetadata::GetMapPrefix(number_as_bytes_)),
origin_(std::move(origin)) {}
SessionStorageMetadata::MapData::~MapData() = default;
SessionStorageMetadata::SessionStorageMetadata() {}
SessionStorageMetadata::~SessionStorageMetadata() {}
std::vector<leveldb::mojom::BatchedOperationPtr>
SessionStorageMetadata::SetupNewDatabase() {
next_map_id_ = 0;
next_map_id_from_namespaces_ = 0;
namespace_origin_map_.clear();
std::vector<leveldb::mojom::BatchedOperationPtr> operations;
operations.reserve(2);
operations.push_back(BatchedOperation::New(
BatchOperationType::PUT_KEY,
std::vector<uint8_t>(std::begin(kDatabaseVersionBytes),
std::end(kDatabaseVersionBytes)),
LatestDatabaseVersionAsVector()));
operations.push_back(
BatchedOperation::New(BatchOperationType::PUT_KEY,
std::vector<uint8_t>(std::begin(kNextMapIdKeyBytes),
std::end(kNextMapIdKeyBytes)),
NumberToValue(next_map_id_)));
return operations;
}
bool SessionStorageMetadata::ParseDatabaseVersion(
base::Optional<std::vector<uint8_t>> value,
std::vector<leveldb::mojom::BatchedOperationPtr>* upgrade_operations) {
if (!value) {
initial_database_version_from_disk_ = 0;
} else {
if (!ValueToNumber(value.value(), &initial_database_version_from_disk_)) {
initial_database_version_from_disk_ = kInvalidDatabaseVersion;
return false;
}
if (initial_database_version_from_disk_ ==
kLatestSessionStorageSchemaVersion)
return true;
}
if (initial_database_version_from_disk_ < kMinSessionStorageSchemaVersion)
return false;
upgrade_operations->push_back(BatchedOperation::New(
BatchOperationType::PUT_KEY,
std::vector<uint8_t>(std::begin(kDatabaseVersionBytes),
std::end(kDatabaseVersionBytes)),
LatestDatabaseVersionAsVector()));
return true;
}
bool SessionStorageMetadata::ParseNamespaces(
std::vector<leveldb::mojom::KeyValuePtr> values,
std::vector<leveldb::mojom::BatchedOperationPtr>* upgrade_operations) {
namespace_origin_map_.clear();
next_map_id_from_namespaces_ = 0;
// Since the data is ordered, all namespace data is in one spot. This keeps a
// reference to the last namespace data map to be more efficient.
std::string last_namespace_id;
std::map<url::Origin, scoped_refptr<MapData>>* last_namespace = nullptr;
std::map<int64_t, scoped_refptr<MapData>> maps;
bool error = false;
for (const leveldb::mojom::KeyValuePtr& key_value : values) {
size_t key_size = key_value->key.size();
base::StringPiece key_as_string =
leveldb::Uint8VectorToStringPiece(key_value->key);
if (key_size < kNamespacePrefixLength) {
LOG(ERROR) << "Key size is less than prefix length: " << key_as_string;
error = true;
break;
}
// The key must start with 'namespace-'.
if (!key_as_string.starts_with(base::StringPiece(
reinterpret_cast<const char*>(kNamespacePrefixBytes),
kNamespacePrefixLength))) {
LOG(ERROR) << "Key must start with 'namespace-': " << key_as_string;
error = true;
break;
}
// Old databases have a dummy 'namespace-' entry.
if (key_size == kNamespacePrefixLength)
continue;
// Check that the prefix is 'namespace-<guid>-
if (key_size < kPrefixBeforeOriginLength ||
key_as_string[kPrefixBeforeOriginLength - 1] !=
static_cast<const char>(kNamespaceOriginSeperatorByte)) {
LOG(ERROR) << "Prefix is not 'namespace-<guid>-': " << key_as_string;
error = true;
break;
}
// Old databases have a dummy 'namespace-<guid>-' entry.
if (key_size == kPrefixBeforeOriginLength)
continue;
base::StringPiece namespace_id = key_as_string.substr(
kNamespacePrefixLength, blink::kSessionStorageNamespaceIdLength);
base::StringPiece origin_str =
key_as_string.substr(kPrefixBeforeOriginLength);
int64_t map_number;
if (!ValueToNumber(key_value->value, &map_number)) {
error = true;
LOG(ERROR) << "Could not parse map number "
<< leveldb::Uint8VectorToStringPiece(key_value->value);
break;
}
if (map_number >= next_map_id_from_namespaces_)
next_map_id_from_namespaces_ = map_number + 1;
auto origin_gurl = GURL(origin_str);
if (!origin_gurl.is_valid()) {
LOG(ERROR) << "Invalid origin " << origin_str;
error = true;
break;
}
auto origin = url::Origin::Create(origin_gurl);
if (namespace_id != last_namespace_id) {
last_namespace_id = namespace_id.as_string();
DCHECK(namespace_origin_map_.find(last_namespace_id) ==
namespace_origin_map_.end());
last_namespace = &(namespace_origin_map_[last_namespace_id]);
}
auto map_it = maps.find(map_number);
if (map_it == maps.end()) {
map_it =
maps.emplace(std::piecewise_construct,
std::forward_as_tuple(map_number),
std::forward_as_tuple(new MapData(map_number, origin)))
.first;
}
map_it->second->IncReferenceCount();
last_namespace->emplace(std::make_pair(std::move(origin), map_it->second));
}
if (error) {
namespace_origin_map_.clear();
next_map_id_from_namespaces_ = 0;
return false;
}
if (next_map_id_ == 0 || next_map_id_ < next_map_id_from_namespaces_)
next_map_id_ = next_map_id_from_namespaces_;
// Namespace metadata migration.
DCHECK_NE(kInvalidDatabaseVersion, initial_database_version_from_disk_);
if (initial_database_version_from_disk_ == 0) {
// Remove the dummy 'namespaces-' entry.
upgrade_operations->push_back(BatchedOperation::New(
BatchOperationType::DELETE_KEY,
std::vector<uint8_t>(std::begin(kNamespacePrefixBytes),
std::end(kNamespacePrefixBytes)),
base::nullopt));
// Remove all the refcount storage.
for (const auto& map_pair : maps) {
upgrade_operations->push_back(
BatchedOperation::New(BatchOperationType::DELETE_KEY,
map_pair.second->KeyPrefix(), base::nullopt));
}
}
return true;
}
void SessionStorageMetadata::ParseNextMapId(
const std::vector<uint8_t>& map_id) {
if (!ValueToNumber(map_id, &next_map_id_))
next_map_id_ = next_map_id_from_namespaces_;
if (next_map_id_ < next_map_id_from_namespaces_)
next_map_id_ = next_map_id_from_namespaces_;
}
// static
std::vector<uint8_t> SessionStorageMetadata::LatestDatabaseVersionAsVector() {
return NumberToValue(kLatestSessionStorageSchemaVersion);
}
scoped_refptr<SessionStorageMetadata::MapData>
SessionStorageMetadata::RegisterNewMap(
NamespaceEntry namespace_entry,
const url::Origin& origin,
std::vector<leveldb::mojom::BatchedOperationPtr>* save_operations) {
auto new_map_data = base::MakeRefCounted<MapData>(next_map_id_, origin);
++next_map_id_;
save_operations->push_back(BatchedOperation::New(
BatchOperationType::PUT_KEY,
std::vector<uint8_t>(
SessionStorageMetadata::kNextMapIdKeyBytes,
std::end(SessionStorageMetadata::kNextMapIdKeyBytes)),
NumberToValue(next_map_id_)));
std::map<url::Origin, scoped_refptr<MapData>>& namespace_origins =
namespace_entry->second;
auto namespace_it = namespace_origins.find(origin);
if (namespace_it != namespace_origins.end()) {
// Check the old map doesn't have the same number as the new map.
DCHECK(namespace_it->second->MapNumberAsBytes() !=
new_map_data->MapNumberAsBytes());
DCHECK_GT(namespace_it->second->ReferenceCount(), 1)
<< "A new map should never be registered for an area that has a "
"single-refcount map.";
// There was already an area key here, so decrement that map reference.
namespace_it->second->DecReferenceCount();
namespace_it->second = new_map_data;
} else {
namespace_origins.emplace(std::make_pair(origin, new_map_data));
}
new_map_data->IncReferenceCount();
save_operations->push_back(BatchedOperation::New(
BatchOperationType::PUT_KEY, GetAreaKey(namespace_entry->first, origin),
new_map_data->MapNumberAsBytes()));
return new_map_data;
}
void SessionStorageMetadata::RegisterShallowClonedNamespace(
NamespaceEntry source_namespace,
NamespaceEntry destination_namespace,
std::vector<leveldb::mojom::BatchedOperationPtr>* save_operations) {
std::map<url::Origin, scoped_refptr<MapData>>& source_origins =
source_namespace->second;
std::map<url::Origin, scoped_refptr<MapData>>& destination_origins =
destination_namespace->second;
DCHECK_EQ(0ul, destination_origins.size())
<< "The destination already has data.";
save_operations->reserve(save_operations->size() + source_origins.size());
for (const auto& origin_map_pair : source_origins) {
destination_origins.emplace(std::piecewise_construct,
std::forward_as_tuple(origin_map_pair.first),
std::forward_as_tuple(origin_map_pair.second));
origin_map_pair.second->IncReferenceCount();
save_operations->push_back(BatchedOperation::New(
BatchOperationType::PUT_KEY,
GetAreaKey(destination_namespace->first, origin_map_pair.first),
origin_map_pair.second->MapNumberAsBytes()));
}
}
void SessionStorageMetadata::DeleteNamespace(
const std::string& namespace_id,
std::vector<BatchedOperationPtr>* delete_operations) {
auto it = namespace_origin_map_.find(namespace_id);
if (it == namespace_origin_map_.end())
return;
delete_operations->push_back(
BatchedOperation::New(BatchOperationType::DELETE_PREFIXED_KEY,
GetNamespacePrefix(namespace_id), base::nullopt));
const std::map<url::Origin, scoped_refptr<MapData>>& origins = it->second;
for (const auto& origin_map_pair : origins) {
MapData* map_data = origin_map_pair.second.get();
DCHECK_GT(map_data->ReferenceCount(), 0);
map_data->DecReferenceCount();
if (map_data->ReferenceCount() == 0) {
delete_operations->push_back(
BatchedOperation::New(BatchOperationType::DELETE_PREFIXED_KEY,
map_data->KeyPrefix(), base::nullopt));
}
}
namespace_origin_map_.erase(it);
}
void SessionStorageMetadata::DeleteArea(
const std::string& namespace_id,
const url::Origin& origin,
std::vector<BatchedOperationPtr>* delete_operations) {
auto ns_entry = namespace_origin_map_.find(namespace_id);
if (ns_entry == namespace_origin_map_.end())
return;
auto origin_map_it = ns_entry->second.find(origin);
if (origin_map_it == ns_entry->second.end())
return;
MapData* map_data = origin_map_it->second.get();
delete_operations->push_back(
BatchedOperation::New(BatchOperationType::DELETE_KEY,
GetAreaKey(namespace_id, origin), base::nullopt));
DCHECK_GT(map_data->ReferenceCount(), 0);
map_data->DecReferenceCount();
if (map_data->ReferenceCount() == 0) {
delete_operations->push_back(
BatchedOperation::New(BatchOperationType::DELETE_PREFIXED_KEY,
map_data->KeyPrefix(), base::nullopt));
}
ns_entry->second.erase(origin_map_it);
}
SessionStorageMetadata::NamespaceEntry
SessionStorageMetadata::GetOrCreateNamespaceEntry(
const std::string& namespace_id) {
// Note: if the entry exists, emplace will return the existing entry and NOT
// insert a new entry.
return namespace_origin_map_
.emplace(std::piecewise_construct, std::forward_as_tuple(namespace_id),
std::forward_as_tuple())
.first;
}
// static
std::vector<uint8_t> SessionStorageMetadata::GetNamespacePrefix(
const std::string& namespace_id) {
std::vector<uint8_t> namespace_prefix(
SessionStorageMetadata::kNamespacePrefixBytes,
std::end(SessionStorageMetadata::kNamespacePrefixBytes));
namespace_prefix.insert(namespace_prefix.end(), namespace_id.data(),
namespace_id.data() + namespace_id.size());
namespace_prefix.push_back(kNamespaceOriginSeperatorByte);
return namespace_prefix;
}
// static
std::vector<uint8_t> SessionStorageMetadata::GetAreaKey(
const std::string& namespace_id,
const url::Origin& origin) {
std::vector<uint8_t> area_key(
SessionStorageMetadata::kNamespacePrefixBytes,
std::end(SessionStorageMetadata::kNamespacePrefixBytes));
area_key.insert(area_key.end(), namespace_id.begin(), namespace_id.end());
area_key.push_back(kNamespaceOriginSeperatorByte);
std::string origin_str = origin.GetURL().spec();
area_key.insert(area_key.end(), origin_str.data(),
origin_str.data() + origin_str.size());
return area_key;
}
// static
std::vector<uint8_t> SessionStorageMetadata::GetMapPrefix(int64_t map_number) {
return GetMapPrefix(NumberToValue(map_number));
}
// static
std::vector<uint8_t> SessionStorageMetadata::GetMapPrefix(
const std::vector<uint8_t>& map_number_as_bytes) {
std::vector<uint8_t> map_prefix(kMapIdPrefixBytes,
std::end(kMapIdPrefixBytes));
map_prefix.insert(map_prefix.end(), map_number_as_bytes.begin(),
map_number_as_bytes.end());
map_prefix.push_back(kNamespaceOriginSeperatorByte);
return map_prefix;
}
} // namespace content