blob: e7bcb907f1557af9a0821542ef835731bab77dea [file] [log] [blame]
// Copyright 2018 The Cobalt Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "cobalt/storage/store_upgrade/upgrade.h"
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <vector>
#include "base/files/file_path.h"
#include "base/optional.h"
#include "base/strings/string_split.h"
#include "base/trace_event/trace_event.h"
#include "cobalt/storage/storage_constants.h"
#include "cobalt/storage/store/memory_store.h"
#include "cobalt/storage/store/storage.pb.h"
#include "cobalt/storage/store_upgrade/sql_vfs.h"
#include "cobalt/storage/store_upgrade/virtual_file.h"
#include "cobalt/storage/store_upgrade/virtual_file_system.h"
#include "nb/memory_scope.h"
#include "net/cookies/canonical_cookie.h"
#include "sql/connection.h"
#include "sql/statement.h"
#include "starboard/storage.h"
#include "third_party/sqlite/sqlite3.h"
#include "url/gurl.h"
namespace cobalt {
namespace storage {
namespace store_upgrade {
namespace {
constexpr char kDefaultSaveFile[] = "cobalt_save.bin";
typedef std::map<std::string, std::string> StorageMap;
const std::string& GetFirstValidDatabaseFile(
const std::vector<std::string>& filenames) {
// Caller must ensure at least one file exists.
DCHECK_GT(filenames.size(), size_t(0));
for (size_t i = 0; i < filenames.size(); ++i) {
sql::Connection connection;
bool is_opened = connection.Open(base::FilePath(filenames[i]));
if (!is_opened) {
continue;
}
int err = connection.ExecuteAndReturnErrorCode("pragma schema_version;");
if (err != SQLITE_OK) {
continue;
}
// File can be opened as a database.
return filenames[i];
}
// Caller must handle case where a valid database file cannot be found.
DLOG(WARNING) << "Cannot find valid database file in save data";
return filenames[0];
}
void GetAllCookies(sql::Connection* conn, Storage* storage) {
sql::Statement get_all(conn->GetCachedStatement(
SQL_FROM_HERE,
"SELECT domain, path, name, value, creation, expiration, last_access, "
"secure, http_only FROM CookieTable"));
while (get_all.Step()) {
Cookie* cookie = storage->add_cookies();
cookie->set_domain(get_all.ColumnString(0));
cookie->set_path(get_all.ColumnString(1));
cookie->set_name(get_all.ColumnString(2));
cookie->set_value(get_all.ColumnString(3));
cookie->set_creation_time_us(get_all.ColumnInt64(4));
cookie->set_expiration_time_us(get_all.ColumnInt64(5));
cookie->set_last_access_time_us(get_all.ColumnInt64(6));
cookie->set_secure(get_all.ColumnBool(7));
cookie->set_http_only(get_all.ColumnBool(8));
DLOG(INFO) << "GetAllCookies: "
<< " domain=" << cookie->domain() << " path=" << cookie->path()
<< " name=" << cookie->name() << " value=" << cookie->value()
<< " creation=" << cookie->creation_time_us()
<< " expiration=" << cookie->expiration_time_us()
<< " last_access=" << cookie->last_access_time_us()
<< " secure=" << cookie->secure()
<< " http_only=" << cookie->http_only();
}
}
base::Optional<loader::Origin> ParseLocalStorageId(const std::string& id) {
std::vector<std::string> id_tokens =
base::SplitString(id, "_", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (id_tokens.size() != 3) {
DLOG(WARNING) << "Failed to parse id=" << id;
return base::nullopt;
}
std::string url = id_tokens[0];
url += "://";
url += id_tokens[1];
std::vector<std::string> port_tokens = base::SplitString(
id_tokens[2], ".", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
if (port_tokens.size() != 2) {
return base::nullopt;
}
if (port_tokens[0] != "0") {
url += ":";
url += port_tokens[0];
}
GURL gurl(url);
loader::Origin origin(gurl);
if (origin.is_opaque()) {
DLOG(WARNING) << "Missing Serialized Origin for id=" << id;
return base::nullopt;
}
return origin;
}
void GetLocalStorage(sql::Connection* conn, Storage* storage) {
sql::Statement get_all(conn->GetCachedStatement(
SQL_FROM_HERE,
"SELECT site_identifier, key, value FROM LocalStorageTable"));
std::map<std::string, LocalStorage*> map_storage;
while (get_all.Step()) {
std::string id(get_all.ColumnString(0));
base::Optional<loader::Origin> origin = ParseLocalStorageId(id);
if (!origin) {
continue;
}
DLOG(INFO) << "GetLocalStorage: id=" << id;
if (map_storage[origin->SerializedOrigin()] == nullptr) {
LocalStorage* local_storage = storage->add_local_storages();
local_storage->set_serialized_origin(origin->SerializedOrigin());
map_storage[origin->SerializedOrigin()] = local_storage;
}
LocalStorageEntry* local_storage_entry =
map_storage[origin->SerializedOrigin()]->add_local_storage_entries();
local_storage_entry->set_key(get_all.ColumnString(1));
local_storage_entry->set_value(get_all.ColumnString(2));
DLOG(INFO) << "GetLocalStorage: key=" << local_storage_entry->key()
<< " value=" << local_storage_entry->value();
}
}
bool OpenConnection(const std::vector<uint8>& raw_bytes,
const std::unique_ptr<VirtualFileSystem>& vfs,
const std::unique_ptr<SqlVfs>& sql_vfs,
const std::unique_ptr<sql::Connection>& connection) {
VirtualFileSystem::SerializedHeader header = {};
if (raw_bytes.size() > 0) {
const char* buffer = reinterpret_cast<const char*>(raw_bytes.data());
int buffer_size = static_cast<int>(raw_bytes.size());
if (raw_bytes.size() >= sizeof(VirtualFileSystem::SerializedHeader)) {
memcpy(&header, buffer, sizeof(VirtualFileSystem::SerializedHeader));
}
if (!vfs->Deserialize(raw_bytes.data(), buffer_size)) {
DLOG(ERROR) << "Failed to deserialize vfs";
return false;
}
}
std::vector<std::string> filenames = vfs->ListFiles();
if (filenames.size() == 0) {
filenames.push_back(kDefaultSaveFile);
}
// Very old legacy save data may contain multiple files (e.g. db-journal as
// well as db), so use the first one that looks like a valid database file.
const std::string& save_name = GetFirstValidDatabaseFile(filenames);
bool ok = connection->Open(base::FilePath(save_name));
if (!ok) {
DLOG(WARNING) << "Failed to open file: " << save_name;
return false;
}
// Open() is lazy. Run a quick check to see if the database is valid.
int err = connection->ExecuteAndReturnErrorCode("pragma schema_version;");
if (err != SQLITE_OK) {
// Database seems to be invalid.
DLOG(WARNING) << "Database " << save_name << " appears to be corrupt.";
return false;
}
// Disable journaling for our in-memory database.
sql::Statement disable_journal(
connection->GetUniqueStatement("PRAGMA journal_mode=OFF"));
ok = disable_journal.Step();
DCHECK(ok);
return true;
}
} // namespace
bool IsUpgradeRequired(const std::vector<uint8>& buffer) {
bool result = buffer.size() >= kStorageHeaderSize &&
memcmp(reinterpret_cast<const char*>(buffer.data()),
kOldStorageHeader, kStorageHeaderSize) == 0;
if (result) {
DLOG(INFO) << "Store upgrade required";
} else {
DLOG(INFO) << "Store upgrade not required";
}
return result;
}
bool UpgradeStore(std::vector<uint8>* buffer) {
std::unique_ptr<VirtualFileSystem> vfs(new VirtualFileSystem());
std::unique_ptr<SqlVfs> sql_vfs(new SqlVfs("cobalt_vfs", vfs.get()));
std::unique_ptr<sql::Connection> connection(new sql::Connection());
if (!OpenConnection(*buffer, vfs, sql_vfs, connection)) {
return false;
}
Storage storage;
GetAllCookies(connection.get(), &storage);
GetLocalStorage(connection.get(), &storage);
connection->Close();
size_t size = storage.ByteSize();
buffer->resize(kStorageHeaderSize + size);
char* buffer_ptr = reinterpret_cast<char*>(buffer->data());
memcpy(buffer_ptr, kStorageHeader, kStorageHeaderSize);
if (size > 0 &&
!storage.SerializeToArray(buffer_ptr + kStorageHeaderSize, size)) {
DLOG(ERROR) << "Failed to serialize message with size=" << size;
return false;
}
return true;
}
} // namespace store_upgrade
} // namespace storage
} // namespace cobalt