cef/libcef/browser/net_service/cookie_helper.cc

259 lines
9.0 KiB
C++

// Copyright (c) 2019 The Chromium Embedded Framework 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 "libcef/browser/net_service/cookie_helper.h"
#include "libcef/browser/thread_util.h"
#include "libcef/common/net_service/net_service_util.h"
#include "base/bind.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/url_constants.h"
#include "net/base/load_flags.h"
#include "net/cookies/cookie_options.h"
#include "net/cookies/cookie_util.h"
#include "services/network/cookie_manager.h"
#include "services/network/public/cpp/resource_request.h"
namespace net_service {
namespace cookie_helper {
namespace {
// Do not keep a reference to the CookieManager returned by this method.
network::mojom::CookieManager* GetCookieManager(
content::BrowserContext* browser_context) {
CEF_REQUIRE_UIT();
return content::BrowserContext::GetDefaultStoragePartition(browser_context)
->GetCookieManagerForBrowserProcess();
}
net::CookieOptions GetCookieOptions(const network::ResourceRequest& request) {
// Match the logic from InterceptionJob::FetchCookies and
// ChromeContentBrowserClient::ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel.
bool should_treat_as_first_party =
request.url.SchemeIsCryptographic() &&
request.site_for_cookies.scheme() == content::kChromeUIScheme;
// Match the logic from URLRequestHttpJob::AddCookieHeaderAndStart.
net::CookieOptions options;
options.set_include_httponly();
options.set_same_site_cookie_context(
net::cookie_util::ComputeSameSiteContextForRequest(
request.method, request.url, request.site_for_cookies,
request.request_initiator, should_treat_as_first_party));
return options;
}
//
// LOADING COOKIES.
//
void ContinueWithLoadedCookies(const AllowCookieCallback& allow_cookie_callback,
DoneCookieCallback done_callback,
const net::CookieAccessResultList& cookies) {
CEF_REQUIRE_IOT();
net::CookieList allowed_cookies;
for (const auto& status : cookies) {
bool allow = false;
allow_cookie_callback.Run(status.cookie, &allow);
if (allow)
allowed_cookies.push_back(status.cookie);
}
std::move(done_callback).Run(cookies.size(), std::move(allowed_cookies));
}
void GetCookieListCallback(const AllowCookieCallback& allow_cookie_callback,
DoneCookieCallback done_callback,
const net::CookieAccessResultList& included_cookies,
const net::CookieAccessResultList&) {
CEF_REQUIRE_UIT();
CEF_POST_TASK(CEF_IOT,
base::BindOnce(ContinueWithLoadedCookies, allow_cookie_callback,
std::move(done_callback), included_cookies));
}
void LoadCookiesOnUIThread(content::BrowserContext* browser_context,
const GURL& url,
const net::CookieOptions& options,
const AllowCookieCallback& allow_cookie_callback,
DoneCookieCallback done_callback) {
CEF_REQUIRE_UIT();
GetCookieManager(browser_context)
->GetCookieList(
url, options,
base::BindOnce(GetCookieListCallback, allow_cookie_callback,
std::move(done_callback)));
}
//
// SAVING COOKIES.
//
struct SaveCookiesProgress {
DoneCookieCallback done_callback_;
int total_count_;
net::CookieList allowed_cookies_;
int num_cookie_lines_left_;
};
void SetCanonicalCookieCallback(SaveCookiesProgress* progress,
const net::CanonicalCookie& cookie,
net::CookieAccessResult access_result) {
CEF_REQUIRE_UIT();
progress->num_cookie_lines_left_--;
if (access_result.status.IsInclude()) {
progress->allowed_cookies_.push_back(cookie);
}
// If all the cookie lines have been handled the request can be continued.
if (progress->num_cookie_lines_left_ == 0) {
CEF_POST_TASK(CEF_IOT,
base::BindOnce(std::move(progress->done_callback_),
progress->total_count_,
std::move(progress->allowed_cookies_)));
delete progress;
}
}
void SaveCookiesOnUIThread(content::BrowserContext* browser_context,
const GURL& url,
const net::CookieOptions& options,
int total_count,
net::CookieList cookies,
DoneCookieCallback done_callback) {
CEF_REQUIRE_UIT();
DCHECK(!cookies.empty());
network::mojom::CookieManager* cookie_manager =
GetCookieManager(browser_context);
// |done_callback| needs to be executed once and only once after the list has
// been fully processed. |num_cookie_lines_left_| keeps track of how many
// async callbacks are currently pending.
auto progress = new SaveCookiesProgress;
progress->done_callback_ = std::move(done_callback);
progress->total_count_ = total_count;
// Make sure to wait for the loop to complete.
progress->num_cookie_lines_left_ = 1;
for (const auto& cookie : cookies) {
progress->num_cookie_lines_left_++;
cookie_manager->SetCanonicalCookie(
cookie, url, options,
base::BindOnce(&SetCanonicalCookieCallback, base::Unretained(progress),
cookie));
}
SetCanonicalCookieCallback(
progress, net::CanonicalCookie(),
net::CookieAccessResult(net::CookieInclusionStatus(
net::CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR)));
}
} // namespace
bool IsCookieableScheme(
const GURL& url,
const base::Optional<std::vector<std::string>>& cookieable_schemes) {
if (!url.has_scheme())
return false;
if (cookieable_schemes) {
// The client has explicitly registered the full set of schemes that should
// be supported.
const auto url_scheme = url.scheme_piece();
for (auto scheme : *cookieable_schemes) {
if (url_scheme == scheme)
return true;
}
return false;
}
// Schemes that support cookies by default.
// This should match CookieMonster::kDefaultCookieableSchemes.
return url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS();
}
void LoadCookies(content::BrowserContext* browser_context,
const network::ResourceRequest& request,
const AllowCookieCallback& allow_cookie_callback,
DoneCookieCallback done_callback) {
CEF_REQUIRE_IOT();
if ((request.load_flags & net::LOAD_DO_NOT_SEND_COOKIES) ||
request.credentials_mode == network::mojom::CredentialsMode::kOmit ||
request.url.IsAboutBlank()) {
// Continue immediately without loading cookies.
std::move(done_callback).Run(0, {});
return;
}
CEF_POST_TASK(
CEF_UIT, base::BindOnce(LoadCookiesOnUIThread, browser_context,
request.url, GetCookieOptions(request),
allow_cookie_callback, std::move(done_callback)));
}
void SaveCookies(content::BrowserContext* browser_context,
const network::ResourceRequest& request,
net::HttpResponseHeaders* headers,
const AllowCookieCallback& allow_cookie_callback,
DoneCookieCallback done_callback) {
CEF_REQUIRE_IOT();
if (request.credentials_mode == network::mojom::CredentialsMode::kOmit ||
request.url.IsAboutBlank() || !headers ||
!headers->HasHeader(net_service::kHTTPSetCookieHeaderName)) {
// Continue immediately without saving cookies.
std::move(done_callback).Run(0, {});
return;
}
// Match the logic in
// URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete.
base::Time response_date;
if (!headers->GetDateValue(&response_date))
response_date = base::Time();
const base::StringPiece name(net_service::kHTTPSetCookieHeaderName);
std::string cookie_string;
size_t iter = 0;
net::CookieList allowed_cookies;
int total_count = 0;
while (headers->EnumerateHeader(&iter, name, &cookie_string)) {
total_count++;
net::CookieInclusionStatus returned_status;
std::unique_ptr<net::CanonicalCookie> cookie = net::CanonicalCookie::Create(
request.url, cookie_string, base::Time::Now(),
base::make_optional(response_date), &returned_status);
if (!returned_status.IsInclude()) {
continue;
}
bool allow = false;
allow_cookie_callback.Run(*cookie, &allow);
if (allow)
allowed_cookies.push_back(*cookie);
}
if (!allowed_cookies.empty()) {
CEF_POST_TASK(
CEF_UIT,
base::BindOnce(SaveCookiesOnUIThread, browser_context, request.url,
GetCookieOptions(request), total_count,
std::move(allowed_cookies), std::move(done_callback)));
} else {
std::move(done_callback).Run(total_count, std::move(allowed_cookies));
}
}
} // namespace cookie_helper
} // namespace net_service