blob: 5c2ae8cfa4567d0090590207975e40f0a7462aa0 [file] [log] [blame]
// Copyright 2015 Google Inc. 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/network/persistent_cookie_store.h"
#include <vector>
#include "base/bind.h"
#include "base/debug/trace_event.h"
#include "base/message_loop.h"
#include "googleurl/src/gurl.h"
#include "sql/connection.h"
#include "sql/statement.h"
namespace cobalt {
namespace network {
namespace {
const int kOriginalCookieSchemaVersion = 1;
const int kLatestCookieSchemaVersion = 1;
const base::TimeDelta kMaxCookieLifetime = base::TimeDelta::FromDays(365 * 2);
std::vector<net::CanonicalCookie*> GetAllCookies(sql::Connection* conn) {
std::vector<net::CanonicalCookie*> actual_cookies;
sql::Statement get_all(conn->GetCachedStatement(
SQL_FROM_HERE,
"SELECT url, name, value, domain, path, mac_key, mac_algorithm, "
"creation, expiration, last_access, secure, http_only "
"FROM CookieTable"));
while (get_all.Step()) {
// We create a CanonicalCookie directly through its constructor instead of
// through CanonicalCookie::Create() and its sanitization because these
// values are just serialized from a former instance of a CanonicalCookie
// object that *was* created through CanonicalCookie::Create().
net::CanonicalCookie* cookie = new net::CanonicalCookie(
GURL(get_all.ColumnString(0)), get_all.ColumnString(1),
get_all.ColumnString(2), get_all.ColumnString(3),
get_all.ColumnString(4), get_all.ColumnString(5),
get_all.ColumnString(6),
base::Time::FromInternalValue(get_all.ColumnInt64(7)),
base::Time::FromInternalValue(get_all.ColumnInt64(8)),
base::Time::FromInternalValue(get_all.ColumnInt64(9)),
get_all.ColumnBool(10), get_all.ColumnBool(11));
actual_cookies.push_back(cookie);
}
return actual_cookies;
}
void SqlInit(const PersistentCookieStore::LoadedCallback& loaded_callback,
storage::SqlContext* sql_context) {
TRACE_EVENT0("cobalt::network", "PersistentCookieStore::SqlInit()");
sql::Connection* conn = sql_context->sql_connection();
// Check the table's schema version.
int schema_version;
bool table_exists =
sql_context->GetSchemaVersion("CookieTable", &schema_version);
if (table_exists) {
if (schema_version == storage::StorageManager::kSchemaTableIsNew) {
// This savegame predates the existence of the schema table.
// Since the cookie table did not change between the initial release of
// the app and the introduction of the schema table, assume that this
// existing cookie table is schema version 1. This avoids a loss of
// cookies on upgrade.
DLOG(INFO) << "Updating CookieTable schema version to "
<< kOriginalCookieSchemaVersion;
sql_context->UpdateSchemaVersion("CookieTable",
kOriginalCookieSchemaVersion);
} else if (schema_version == storage::StorageManager::kSchemaVersionLost) {
// Since there has only been one schema so far, treat this the same as
// kSchemaTableIsNew. When there are multiple schemas in the wild,
// we may want to drop the table instead.
sql_context->UpdateSchemaVersion("CookieTable",
kOriginalCookieSchemaVersion);
}
} else {
// The table does not exist, so create it in its latest form.
sql::Statement create_table(conn->GetUniqueStatement(
"CREATE TABLE CookieTable ("
"url TEXT, "
"name TEXT, "
"value TEXT, "
"domain TEXT, "
"path TEXT, "
"mac_key TEXT, "
"mac_algorithm TEXT, "
"creation INTEGER, "
"expiration INTEGER, "
"last_access INTEGER, "
"secure INTEGER, "
"http_only INTEGER, "
"UNIQUE(name, domain, path) ON CONFLICT REPLACE)"));
bool ok = create_table.Run();
DCHECK(ok);
sql_context->UpdateSchemaVersion("CookieTable", kLatestCookieSchemaVersion);
}
loaded_callback.Run(GetAllCookies(conn));
}
void SqlAddCookie(const net::CanonicalCookie& cc,
storage::SqlContext* sql_context) {
base::Time maximum_expiry = base::Time::Now() + kMaxCookieLifetime;
base::Time expiry = cc.ExpiryDate();
if (expiry > maximum_expiry) {
expiry = maximum_expiry;
}
sql::Connection* conn = sql_context->sql_connection();
sql::Statement insert_cookie(conn->GetCachedStatement(
SQL_FROM_HERE,
"INSERT INTO CookieTable ("
"url, name, value, domain, path, mac_key, mac_algorithm, "
"creation, expiration, last_access, secure, http_only"
") VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"));
insert_cookie.BindString(0, cc.Source());
insert_cookie.BindString(1, cc.Name());
insert_cookie.BindString(2, cc.Value());
insert_cookie.BindString(3, cc.Domain());
insert_cookie.BindString(4, cc.Path());
insert_cookie.BindString(5, cc.MACKey());
insert_cookie.BindString(6, cc.MACAlgorithm());
insert_cookie.BindInt64(7, cc.CreationDate().ToInternalValue());
insert_cookie.BindInt64(8, expiry.ToInternalValue());
insert_cookie.BindInt64(9, cc.LastAccessDate().ToInternalValue());
insert_cookie.BindBool(10, cc.IsSecure());
insert_cookie.BindBool(11, cc.IsHttpOnly());
bool ok = insert_cookie.Run();
DCHECK(ok);
sql_context->FlushOnChange();
}
void SqlUpdateCookieAccessTime(const net::CanonicalCookie& cc,
storage::SqlContext* sql_context) {
sql::Connection* conn = sql_context->sql_connection();
sql::Statement touch_cookie(
conn->GetCachedStatement(SQL_FROM_HERE,
"UPDATE CookieTable SET last_access = ? WHERE "
"name = ? AND domain = ? AND path = ?"));
base::Time maximum_expiry = base::Time::Now() + kMaxCookieLifetime;
base::Time expiry = cc.ExpiryDate();
if (expiry > maximum_expiry) {
expiry = maximum_expiry;
}
touch_cookie.BindInt64(0, expiry.ToInternalValue());
touch_cookie.BindString(1, cc.Name());
touch_cookie.BindString(2, cc.Domain());
touch_cookie.BindString(3, cc.Path());
bool ok = touch_cookie.Run();
DCHECK(ok);
sql_context->FlushOnChange();
}
void SqlDeleteCookie(const net::CanonicalCookie& cc,
storage::SqlContext* sql_context) {
sql::Connection* conn = sql_context->sql_connection();
sql::Statement delete_cookie(conn->GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM CookieTable WHERE name = ? AND domain = ? AND path = ?"));
delete_cookie.BindString(0, cc.Name());
delete_cookie.BindString(1, cc.Domain());
delete_cookie.BindString(2, cc.Path());
bool ok = delete_cookie.Run();
DCHECK(ok);
sql_context->FlushOnChange();
}
void SqlSendEmptyCookieList(
const PersistentCookieStore::LoadedCallback& loaded_callback,
storage::SqlContext* sql_context) {
UNREFERENCED_PARAMETER(sql_context);
std::vector<net::CanonicalCookie*> empty_cookie_list;
loaded_callback.Run(empty_cookie_list);
}
} // namespace
PersistentCookieStore::PersistentCookieStore(storage::StorageManager* storage)
: storage_(storage) {}
PersistentCookieStore::~PersistentCookieStore() {}
void PersistentCookieStore::Load(const LoadedCallback& loaded_callback) {
storage_->GetSqlContext(base::Bind(&SqlInit, loaded_callback));
}
void PersistentCookieStore::LoadCookiesForKey(
const std::string& key, const LoadedCallback& loaded_callback) {
UNREFERENCED_PARAMETER(key);
// We don't support loading of individual cookies.
// This is always called after Load(), so just post the callback to the SQL
// thread to make sure it is run after Load() has finished.
// See comments in net/cookie_monster.cc for more information.
storage_->GetSqlContext(base::Bind(&SqlSendEmptyCookieList,
loaded_callback));
}
void PersistentCookieStore::AddCookie(const net::CanonicalCookie& cc) {
// We expect that all cookies we are fed are meant to persist.
DCHECK(cc.IsPersistent());
storage_->GetSqlContext(base::Bind(&SqlAddCookie, cc));
}
void PersistentCookieStore::UpdateCookieAccessTime(
const net::CanonicalCookie& cc) {
storage_->GetSqlContext(base::Bind(&SqlUpdateCookieAccessTime, cc));
}
void PersistentCookieStore::DeleteCookie(const net::CanonicalCookie& cc) {
storage_->GetSqlContext(base::Bind(&SqlDeleteCookie, cc));
}
void PersistentCookieStore::SetForceKeepSessionState() {
// We don't expect this to be called, and we don't implement these semantics.
NOTREACHED();
}
void PersistentCookieStore::Flush(const base::Closure& callback) {
storage_->FlushNow(callback);
}
} // namespace network
} // namespace cobalt