atom_api_cookies.cc 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  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 "shell/browser/api/atom_api_cookies.h"
  5. #include <memory>
  6. #include <utility>
  7. #include "base/time/time.h"
  8. #include "base/values.h"
  9. #include "content/public/browser/browser_context.h"
  10. #include "content/public/browser/browser_task_traits.h"
  11. #include "content/public/browser/browser_thread.h"
  12. #include "content/public/browser/storage_partition.h"
  13. #include "gin/dictionary.h"
  14. #include "gin/object_template_builder.h"
  15. #include "native_mate/dictionary.h"
  16. #include "net/cookies/canonical_cookie.h"
  17. #include "net/cookies/cookie_store.h"
  18. #include "net/cookies/cookie_util.h"
  19. #include "shell/browser/atom_browser_context.h"
  20. #include "shell/browser/cookie_change_notifier.h"
  21. #include "shell/common/native_mate_converters/callback.h"
  22. #include "shell/common/native_mate_converters/gurl_converter.h"
  23. #include "shell/common/native_mate_converters/value_converter.h"
  24. using content::BrowserThread;
  25. namespace gin {
  26. template <>
  27. struct Converter<net::CanonicalCookie> {
  28. static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
  29. const net::CanonicalCookie& val) {
  30. gin::Dictionary dict(isolate, v8::Object::New(isolate));
  31. dict.Set("name", val.Name());
  32. dict.Set("value", val.Value());
  33. dict.Set("domain", val.Domain());
  34. dict.Set("hostOnly", net::cookie_util::DomainIsHostOnly(val.Domain()));
  35. dict.Set("path", val.Path());
  36. dict.Set("secure", val.IsSecure());
  37. dict.Set("httpOnly", val.IsHttpOnly());
  38. dict.Set("session", !val.IsPersistent());
  39. if (val.IsPersistent())
  40. dict.Set("expirationDate", val.ExpiryDate().ToDoubleT());
  41. return ConvertToV8(isolate, dict).As<v8::Object>();
  42. }
  43. };
  44. template <>
  45. struct Converter<network::mojom::CookieChangeCause> {
  46. static v8::Local<v8::Value> ToV8(
  47. v8::Isolate* isolate,
  48. const network::mojom::CookieChangeCause& val) {
  49. switch (val) {
  50. case network::mojom::CookieChangeCause::INSERTED:
  51. case network::mojom::CookieChangeCause::EXPLICIT:
  52. return gin::StringToV8(isolate, "explicit");
  53. case network::mojom::CookieChangeCause::OVERWRITE:
  54. return gin::StringToV8(isolate, "overwrite");
  55. case network::mojom::CookieChangeCause::EXPIRED:
  56. return gin::StringToV8(isolate, "expired");
  57. case network::mojom::CookieChangeCause::EVICTED:
  58. return gin::StringToV8(isolate, "evicted");
  59. case network::mojom::CookieChangeCause::EXPIRED_OVERWRITE:
  60. return gin::StringToV8(isolate, "expired-overwrite");
  61. default:
  62. return gin::StringToV8(isolate, "unknown");
  63. }
  64. }
  65. };
  66. } // namespace gin
  67. namespace electron {
  68. namespace api {
  69. namespace {
  70. // Returns whether |domain| matches |filter|.
  71. bool MatchesDomain(std::string filter, const std::string& domain) {
  72. // Add a leading '.' character to the filter domain if it doesn't exist.
  73. if (net::cookie_util::DomainIsHostOnly(filter))
  74. filter.insert(0, ".");
  75. std::string sub_domain(domain);
  76. // Strip any leading '.' character from the input cookie domain.
  77. if (!net::cookie_util::DomainIsHostOnly(sub_domain))
  78. sub_domain = sub_domain.substr(1);
  79. // Now check whether the domain argument is a subdomain of the filter domain.
  80. for (sub_domain.insert(0, "."); sub_domain.length() >= filter.length();) {
  81. if (sub_domain == filter)
  82. return true;
  83. const size_t next_dot = sub_domain.find('.', 1); // Skip over leading dot.
  84. sub_domain.erase(0, next_dot);
  85. }
  86. return false;
  87. }
  88. // Returns whether |cookie| matches |filter|.
  89. bool MatchesCookie(const base::Value& filter,
  90. const net::CanonicalCookie& cookie) {
  91. const std::string* str;
  92. if ((str = filter.FindStringKey("name")) && *str != cookie.Name())
  93. return false;
  94. if ((str = filter.FindStringKey("path")) && *str != cookie.Path())
  95. return false;
  96. if ((str = filter.FindStringKey("domain")) &&
  97. !MatchesDomain(*str, cookie.Domain()))
  98. return false;
  99. base::Optional<bool> secure_filter = filter.FindBoolKey("secure");
  100. if (secure_filter && *secure_filter == cookie.IsSecure())
  101. return false;
  102. base::Optional<bool> session_filter = filter.FindBoolKey("session");
  103. if (session_filter && *session_filter != !cookie.IsPersistent())
  104. return false;
  105. return true;
  106. }
  107. // Remove cookies from |list| not matching |filter|, and pass it to |callback|.
  108. void FilterCookies(const base::Value& filter,
  109. util::Promise promise,
  110. const net::CookieList& cookies) {
  111. net::CookieList result;
  112. for (const auto& cookie : cookies) {
  113. if (MatchesCookie(filter, cookie))
  114. result.push_back(cookie);
  115. }
  116. promise.Resolve(gin::ConvertToV8(promise.isolate(), result));
  117. }
  118. void FilterCookieWithStatuses(const base::Value& filter,
  119. util::Promise promise,
  120. const net::CookieStatusList& list,
  121. const net::CookieStatusList& excluded_list) {
  122. FilterCookies(filter, std::move(promise),
  123. net::cookie_util::StripStatuses(list));
  124. }
  125. // Parse dictionary property to CanonicalCookie time correctly.
  126. base::Time ParseTimeProperty(const base::Optional<double>& value) {
  127. if (!value) // empty time means ignoring the parameter
  128. return base::Time();
  129. if (*value == 0) // FromDoubleT would convert 0 to empty Time
  130. return base::Time::UnixEpoch();
  131. return base::Time::FromDoubleT(*value);
  132. }
  133. std::string InclusionStatusToString(
  134. net::CanonicalCookie::CookieInclusionStatus status) {
  135. if (status.HasExclusionReason(
  136. net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_HTTP_ONLY))
  137. return "Failed to create httponly cookie";
  138. if (status.HasExclusionReason(
  139. net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_SECURE_ONLY))
  140. return "Cannot create a secure cookie from an insecure URL";
  141. if (status.HasExclusionReason(net::CanonicalCookie::CookieInclusionStatus::
  142. EXCLUDE_FAILURE_TO_STORE))
  143. return "Failed to parse cookie";
  144. if (status.HasExclusionReason(
  145. net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN))
  146. return "Failed to get cookie domain";
  147. if (status.HasExclusionReason(
  148. net::CanonicalCookie::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX))
  149. return "Failed because the cookie violated prefix rules.";
  150. if (status.HasExclusionReason(net::CanonicalCookie::CookieInclusionStatus::
  151. EXCLUDE_NONCOOKIEABLE_SCHEME))
  152. return "Cannot set cookie for current scheme";
  153. return "Setting cookie failed";
  154. }
  155. } // namespace
  156. Cookies::Cookies(v8::Isolate* isolate, AtomBrowserContext* browser_context)
  157. : browser_context_(browser_context) {
  158. Init(isolate);
  159. cookie_change_subscription_ =
  160. browser_context_->cookie_change_notifier()->RegisterCookieChangeCallback(
  161. base::BindRepeating(&Cookies::OnCookieChanged,
  162. base::Unretained(this)));
  163. }
  164. Cookies::~Cookies() {}
  165. v8::Local<v8::Promise> Cookies::Get(const mate::Dictionary& filter) {
  166. util::Promise promise(isolate());
  167. v8::Local<v8::Promise> handle = promise.GetHandle();
  168. auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition(
  169. browser_context_.get());
  170. auto* manager = storage_partition->GetCookieManagerForBrowserProcess();
  171. base::DictionaryValue dict;
  172. mate::ConvertFromV8(isolate(), filter.GetHandle(), &dict);
  173. std::string url;
  174. filter.Get("url", &url);
  175. if (url.empty()) {
  176. manager->GetAllCookies(
  177. base::BindOnce(&FilterCookies, std::move(dict), std::move(promise)));
  178. } else {
  179. net::CookieOptions options;
  180. options.set_include_httponly();
  181. options.set_same_site_cookie_context(
  182. net::CookieOptions::SameSiteCookieContext::SAME_SITE_STRICT);
  183. options.set_do_not_update_access_time();
  184. manager->GetCookieList(GURL(url), options,
  185. base::BindOnce(&FilterCookieWithStatuses,
  186. std::move(dict), std::move(promise)));
  187. }
  188. return handle;
  189. }
  190. v8::Local<v8::Promise> Cookies::Remove(const GURL& url,
  191. const std::string& name) {
  192. util::Promise promise(isolate());
  193. v8::Local<v8::Promise> handle = promise.GetHandle();
  194. auto cookie_deletion_filter = network::mojom::CookieDeletionFilter::New();
  195. cookie_deletion_filter->url = url;
  196. cookie_deletion_filter->cookie_name = name;
  197. auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition(
  198. browser_context_.get());
  199. auto* manager = storage_partition->GetCookieManagerForBrowserProcess();
  200. manager->DeleteCookies(
  201. std::move(cookie_deletion_filter),
  202. base::BindOnce(
  203. [](util::Promise promise, uint32_t num_deleted) {
  204. util::Promise::ResolveEmptyPromise(std::move(promise));
  205. },
  206. std::move(promise)));
  207. return handle;
  208. }
  209. v8::Local<v8::Promise> Cookies::Set(const base::DictionaryValue& details) {
  210. util::Promise promise(isolate());
  211. v8::Local<v8::Promise> handle = promise.GetHandle();
  212. const std::string* url_string = details.FindStringKey("url");
  213. const std::string* name = details.FindStringKey("name");
  214. const std::string* value = details.FindStringKey("value");
  215. const std::string* domain = details.FindStringKey("domain");
  216. const std::string* path = details.FindStringKey("path");
  217. bool secure = details.FindBoolKey("secure").value_or(false);
  218. bool http_only = details.FindBoolKey("httpOnly").value_or(false);
  219. GURL url(url_string ? *url_string : "");
  220. if (!url.is_valid()) {
  221. promise.RejectWithErrorMessage(
  222. InclusionStatusToString(net::CanonicalCookie::CookieInclusionStatus(
  223. net::CanonicalCookie::CookieInclusionStatus::
  224. EXCLUDE_INVALID_DOMAIN)));
  225. return handle;
  226. }
  227. auto canonical_cookie = net::CanonicalCookie::CreateSanitizedCookie(
  228. url, name ? *name : "", value ? *value : "", domain ? *domain : "",
  229. path ? *path : "",
  230. ParseTimeProperty(details.FindDoubleKey("creationDate")),
  231. ParseTimeProperty(details.FindDoubleKey("expirationDate")),
  232. ParseTimeProperty(details.FindDoubleKey("lastAccessDate")), secure,
  233. http_only, net::CookieSameSite::NO_RESTRICTION,
  234. net::COOKIE_PRIORITY_DEFAULT);
  235. if (!canonical_cookie || !canonical_cookie->IsCanonical()) {
  236. promise.RejectWithErrorMessage(
  237. InclusionStatusToString(net::CanonicalCookie::CookieInclusionStatus(
  238. net::CanonicalCookie::CookieInclusionStatus::
  239. EXCLUDE_FAILURE_TO_STORE)));
  240. return handle;
  241. }
  242. net::CookieOptions options;
  243. if (http_only) {
  244. options.set_include_httponly();
  245. }
  246. auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition(
  247. browser_context_.get());
  248. auto* manager = storage_partition->GetCookieManagerForBrowserProcess();
  249. manager->SetCanonicalCookie(
  250. *canonical_cookie, url.scheme(), options,
  251. base::BindOnce(
  252. [](util::Promise promise,
  253. net::CanonicalCookie::CookieInclusionStatus status) {
  254. if (status.IsInclude()) {
  255. promise.Resolve();
  256. } else {
  257. promise.RejectWithErrorMessage(InclusionStatusToString(status));
  258. }
  259. },
  260. std::move(promise)));
  261. return handle;
  262. }
  263. v8::Local<v8::Promise> Cookies::FlushStore() {
  264. util::Promise promise(isolate());
  265. v8::Local<v8::Promise> handle = promise.GetHandle();
  266. auto* storage_partition = content::BrowserContext::GetDefaultStoragePartition(
  267. browser_context_.get());
  268. auto* manager = storage_partition->GetCookieManagerForBrowserProcess();
  269. manager->FlushCookieStore(
  270. base::BindOnce(util::Promise::ResolveEmptyPromise, std::move(promise)));
  271. return handle;
  272. }
  273. void Cookies::OnCookieChanged(const CookieDetails* details) {
  274. Emit("changed", gin::ConvertToV8(isolate(), *(details->cookie)),
  275. gin::ConvertToV8(isolate(), details->cause),
  276. gin::ConvertToV8(isolate(), details->removed));
  277. }
  278. // static
  279. gin::Handle<Cookies> Cookies::Create(v8::Isolate* isolate,
  280. AtomBrowserContext* browser_context) {
  281. return gin::CreateHandle(isolate, new Cookies(isolate, browser_context));
  282. }
  283. // static
  284. void Cookies::BuildPrototype(v8::Isolate* isolate,
  285. v8::Local<v8::FunctionTemplate> prototype) {
  286. prototype->SetClassName(gin::StringToV8(isolate, "Cookies"));
  287. mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
  288. .SetMethod("get", &Cookies::Get)
  289. .SetMethod("remove", &Cookies::Remove)
  290. .SetMethod("set", &Cookies::Set)
  291. .SetMethod("flushStore", &Cookies::FlushStore);
  292. }
  293. } // namespace api
  294. } // namespace electron