| // Copyright 2019 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "fuchsia_web/webengine/browser/cookie_manager_impl.h" |
| |
| #include <lib/fidl/cpp/binding.h> |
| |
| #include "base/fuchsia/fuchsia_logging.h" |
| #include "mojo/public/cpp/bindings/pending_remote.h" |
| #include "mojo/public/cpp/bindings/receiver.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_change_dispatcher.h" |
| #include "services/network/public/mojom/network_context.mojom.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| fuchsia::web::Cookie ConvertCanonicalCookie( |
| const net::CanonicalCookie& canonical_cookie, |
| net::CookieChangeCause cause) { |
| fuchsia::web::CookieId id; |
| id.set_name(canonical_cookie.Name()); |
| id.set_domain(canonical_cookie.Domain()); |
| id.set_path(canonical_cookie.Path()); |
| |
| fuchsia::web::Cookie cookie; |
| cookie.set_id(std::move(id)); |
| switch (cause) { |
| case net::CookieChangeCause::INSERTED: |
| cookie.set_value(canonical_cookie.Value()); |
| break; |
| case net::CookieChangeCause::EXPLICIT: |
| case net::CookieChangeCause::UNKNOWN_DELETION: |
| case net::CookieChangeCause::OVERWRITE: |
| case net::CookieChangeCause::EXPIRED: |
| case net::CookieChangeCause::EVICTED: |
| case net::CookieChangeCause::EXPIRED_OVERWRITE: |
| break; |
| }; |
| |
| return cookie; |
| } |
| |
| class CookiesIteratorImpl final : public fuchsia::web::CookiesIterator, |
| public network::mojom::CookieChangeListener { |
| public: |
| // |this| will delete itself when |mojo_request| or |changes| disconnect. |
| CookiesIteratorImpl( |
| mojo::PendingReceiver<network::mojom::CookieChangeListener> mojo_receiver, |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> changes) |
| : CookiesIteratorImpl(std::move(changes)) { |
| mojo_receiver_.Bind(std::move(mojo_receiver)); |
| mojo_receiver_.set_disconnect_handler(base::BindOnce( |
| &CookiesIteratorImpl::OnMojoError, base::Unretained(this))); |
| } |
| // |this| will delete itself when |iterator| disconnects, or if a GetNext() |
| // leaves |queued_cookies_| empty. |
| CookiesIteratorImpl( |
| const std::vector<net::CanonicalCookie>& cookies, |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> iterator) |
| : CookiesIteratorImpl(std::move(iterator)) { |
| for (const auto& cookie : cookies) { |
| queued_cookies_[cookie.UniqueKey()] = |
| ConvertCanonicalCookie(cookie, net::CookieChangeCause::INSERTED); |
| } |
| } |
| // Same as above except it takes CookieAccessResultList instead of just |
| // CookieList. |
| CookiesIteratorImpl( |
| const std::vector<net::CookieWithAccessResult>& |
| cookies_with_access_results, |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> iterator) |
| : CookiesIteratorImpl(std::move(iterator)) { |
| for (const auto& cookie_with_access_result : cookies_with_access_results) { |
| queued_cookies_[cookie_with_access_result.cookie.UniqueKey()] = |
| ConvertCanonicalCookie(cookie_with_access_result.cookie, |
| net::CookieChangeCause::INSERTED); |
| } |
| } |
| |
| CookiesIteratorImpl(const CookiesIteratorImpl&) = delete; |
| CookiesIteratorImpl& operator=(const CookiesIteratorImpl&) = delete; |
| |
| ~CookiesIteratorImpl() override = default; |
| |
| // fuchsia::web::CookiesIterator implementation: |
| void GetNext(GetNextCallback callback) override { |
| DCHECK(!get_next_callback_); |
| get_next_callback_ = std::move(callback); |
| MaybeSendQueuedCookies(); |
| } |
| |
| private: |
| explicit CookiesIteratorImpl( |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> iterator) |
| : mojo_receiver_(this), fidl_binding_(this) { |
| fidl_binding_.Bind(std::move(iterator)); |
| fidl_binding_.set_error_handler([this](zx_status_t status) { |
| ZX_LOG_IF(ERROR, status != ZX_ERR_PEER_CLOSED, status) |
| << "CookieChangeListener disconnected."; |
| delete this; |
| }); |
| } |
| |
| void OnMojoError() { |
| LOG(ERROR) << "NetworkService disconnected CookiesIterator."; |
| fidl_binding_.Close(ZX_ERR_UNAVAILABLE); |
| delete this; |
| } |
| |
| void MaybeSendQueuedCookies() { |
| // Assuming cookies values never exceed 4KB in size, plus some overhead for |
| // the name, domain and path, and that Zircon messages can be up to 64KB. |
| constexpr int kMaxCookiesPerMessage = 8; |
| |
| if (!get_next_callback_) |
| return; |
| if (mojo_receiver_.is_bound() && queued_cookies_.empty()) |
| return; |
| |
| // Build a vector of Cookies to return to the caller. |
| fuchsia::web::CookiesIterator::GetNextCallback callback( |
| std::move(get_next_callback_)); |
| std::vector<fuchsia::web::Cookie> cookies; |
| while (!queued_cookies_.empty() && cookies.size() < kMaxCookiesPerMessage) { |
| auto cookie = queued_cookies_.begin(); |
| cookies.emplace_back(std::move(cookie->second)); |
| queued_cookies_.erase(cookie); |
| } |
| callback(std::move(cookies)); |
| |
| // If this is a one-off CookieIterator then tear down once |queued_cookies_| |
| // is empty. |
| if (queued_cookies_.empty() && !mojo_receiver_.is_bound()) |
| delete this; |
| } |
| |
| // network::mojom::CookieChangeListener implementation: |
| void OnCookieChange(const net::CookieChangeInfo& change) override { |
| queued_cookies_[change.cookie.UniqueKey()] = |
| ConvertCanonicalCookie(change.cookie, change.cause); |
| MaybeSendQueuedCookies(); |
| } |
| |
| mojo::Receiver<network::mojom::CookieChangeListener> mojo_receiver_; |
| fidl::Binding<fuchsia::web::CookiesIterator> fidl_binding_; |
| |
| GetNextCallback get_next_callback_; |
| |
| // Map from "unique key"s (see net::CanonicalCookie::UniqueKey()) to the |
| // corresponding fuchsia::web::Cookie. |
| std::map<net::CanonicalCookie::UniqueCookieKey, fuchsia::web::Cookie> |
| queued_cookies_; |
| }; |
| |
| void OnAllCookiesReceived( |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> iterator, |
| const std::vector<net::CanonicalCookie>& cookies) { |
| new CookiesIteratorImpl(cookies, std::move(iterator)); |
| } |
| |
| void OnCookiesAndExcludedReceived( |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> iterator, |
| const std::vector<net::CookieWithAccessResult>& cookies_with_access_results, |
| const std::vector<net::CookieWithAccessResult>& excluded_cookies) { |
| // Since CookieOptions::set_return_excluded_cookies() is not used when calling |
| // the Mojo GetCookieList() API, |excluded_cookies| should be empty. |
| DCHECK(excluded_cookies.empty()); |
| new CookiesIteratorImpl(cookies_with_access_results, std::move(iterator)); |
| } |
| |
| } // namespace |
| |
| CookieManagerImpl::CookieManagerImpl( |
| network::NetworkContextGetter get_network_context) |
| : get_network_context_(std::move(get_network_context)) {} |
| |
| CookieManagerImpl::~CookieManagerImpl() = default; |
| |
| void CookieManagerImpl::ObserveCookieChanges( |
| fidl::StringPtr url, |
| fidl::StringPtr name, |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> changes) { |
| EnsureCookieManager(); |
| |
| mojo::PendingRemote<network::mojom::CookieChangeListener> mojo_listener; |
| new CookiesIteratorImpl(mojo_listener.InitWithNewPipeAndPassReceiver(), |
| std::move(changes)); |
| |
| if (url) { |
| std::optional<std::string> maybe_name; |
| if (name) |
| maybe_name = *name; |
| cookie_manager_->AddCookieChangeListener(GURL(*url), maybe_name, |
| std::move(mojo_listener)); |
| } else { |
| cookie_manager_->AddGlobalChangeListener(std::move(mojo_listener)); |
| } |
| } |
| |
| void CookieManagerImpl::GetCookieList( |
| fidl::StringPtr url, |
| fidl::StringPtr name, |
| fidl::InterfaceRequest<fuchsia::web::CookiesIterator> iterator) { |
| EnsureCookieManager(); |
| |
| if (!url && !name) { |
| cookie_manager_->GetAllCookies( |
| base::BindOnce(&OnAllCookiesReceived, std::move(iterator))); |
| } else { |
| if (!name) { |
| // Include HTTP and 1st-party-only cookies in those returned. |
| net::CookieOptions options; |
| options.set_include_httponly(); |
| options.set_same_site_cookie_context( |
| net::CookieOptions::SameSiteCookieContext::MakeInclusive()); |
| |
| cookie_manager_->GetCookieList( |
| GURL(*url), options, net::CookiePartitionKeyCollection::Todo(), |
| base::BindOnce(&OnCookiesAndExcludedReceived, std::move(iterator))); |
| } else { |
| // TODO(858853): Support filtering by name. |
| iterator.Close(ZX_ERR_NOT_SUPPORTED); |
| } |
| } |
| } |
| |
| void CookieManagerImpl::EnsureCookieManager() { |
| if (cookie_manager_.is_bound()) |
| return; |
| get_network_context_.Run()->GetCookieManager( |
| cookie_manager_.BindNewPipeAndPassReceiver()); |
| cookie_manager_.set_disconnect_handler(base::BindOnce( |
| &CookieManagerImpl::OnMojoDisconnect, base::Unretained(this))); |
| } |
| |
| void CookieManagerImpl::OnMojoDisconnect() { |
| LOG(ERROR) << "NetworkService disconnected CookieManager."; |
| if (on_mojo_disconnected_for_test_) |
| std::move(on_mojo_disconnected_for_test_).Run(); |
| cookie_manager_.reset(); |
| } |