1 // Copyright (c) 2019 The Chromium Embedded Framework Authors. All rights
2 // reserved. Use of this source code is governed by a BSD-style license that can
3 // be found in the LICENSE file.
4
5 #include "libcef/browser/net_service/cookie_helper.h"
6
7 #include "libcef/browser/thread_util.h"
8 #include "libcef/common/net_service/net_service_util.h"
9
10 #include "base/bind.h"
11 #include "content/public/browser/browser_context.h"
12 #include "content/public/browser/storage_partition.h"
13 #include "content/public/common/url_constants.h"
14 #include "net/base/load_flags.h"
15 #include "net/cookies/cookie_options.h"
16 #include "net/cookies/cookie_util.h"
17 #include "services/network/cookie_manager.h"
18 #include "services/network/public/cpp/resource_request.h"
19
20 namespace net_service {
21 namespace cookie_helper {
22
23 namespace {
24
25 // Do not keep a reference to the object returned by this method.
GetBrowserContext(const CefBrowserContext::Getter & getter)26 CefBrowserContext* GetBrowserContext(const CefBrowserContext::Getter& getter) {
27 CEF_REQUIRE_UIT();
28 DCHECK(!getter.is_null());
29
30 // Will return nullptr if the BrowserContext has been shut down.
31 return getter.Run();
32 }
33
34 // Do not keep a reference to the object returned by this method.
GetCookieManager(content::BrowserContext * browser_context)35 network::mojom::CookieManager* GetCookieManager(
36 content::BrowserContext* browser_context) {
37 CEF_REQUIRE_UIT();
38 return browser_context->GetDefaultStoragePartition()
39 ->GetCookieManagerForBrowserProcess();
40 }
41
GetCookieOptions(const network::ResourceRequest & request)42 net::CookieOptions GetCookieOptions(const network::ResourceRequest& request) {
43 // Match the logic from InterceptionJob::FetchCookies and
44 // ChromeContentBrowserClient::ShouldIgnoreSameSiteCookieRestrictionsWhenTopLevel.
45 bool should_treat_as_first_party =
46 request.url.SchemeIsCryptographic() &&
47 request.site_for_cookies.scheme() == content::kChromeUIScheme;
48 bool is_main_frame_navigation =
49 request.trusted_params &&
50 request.trusted_params->isolation_info.request_type() ==
51 net::IsolationInfo::RequestType::kMainFrame;
52
53 // Match the logic from URLRequestHttpJob::AddCookieHeaderAndStart.
54 net::CookieOptions options;
55 options.set_include_httponly();
56 options.set_same_site_cookie_context(
57 net::cookie_util::ComputeSameSiteContextForRequest(
58 request.method, {request.url}, request.site_for_cookies,
59 request.request_initiator, is_main_frame_navigation,
60 should_treat_as_first_party));
61
62 return options;
63 }
64
65 //
66 // LOADING COOKIES.
67 //
68
ContinueWithLoadedCookies(const AllowCookieCallback & allow_cookie_callback,DoneCookieCallback done_callback,const net::CookieAccessResultList & cookies)69 void ContinueWithLoadedCookies(const AllowCookieCallback& allow_cookie_callback,
70 DoneCookieCallback done_callback,
71 const net::CookieAccessResultList& cookies) {
72 CEF_REQUIRE_IOT();
73 net::CookieList allowed_cookies;
74 for (const auto& status : cookies) {
75 bool allow = false;
76 allow_cookie_callback.Run(status.cookie, &allow);
77 if (allow)
78 allowed_cookies.push_back(status.cookie);
79 }
80 std::move(done_callback).Run(cookies.size(), std::move(allowed_cookies));
81 }
82
GetCookieListCallback(const AllowCookieCallback & allow_cookie_callback,DoneCookieCallback done_callback,const net::CookieAccessResultList & included_cookies,const net::CookieAccessResultList &)83 void GetCookieListCallback(const AllowCookieCallback& allow_cookie_callback,
84 DoneCookieCallback done_callback,
85 const net::CookieAccessResultList& included_cookies,
86 const net::CookieAccessResultList&) {
87 CEF_REQUIRE_UIT();
88 CEF_POST_TASK(CEF_IOT,
89 base::BindOnce(ContinueWithLoadedCookies, allow_cookie_callback,
90 std::move(done_callback), included_cookies));
91 }
92
LoadCookiesOnUIThread(const CefBrowserContext::Getter & browser_context_getter,const GURL & url,const net::CookieOptions & options,net::CookiePartitionKeyCollection cookie_partition_key_collection,const AllowCookieCallback & allow_cookie_callback,DoneCookieCallback done_callback)93 void LoadCookiesOnUIThread(
94 const CefBrowserContext::Getter& browser_context_getter,
95 const GURL& url,
96 const net::CookieOptions& options,
97 net::CookiePartitionKeyCollection cookie_partition_key_collection,
98 const AllowCookieCallback& allow_cookie_callback,
99 DoneCookieCallback done_callback) {
100 auto cef_browser_context = GetBrowserContext(browser_context_getter);
101 auto browser_context =
102 cef_browser_context ? cef_browser_context->AsBrowserContext() : nullptr;
103 if (!browser_context) {
104 GetCookieListCallback(allow_cookie_callback, std::move(done_callback),
105 net::CookieAccessResultList(),
106 net::CookieAccessResultList());
107 return;
108 }
109
110 GetCookieManager(browser_context)
111 ->GetCookieList(
112 url, options, cookie_partition_key_collection,
113 base::BindOnce(GetCookieListCallback, allow_cookie_callback,
114 std::move(done_callback)));
115 }
116
117 //
118 // SAVING COOKIES.
119 //
120
121 struct SaveCookiesProgress {
122 DoneCookieCallback done_callback_;
123 int total_count_;
124 net::CookieList allowed_cookies_;
125 int num_cookie_lines_left_;
126 };
127
SetCanonicalCookieCallback(SaveCookiesProgress * progress,const net::CanonicalCookie & cookie,net::CookieAccessResult access_result)128 void SetCanonicalCookieCallback(SaveCookiesProgress* progress,
129 const net::CanonicalCookie& cookie,
130 net::CookieAccessResult access_result) {
131 CEF_REQUIRE_UIT();
132 progress->num_cookie_lines_left_--;
133 if (access_result.status.IsInclude()) {
134 progress->allowed_cookies_.push_back(cookie);
135 }
136
137 // If all the cookie lines have been handled the request can be continued.
138 if (progress->num_cookie_lines_left_ == 0) {
139 CEF_POST_TASK(CEF_IOT,
140 base::BindOnce(std::move(progress->done_callback_),
141 progress->total_count_,
142 std::move(progress->allowed_cookies_)));
143 delete progress;
144 }
145 }
146
SaveCookiesOnUIThread(const CefBrowserContext::Getter & browser_context_getter,const GURL & url,const net::CookieOptions & options,int total_count,net::CookieList cookies,DoneCookieCallback done_callback)147 void SaveCookiesOnUIThread(
148 const CefBrowserContext::Getter& browser_context_getter,
149 const GURL& url,
150 const net::CookieOptions& options,
151 int total_count,
152 net::CookieList cookies,
153 DoneCookieCallback done_callback) {
154 DCHECK(!cookies.empty());
155
156 auto cef_browser_context = GetBrowserContext(browser_context_getter);
157 auto browser_context =
158 cef_browser_context ? cef_browser_context->AsBrowserContext() : nullptr;
159 if (!browser_context) {
160 std::move(done_callback).Run(0, net::CookieList());
161 return;
162 }
163
164 network::mojom::CookieManager* cookie_manager =
165 GetCookieManager(browser_context);
166
167 // |done_callback| needs to be executed once and only once after the list has
168 // been fully processed. |num_cookie_lines_left_| keeps track of how many
169 // async callbacks are currently pending.
170 auto progress = new SaveCookiesProgress;
171 progress->done_callback_ = std::move(done_callback);
172 progress->total_count_ = total_count;
173
174 // Make sure to wait for the loop to complete.
175 progress->num_cookie_lines_left_ = 1;
176
177 for (const auto& cookie : cookies) {
178 progress->num_cookie_lines_left_++;
179 cookie_manager->SetCanonicalCookie(
180 cookie, url, options,
181 base::BindOnce(&SetCanonicalCookieCallback, base::Unretained(progress),
182 cookie));
183 }
184
185 SetCanonicalCookieCallback(
186 progress, net::CanonicalCookie(),
187 net::CookieAccessResult(net::CookieInclusionStatus(
188 net::CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR)));
189 }
190
191 } // namespace
192
IsCookieableScheme(const GURL & url,const absl::optional<std::vector<std::string>> & cookieable_schemes)193 bool IsCookieableScheme(
194 const GURL& url,
195 const absl::optional<std::vector<std::string>>& cookieable_schemes) {
196 if (!url.has_scheme())
197 return false;
198
199 if (cookieable_schemes) {
200 // The client has explicitly registered the full set of schemes that should
201 // be supported.
202 const auto url_scheme = url.scheme_piece();
203 for (auto scheme : *cookieable_schemes) {
204 if (url_scheme == scheme)
205 return true;
206 }
207 return false;
208 }
209
210 // Schemes that support cookies by default.
211 // This should match CookieMonster::kDefaultCookieableSchemes.
212 return url.SchemeIsHTTPOrHTTPS() || url.SchemeIsWSOrWSS();
213 }
214
LoadCookies(const CefBrowserContext::Getter & browser_context_getter,const network::ResourceRequest & request,const AllowCookieCallback & allow_cookie_callback,DoneCookieCallback done_callback)215 void LoadCookies(const CefBrowserContext::Getter& browser_context_getter,
216 const network::ResourceRequest& request,
217 const AllowCookieCallback& allow_cookie_callback,
218 DoneCookieCallback done_callback) {
219 CEF_REQUIRE_IOT();
220
221 if ((request.load_flags & net::LOAD_DO_NOT_SEND_COOKIES) ||
222 request.credentials_mode == network::mojom::CredentialsMode::kOmit ||
223 request.url.IsAboutBlank()) {
224 // Continue immediately without loading cookies.
225 std::move(done_callback).Run(0, {});
226 return;
227 }
228
229 CEF_POST_TASK(
230 CEF_UIT, base::BindOnce(LoadCookiesOnUIThread, browser_context_getter,
231 request.url, GetCookieOptions(request),
232 net::CookiePartitionKeyCollection(),
233 allow_cookie_callback, std::move(done_callback)));
234 }
235
SaveCookies(const CefBrowserContext::Getter & browser_context_getter,const network::ResourceRequest & request,net::HttpResponseHeaders * headers,const AllowCookieCallback & allow_cookie_callback,DoneCookieCallback done_callback)236 void SaveCookies(const CefBrowserContext::Getter& browser_context_getter,
237 const network::ResourceRequest& request,
238 net::HttpResponseHeaders* headers,
239 const AllowCookieCallback& allow_cookie_callback,
240 DoneCookieCallback done_callback) {
241 CEF_REQUIRE_IOT();
242
243 if (request.credentials_mode == network::mojom::CredentialsMode::kOmit ||
244 request.url.IsAboutBlank() || !headers ||
245 !headers->HasHeader(net_service::kHTTPSetCookieHeaderName)) {
246 // Continue immediately without saving cookies.
247 std::move(done_callback).Run(0, {});
248 return;
249 }
250
251 // Match the logic in
252 // URLRequestHttpJob::SaveCookiesAndNotifyHeadersComplete.
253 base::Time response_date;
254 if (!headers->GetDateValue(&response_date))
255 response_date = base::Time();
256
257 const base::StringPiece name(net_service::kHTTPSetCookieHeaderName);
258 std::string cookie_string;
259 size_t iter = 0;
260 net::CookieList allowed_cookies;
261 int total_count = 0;
262
263 while (headers->EnumerateHeader(&iter, name, &cookie_string)) {
264 total_count++;
265
266 net::CookieInclusionStatus returned_status;
267 std::unique_ptr<net::CanonicalCookie> cookie = net::CanonicalCookie::Create(
268 request.url, cookie_string, base::Time::Now(),
269 absl::make_optional(response_date), net::CookiePartitionKey::Todo(),
270 &returned_status);
271 if (!returned_status.IsInclude()) {
272 continue;
273 }
274
275 bool allow = false;
276 allow_cookie_callback.Run(*cookie, &allow);
277 if (allow)
278 allowed_cookies.push_back(*cookie);
279 }
280
281 if (!allowed_cookies.empty()) {
282 CEF_POST_TASK(
283 CEF_UIT,
284 base::BindOnce(SaveCookiesOnUIThread, browser_context_getter,
285 request.url, GetCookieOptions(request), total_count,
286 std::move(allowed_cookies), std::move(done_callback)));
287
288 } else {
289 std::move(done_callback).Run(total_count, std::move(allowed_cookies));
290 }
291 }
292
293 } // namespace cookie_helper
294 } // namespace net_service