atom_api_cookies.cc 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. // Copyright (c) 2015 GitHub, Inc.
  2. // Use of this source code is governed by the MIT license that can be
  3. // found in the LICENSE file.
  4. #include "atom/browser/api/atom_api_cookies.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "atom/browser/atom_browser_context.h"
  8. #include "atom/browser/cookie_change_notifier.h"
  9. #include "atom/common/native_mate_converters/callback.h"
  10. #include "atom/common/native_mate_converters/gurl_converter.h"
  11. #include "atom/common/native_mate_converters/value_converter.h"
  12. #include "base/task/post_task.h"
  13. #include "base/time/time.h"
  14. #include "base/values.h"
  15. #include "content/public/browser/browser_context.h"
  16. #include "content/public/browser/browser_task_traits.h"
  17. #include "content/public/browser/browser_thread.h"
  18. #include "native_mate/dictionary.h"
  19. #include "native_mate/object_template_builder.h"
  20. #include "net/cookies/canonical_cookie.h"
  21. #include "net/cookies/cookie_store.h"
  22. #include "net/cookies/cookie_util.h"
  23. #include "net/url_request/url_request_context.h"
  24. #include "net/url_request/url_request_context_getter.h"
  25. using content::BrowserThread;
  26. namespace mate {
  27. template <>
  28. struct Converter<atom::api::Cookies::Error> {
  29. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
  30. atom::api::Cookies::Error val) {
  31. if (val == atom::api::Cookies::SUCCESS)
  32. return v8::Null(isolate);
  33. else
  34. return v8::Exception::Error(StringToV8(isolate, "Setting cookie failed"));
  35. }
  36. };
  37. template <>
  38. struct Converter<net::CanonicalCookie> {
  39. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
  40. const net::CanonicalCookie& val) {
  41. mate::Dictionary dict(isolate, v8::Object::New(isolate));
  42. dict.Set("name", val.Name());
  43. dict.Set("value", val.Value());
  44. dict.Set("domain", val.Domain());
  45. dict.Set("hostOnly", net::cookie_util::DomainIsHostOnly(val.Domain()));
  46. dict.Set("path", val.Path());
  47. dict.Set("secure", val.IsSecure());
  48. dict.Set("httpOnly", val.IsHttpOnly());
  49. dict.Set("session", !val.IsPersistent());
  50. if (val.IsPersistent())
  51. dict.Set("expirationDate", val.ExpiryDate().ToDoubleT());
  52. return dict.GetHandle();
  53. }
  54. };
  55. template <>
  56. struct Converter<network::mojom::CookieChangeCause> {
  57. static v8::Local<v8::Value> ToV8(
  58. v8::Isolate* isolate,
  59. const network::mojom::CookieChangeCause& val) {
  60. switch (val) {
  61. case network::mojom::CookieChangeCause::INSERTED:
  62. case network::mojom::CookieChangeCause::EXPLICIT:
  63. return mate::StringToV8(isolate, "explicit");
  64. case network::mojom::CookieChangeCause::OVERWRITE:
  65. return mate::StringToV8(isolate, "overwrite");
  66. case network::mojom::CookieChangeCause::EXPIRED:
  67. return mate::StringToV8(isolate, "expired");
  68. case network::mojom::CookieChangeCause::EVICTED:
  69. return mate::StringToV8(isolate, "evicted");
  70. case network::mojom::CookieChangeCause::EXPIRED_OVERWRITE:
  71. return mate::StringToV8(isolate, "expired-overwrite");
  72. default:
  73. return mate::StringToV8(isolate, "unknown");
  74. }
  75. }
  76. };
  77. } // namespace mate
  78. namespace atom {
  79. namespace api {
  80. namespace {
  81. // Returns whether |domain| matches |filter|.
  82. bool MatchesDomain(std::string filter, const std::string& domain) {
  83. // Add a leading '.' character to the filter domain if it doesn't exist.
  84. if (net::cookie_util::DomainIsHostOnly(filter))
  85. filter.insert(0, ".");
  86. std::string sub_domain(domain);
  87. // Strip any leading '.' character from the input cookie domain.
  88. if (!net::cookie_util::DomainIsHostOnly(sub_domain))
  89. sub_domain = sub_domain.substr(1);
  90. // Now check whether the domain argument is a subdomain of the filter domain.
  91. for (sub_domain.insert(0, "."); sub_domain.length() >= filter.length();) {
  92. if (sub_domain == filter)
  93. return true;
  94. const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot.
  95. sub_domain.erase(0, next_dot);
  96. }
  97. return false;
  98. }
  99. // Returns whether |cookie| matches |filter|.
  100. bool MatchesCookie(const base::DictionaryValue* filter,
  101. const net::CanonicalCookie& cookie) {
  102. std::string str;
  103. bool b;
  104. if (filter->GetString("name", &str) && str != cookie.Name())
  105. return false;
  106. if (filter->GetString("path", &str) && str != cookie.Path())
  107. return false;
  108. if (filter->GetString("domain", &str) && !MatchesDomain(str, cookie.Domain()))
  109. return false;
  110. if (filter->GetBoolean("secure", &b) && b != cookie.IsSecure())
  111. return false;
  112. if (filter->GetBoolean("session", &b) && b != !cookie.IsPersistent())
  113. return false;
  114. return true;
  115. }
  116. // Helper to returns the CookieStore.
  117. inline net::CookieStore* GetCookieStore(
  118. scoped_refptr<net::URLRequestContextGetter> getter) {
  119. return getter->GetURLRequestContext()->cookie_store();
  120. }
  121. // Remove cookies from |list| not matching |filter|, and pass it to |callback|.
  122. void FilterCookies(std::unique_ptr<base::DictionaryValue> filter,
  123. util::Promise promise,
  124. const net::CookieList& list,
  125. const net::CookieStatusList& excluded_list) {
  126. net::CookieList result;
  127. for (const auto& cookie : list) {
  128. if (MatchesCookie(filter.get(), cookie))
  129. result.push_back(cookie);
  130. }
  131. base::PostTaskWithTraits(
  132. FROM_HERE, {BrowserThread::UI},
  133. base::BindOnce(util::Promise::ResolvePromise<const net::CookieList&>,
  134. std::move(promise), std::move(result)));
  135. }
  136. // Receives cookies matching |filter| in IO thread.
  137. void GetCookiesOnIO(scoped_refptr<net::URLRequestContextGetter> getter,
  138. std::unique_ptr<base::DictionaryValue> filter,
  139. util::Promise promise) {
  140. std::string url;
  141. filter->GetString("url", &url);
  142. auto filtered_callback =
  143. base::BindOnce(FilterCookies, std::move(filter), std::move(promise));
  144. // Empty url will match all url cookies.
  145. if (url.empty())
  146. GetCookieStore(getter)->GetAllCookiesAsync(std::move(filtered_callback));
  147. else
  148. GetCookieStore(getter)->GetAllCookiesForURLAsync(
  149. GURL(url), std::move(filtered_callback));
  150. }
  151. // Removes cookie with |url| and |name| in IO thread.
  152. void RemoveCookieOnIO(scoped_refptr<net::URLRequestContextGetter> getter,
  153. const GURL& url,
  154. const std::string& name,
  155. util::Promise promise) {
  156. net::CookieDeletionInfo cookie_info;
  157. cookie_info.url = url;
  158. cookie_info.name = name;
  159. GetCookieStore(getter)->DeleteAllMatchingInfoAsync(
  160. std::move(cookie_info),
  161. base::BindOnce(
  162. [](util::Promise promise, uint32_t num_deleted) {
  163. util::Promise::ResolveEmptyPromise(std::move(promise));
  164. },
  165. std::move(promise)));
  166. }
  167. // Callback of SetCookie.
  168. void OnSetCookie(util::Promise promise,
  169. net::CanonicalCookie::CookieInclusionStatus status) {
  170. std::string errmsg;
  171. switch (status) {
  172. case net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_HTTP_ONLY:
  173. errmsg = "Failed to create httponly cookie";
  174. break;
  175. case net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_SECURE_ONLY:
  176. errmsg = "Cannot create a secure cookie from an insecure URL";
  177. break;
  178. case net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE:
  179. errmsg = "Failed to parse cookie";
  180. break;
  181. case net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN:
  182. errmsg = "Failed to get cookie domain";
  183. break;
  184. case net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX:
  185. errmsg = "Failed because the cookie violated prefix rules.";
  186. break;
  187. case net::CanonicalCookie::CookieInclusionStatus::
  188. EXCLUDE_NONCOOKIEABLE_SCHEME:
  189. errmsg = "Cannot set cookie for current scheme";
  190. break;
  191. case net::CanonicalCookie::CookieInclusionStatus::INCLUDE:
  192. errmsg = "";
  193. break;
  194. default:
  195. errmsg = "Setting cookie failed";
  196. break;
  197. }
  198. if (errmsg.empty()) {
  199. base::PostTaskWithTraits(
  200. FROM_HERE, {BrowserThread::UI},
  201. base::BindOnce(util::Promise::ResolveEmptyPromise, std::move(promise)));
  202. } else {
  203. base::PostTaskWithTraits(
  204. FROM_HERE, {BrowserThread::UI},
  205. base::BindOnce(util::Promise::RejectPromise, std::move(promise),
  206. std::move(errmsg)));
  207. }
  208. }
  209. // Flushes cookie store in IO thread.
  210. void FlushCookieStoreOnIOThread(
  211. scoped_refptr<net::URLRequestContextGetter> getter,
  212. util::Promise promise) {
  213. GetCookieStore(getter)->FlushStore(
  214. base::BindOnce(util::Promise::ResolveEmptyPromise, std::move(promise)));
  215. }
  216. // Sets cookie with |details| in IO thread.
  217. void SetCookieOnIO(scoped_refptr<net::URLRequestContextGetter> getter,
  218. std::unique_ptr<base::DictionaryValue> details,
  219. util::Promise promise) {
  220. std::string url_string, name, value, domain, path;
  221. bool secure = false;
  222. bool http_only = false;
  223. double creation_date;
  224. double expiration_date;
  225. double last_access_date;
  226. details->GetString("url", &url_string);
  227. details->GetString("name", &name);
  228. details->GetString("value", &value);
  229. details->GetString("domain", &domain);
  230. details->GetString("path", &path);
  231. details->GetBoolean("secure", &secure);
  232. details->GetBoolean("httpOnly", &http_only);
  233. base::Time creation_time;
  234. if (details->GetDouble("creationDate", &creation_date)) {
  235. creation_time = (creation_date == 0)
  236. ? base::Time::UnixEpoch()
  237. : base::Time::FromDoubleT(creation_date);
  238. }
  239. base::Time expiration_time;
  240. if (details->GetDouble("expirationDate", &expiration_date)) {
  241. expiration_time = (expiration_date == 0)
  242. ? base::Time::UnixEpoch()
  243. : base::Time::FromDoubleT(expiration_date);
  244. }
  245. base::Time last_access_time;
  246. if (details->GetDouble("lastAccessDate", &last_access_date)) {
  247. last_access_time = (last_access_date == 0)
  248. ? base::Time::UnixEpoch()
  249. : base::Time::FromDoubleT(last_access_date);
  250. }
  251. GURL url(url_string);
  252. std::unique_ptr<net::CanonicalCookie> canonical_cookie(
  253. net::CanonicalCookie::CreateSanitizedCookie(
  254. url, name, value, domain, path, creation_time, expiration_time,
  255. last_access_time, secure, http_only,
  256. net::CookieSameSite::NO_RESTRICTION, net::COOKIE_PRIORITY_DEFAULT));
  257. auto completion_callback = base::BindOnce(OnSetCookie, std::move(promise));
  258. if (!canonical_cookie || !canonical_cookie->IsCanonical()) {
  259. std::move(completion_callback)
  260. .Run(net::CanonicalCookie::CookieInclusionStatus::
  261. EXCLUDE_FAILURE_TO_STORE);
  262. return;
  263. }
  264. if (!url.is_valid()) {
  265. std::move(completion_callback)
  266. .Run(net::CanonicalCookie::CookieInclusionStatus::
  267. EXCLUDE_INVALID_DOMAIN);
  268. return;
  269. }
  270. if (name.empty()) {
  271. std::move(completion_callback)
  272. .Run(net::CanonicalCookie::CookieInclusionStatus::
  273. EXCLUDE_FAILURE_TO_STORE);
  274. return;
  275. }
  276. net::CookieOptions options;
  277. if (http_only) {
  278. options.set_include_httponly();
  279. }
  280. GetCookieStore(getter)->SetCanonicalCookieAsync(
  281. std::move(canonical_cookie), url.scheme(), options,
  282. std::move(completion_callback));
  283. }
  284. } // namespace
  285. Cookies::Cookies(v8::Isolate* isolate, AtomBrowserContext* browser_context)
  286. : browser_context_(browser_context) {
  287. Init(isolate);
  288. cookie_change_subscription_ =
  289. browser_context_->cookie_change_notifier()->RegisterCookieChangeCallback(
  290. base::Bind(&Cookies::OnCookieChanged, base::Unretained(this)));
  291. }
  292. Cookies::~Cookies() {}
  293. v8::Local<v8::Promise> Cookies::Get(const base::DictionaryValue& filter) {
  294. util::Promise promise(isolate());
  295. v8::Local<v8::Promise> handle = promise.GetHandle();
  296. auto copy = base::DictionaryValue::From(
  297. base::Value::ToUniquePtrValue(filter.Clone()));
  298. auto* getter = browser_context_->GetRequestContext();
  299. base::PostTaskWithTraits(
  300. FROM_HERE, {BrowserThread::IO},
  301. base::BindOnce(GetCookiesOnIO, base::RetainedRef(getter), std::move(copy),
  302. std::move(promise)));
  303. return handle;
  304. }
  305. v8::Local<v8::Promise> Cookies::Remove(const GURL& url,
  306. const std::string& name) {
  307. util::Promise promise(isolate());
  308. v8::Local<v8::Promise> handle = promise.GetHandle();
  309. auto* getter = browser_context_->GetRequestContext();
  310. base::PostTaskWithTraits(
  311. FROM_HERE, {BrowserThread::IO},
  312. base::BindOnce(RemoveCookieOnIO, base::RetainedRef(getter), url, name,
  313. std::move(promise)));
  314. return handle;
  315. }
  316. v8::Local<v8::Promise> Cookies::Set(const base::DictionaryValue& details) {
  317. util::Promise promise(isolate());
  318. v8::Local<v8::Promise> handle = promise.GetHandle();
  319. auto copy = base::DictionaryValue::From(
  320. base::Value::ToUniquePtrValue(details.Clone()));
  321. auto* getter = browser_context_->GetRequestContext();
  322. base::PostTaskWithTraits(
  323. FROM_HERE, {BrowserThread::IO},
  324. base::BindOnce(SetCookieOnIO, base::RetainedRef(getter), std::move(copy),
  325. std::move(promise)));
  326. return handle;
  327. }
  328. v8::Local<v8::Promise> Cookies::FlushStore() {
  329. util::Promise promise(isolate());
  330. v8::Local<v8::Promise> handle = promise.GetHandle();
  331. auto* getter = browser_context_->GetRequestContext();
  332. base::PostTaskWithTraits(
  333. FROM_HERE, {BrowserThread::IO},
  334. base::BindOnce(FlushCookieStoreOnIOThread, base::RetainedRef(getter),
  335. std::move(promise)));
  336. return handle;
  337. }
  338. void Cookies::OnCookieChanged(const CookieDetails* details) {
  339. Emit("changed", *(details->cookie), details->cause, details->removed);
  340. }
  341. // static
  342. mate::Handle<Cookies> Cookies::Create(v8::Isolate* isolate,
  343. AtomBrowserContext* browser_context) {
  344. return mate::CreateHandle(isolate, new Cookies(isolate, browser_context));
  345. }
  346. // static
  347. void Cookies::BuildPrototype(v8::Isolate* isolate,
  348. v8::Local<v8::FunctionTemplate> prototype) {
  349. prototype->SetClassName(mate::StringToV8(isolate, "Cookies"));
  350. mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
  351. .SetMethod("get", &Cookies::Get)
  352. .SetMethod("remove", &Cookies::Remove)
  353. .SetMethod("set", &Cookies::Set)
  354. .SetMethod("flushStore", &Cookies::FlushStore);
  355. }
  356. } // namespace api
  357. } // namespace atom