blob: a6e5a1ee0e6111af66296dee45ecb808a5695ad4 [file] [log] [blame]
// Copyright 2017 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 "services/network/restricted_cookie_manager.h"
#include <memory>
#include <utility>
#include <vector>
#include "base/bind.h"
#include "base/memory/weak_ptr.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "mojo/public/cpp/bindings/message.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_store.h"
namespace network {
namespace {
// TODO(pwnall): De-duplicate from cookie_manager.cc
mojom::CookieChangeCause ToCookieChangeCause(net::CookieChangeCause net_cause) {
switch (net_cause) {
case net::CookieChangeCause::INSERTED:
return mojom::CookieChangeCause::INSERTED;
case net::CookieChangeCause::EXPLICIT:
return mojom::CookieChangeCause::EXPLICIT;
case net::CookieChangeCause::UNKNOWN_DELETION:
return mojom::CookieChangeCause::UNKNOWN_DELETION;
case net::CookieChangeCause::OVERWRITE:
return mojom::CookieChangeCause::OVERWRITE;
case net::CookieChangeCause::EXPIRED:
return mojom::CookieChangeCause::EXPIRED;
case net::CookieChangeCause::EVICTED:
return mojom::CookieChangeCause::EVICTED;
case net::CookieChangeCause::EXPIRED_OVERWRITE:
return mojom::CookieChangeCause::EXPIRED_OVERWRITE;
}
NOTREACHED();
return mojom::CookieChangeCause::EXPLICIT;
}
net::CookieStore::SetCookiesCallback StatusToBool(
base::OnceCallback<void(bool)> callback) {
return base::BindOnce(
[](base::OnceCallback<void(bool)> callback,
const net::CanonicalCookie::CookieInclusionStatus status) {
bool success =
(status == net::CanonicalCookie::CookieInclusionStatus::INCLUDE);
std::move(callback).Run(success);
},
std::move(callback));
}
} // anonymous namespace
class RestrictedCookieManager::Listener : public base::LinkNode<Listener> {
public:
Listener(net::CookieStore* cookie_store,
const GURL& url,
net::CookieOptions options,
mojom::CookieChangeListenerPtr mojo_listener)
: url_(url), options_(options), mojo_listener_(std::move(mojo_listener)) {
// TODO(pwnall): add a constructor w/options to net::CookieChangeDispatcher.
cookie_store_subscription_ =
cookie_store->GetChangeDispatcher().AddCallbackForUrl(
url,
base::BindRepeating(
&Listener::OnCookieChange,
// Safe because net::CookieChangeDispatcher guarantees that the
// callback will stop being called immediately after we remove
// the subscription, and the cookie store lives on the same
// thread as we do.
base::Unretained(this)));
}
~Listener() { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); }
mojom::CookieChangeListenerPtr& mojo_listener() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return mojo_listener_;
}
private:
// net::CookieChangeDispatcher callback.
void OnCookieChange(const net::CanonicalCookie& cookie,
net::CookieChangeCause cause) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (cookie.IncludeForRequestURL(url_, options_) !=
net::CanonicalCookie::CookieInclusionStatus::INCLUDE)
return;
mojo_listener_->OnCookieChange(cookie, ToCookieChangeCause(cause));
}
// The CookieChangeDispatcher subscription used by this listener.
std::unique_ptr<net::CookieChangeSubscription> cookie_store_subscription_;
// The URL whose cookies this listener is interested in.
const GURL url_;
// CanonicalCookie::IncludeForRequestURL options for this listener's interest.
const net::CookieOptions options_;
mojom::CookieChangeListenerPtr mojo_listener_;
SEQUENCE_CHECKER(sequence_checker_);
DISALLOW_COPY_AND_ASSIGN(Listener);
};
RestrictedCookieManager::RestrictedCookieManager(net::CookieStore* cookie_store,
const url::Origin& origin)
: cookie_store_(cookie_store), origin_(origin), weak_ptr_factory_(this) {
DCHECK(cookie_store);
}
RestrictedCookieManager::~RestrictedCookieManager() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
base::LinkNode<Listener>* node = listeners_.head();
while (node != listeners_.end()) {
Listener* listener_reference = node->value();
node = node->next();
// The entire list is going away, no need to remove nodes from it.
delete listener_reference;
}
}
void RestrictedCookieManager::GetAllForUrl(
const GURL& url,
const GURL& site_for_cookies,
mojom::CookieManagerGetOptionsPtr options,
GetAllForUrlCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ValidateAccessToCookiesAt(url)) {
std::move(callback).Run({});
return;
}
net::CookieOptions net_options;
if (net::registry_controlled_domains::SameDomainOrHost(
url, site_for_cookies,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
// TODO(mkwst): This check ought to further distinguish between frames
// initiated in a strict or lax same-site context.
net_options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
} else {
net_options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::DO_NOT_INCLUDE);
}
cookie_store_->GetCookieListWithOptionsAsync(
url, net_options,
base::BindOnce(&RestrictedCookieManager::CookieListToGetAllForUrlCallback,
weak_ptr_factory_.GetWeakPtr(), url, site_for_cookies,
std::move(options), std::move(callback)));
}
void RestrictedCookieManager::CookieListToGetAllForUrlCallback(
const GURL& url,
const GURL& site_for_cookies,
mojom::CookieManagerGetOptionsPtr options,
GetAllForUrlCallback callback,
const net::CookieList& cookie_list,
const net::CookieStatusList& excluded_cookies) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// TODO(pwnall): Call NetworkDelegate::CanGetCookies() on a NetworkDelegate
// associated with the NetworkContext.
std::vector<net::CanonicalCookie> result;
result.reserve(cookie_list.size());
mojom::CookieMatchType match_type = options->match_type;
const std::string& match_name = options->name;
for (size_t i = 0; i < cookie_list.size(); ++i) {
const net::CanonicalCookie& cookie = cookie_list[i];
const std::string& cookie_name = cookie.Name();
if (match_type == mojom::CookieMatchType::EQUALS) {
if (cookie_name != match_name)
continue;
} else if (match_type == mojom::CookieMatchType::STARTS_WITH) {
if (!base::StartsWith(cookie_name, match_name,
base::CompareCase::SENSITIVE)) {
continue;
}
} else {
NOTREACHED();
}
result.emplace_back(cookie);
}
std::move(callback).Run(std::move(result));
}
void RestrictedCookieManager::SetCanonicalCookie(
const net::CanonicalCookie& cookie,
const GURL& url,
const GURL& site_for_cookies,
SetCanonicalCookieCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ValidateAccessToCookiesAt(url)) {
std::move(callback).Run(false);
return;
}
// TODO(pwnall): Validate the CanonicalCookie fields.
// TODO(pwnall): Call NetworkDelegate::CanSetCookie() on a NetworkDelegate
// associated with the NetworkContext.
base::Time now = base::Time::NowFromSystemTime();
auto sanitized_cookie = std::make_unique<net::CanonicalCookie>(
cookie.Name(), cookie.Value(), cookie.Domain(), cookie.Path(), now,
cookie.ExpiryDate(), now, cookie.IsSecure(), cookie.IsHttpOnly(),
cookie.SameSite(), cookie.Priority());
// TODO(pwnall): source_scheme might depend on the renderer.
bool modify_http_only = false;
cookie_store_->SetCanonicalCookieAsync(std::move(sanitized_cookie),
origin_.scheme(), modify_http_only,
StatusToBool(std::move(callback)));
}
void RestrictedCookieManager::AddChangeListener(
const GURL& url,
const GURL& site_for_cookies,
mojom::CookieChangeListenerPtr mojo_listener,
AddChangeListenerCallback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!ValidateAccessToCookiesAt(url)) {
std::move(callback).Run();
return;
}
net::CookieOptions net_options;
if (net::registry_controlled_domains::SameDomainOrHost(
url, site_for_cookies,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) {
// TODO(mkwst): This check ought to further distinguish between frames
// initiated in a strict or lax same-site context.
net_options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::INCLUDE_STRICT_AND_LAX);
} else {
net_options.set_same_site_cookie_mode(
net::CookieOptions::SameSiteCookieMode::DO_NOT_INCLUDE);
}
auto listener = std::make_unique<Listener>(cookie_store_, url, net_options,
std::move(mojo_listener));
listener->mojo_listener().set_connection_error_handler(
base::BindOnce(&RestrictedCookieManager::RemoveChangeListener,
weak_ptr_factory_.GetWeakPtr(),
// Safe because this owns the listener, so the listener is
// guaranteed to be alive for as long as the weak pointer
// above resolves.
base::Unretained(listener.get())));
// The linked list takes over the Listener ownership.
listeners_.Append(listener.release());
std::move(callback).Run();
}
void RestrictedCookieManager::RemoveChangeListener(Listener* listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
listener->RemoveFromList();
delete listener;
}
bool RestrictedCookieManager::ValidateAccessToCookiesAt(const GURL& url) {
if (origin_.IsSameOriginWith(url::Origin::Create(url)))
return true;
mojo::ReportBadMessage("Incorrect url origin");
return false;
}
} // namespace network