/src/mozilla-central/netwerk/cookie/nsCookieService.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
2 | | /* vim: set sw=2 ts=8 et tw=80 : */ |
3 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
4 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
5 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
6 | | |
7 | | #include "mozilla/AntiTrackingCommon.h" |
8 | | #include "mozilla/Attributes.h" |
9 | | #include "mozilla/ClearOnShutdown.h" |
10 | | #include "mozilla/DebugOnly.h" |
11 | | #include "mozilla/Likely.h" |
12 | | #include "mozilla/Printf.h" |
13 | | #include "mozilla/Unused.h" |
14 | | |
15 | | #include "mozilla/net/CookieServiceChild.h" |
16 | | #include "mozilla/net/NeckoCommon.h" |
17 | | |
18 | | #include "nsCookieService.h" |
19 | | #include "nsContentUtils.h" |
20 | | #include "nsIServiceManager.h" |
21 | | #include "nsIScriptSecurityManager.h" |
22 | | #include "nsIWebProgressListener.h" |
23 | | |
24 | | #include "nsIIOService.h" |
25 | | #include "nsIPermissionManager.h" |
26 | | #include "nsIProtocolHandler.h" |
27 | | #include "nsIPrefBranch.h" |
28 | | #include "nsIPrefService.h" |
29 | | #include "nsIScriptError.h" |
30 | | #include "nsCookiePermission.h" |
31 | | #include "nsIURI.h" |
32 | | #include "nsIURL.h" |
33 | | #include "nsIChannel.h" |
34 | | #include "nsIFile.h" |
35 | | #include "nsIObserverService.h" |
36 | | #include "nsILineInputStream.h" |
37 | | #include "nsIEffectiveTLDService.h" |
38 | | #include "nsIIDNService.h" |
39 | | #include "nsIThread.h" |
40 | | #include "mozIThirdPartyUtil.h" |
41 | | |
42 | | #include "nsTArray.h" |
43 | | #include "nsCOMArray.h" |
44 | | #include "nsIMutableArray.h" |
45 | | #include "nsArrayEnumerator.h" |
46 | | #include "nsEnumeratorUtils.h" |
47 | | #include "nsAutoPtr.h" |
48 | | #include "nsReadableUtils.h" |
49 | | #include "nsCRT.h" |
50 | | #include "prprf.h" |
51 | | #include "nsNetUtil.h" |
52 | | #include "nsNetCID.h" |
53 | | #include "nsISimpleEnumerator.h" |
54 | | #include "nsIInputStream.h" |
55 | | #include "nsAppDirectoryServiceDefs.h" |
56 | | #include "nsNetCID.h" |
57 | | #include "mozilla/storage.h" |
58 | | #include "mozilla/AutoRestore.h" |
59 | | #include "mozilla/FileUtils.h" |
60 | | #include "mozilla/ScopeExit.h" |
61 | | #include "mozilla/StaticPrefs.h" |
62 | | #include "mozilla/Telemetry.h" |
63 | | #include "nsIConsoleService.h" |
64 | | #include "nsTPriorityQueue.h" |
65 | | #include "nsVariant.h" |
66 | | |
67 | | using namespace mozilla; |
68 | | using namespace mozilla::net; |
69 | | |
70 | | // Create key from baseDomain that will access the default cookie namespace. |
71 | | // TODO: When we figure out what the API will look like for nsICookieManager{2} |
72 | | // on content processes (see bug 777620), change to use the appropriate app |
73 | | // namespace. For now those IDLs aren't supported on child processes. |
74 | | #define DEFAULT_APP_KEY(baseDomain) \ |
75 | 0 | nsCookieKey(baseDomain, OriginAttributes()) |
76 | | |
77 | | /****************************************************************************** |
78 | | * nsCookieService impl: |
79 | | * useful types & constants |
80 | | ******************************************************************************/ |
81 | | |
82 | | static StaticRefPtr<nsCookieService> gCookieService; |
83 | | bool nsCookieService::sSameSiteEnabled = false; |
84 | | |
85 | | // XXX_hack. See bug 178993. |
86 | | // This is a hack to hide HttpOnly cookies from older browsers |
87 | 0 | #define HTTP_ONLY_PREFIX "#HttpOnly_" |
88 | | |
89 | | #define COOKIES_FILE "cookies.sqlite" |
90 | 0 | #define COOKIES_SCHEMA_VERSION 9 |
91 | | |
92 | | // parameter indexes; see |Read| |
93 | 0 | #define IDX_NAME 0 |
94 | 0 | #define IDX_VALUE 1 |
95 | 0 | #define IDX_HOST 2 |
96 | 0 | #define IDX_PATH 3 |
97 | 0 | #define IDX_EXPIRY 4 |
98 | 0 | #define IDX_LAST_ACCESSED 5 |
99 | 0 | #define IDX_CREATION_TIME 6 |
100 | 0 | #define IDX_SECURE 7 |
101 | 0 | #define IDX_HTTPONLY 8 |
102 | 0 | #define IDX_BASE_DOMAIN 9 |
103 | 0 | #define IDX_ORIGIN_ATTRIBUTES 10 |
104 | 0 | #define IDX_SAME_SITE 11 |
105 | | |
106 | 3 | #define TOPIC_CLEAR_ORIGIN_DATA "clear-origin-attributes-data" |
107 | | |
108 | | static const int64_t kCookiePurgeAge = |
109 | | int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds |
110 | | |
111 | | #define OLD_COOKIE_FILE_NAME "cookies.txt" |
112 | | |
113 | | #undef LIMIT |
114 | 0 | #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default)) |
115 | | |
116 | | #undef ADD_TEN_PERCENT |
117 | 0 | #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10) |
118 | | |
119 | | // default limits for the cookie list. these can be tuned by the |
120 | | // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively. |
121 | | static const uint32_t kMaxNumberOfCookies = 3000; |
122 | | static const uint32_t kMaxCookiesPerHost = 180; |
123 | | static const uint32_t kCookieQuotaPerHost = 150; |
124 | | static const uint32_t kMaxBytesPerCookie = 4096; |
125 | | static const uint32_t kMaxBytesPerPath = 1024; |
126 | | |
127 | | // pref string constants |
128 | | static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior"; |
129 | | static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber"; |
130 | | static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost"; |
131 | | static const char kPrefCookieQuotaPerHost[] = "network.cookie.quotaPerHost"; |
132 | | static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge"; |
133 | | static const char kPrefThirdPartySession[] = "network.cookie.thirdparty.sessionOnly"; |
134 | | static const char kPrefThirdPartyNonsecureSession[] = "network.cookie.thirdparty.nonsecureSessionOnly"; |
135 | | static const char kCookieLeaveSecurityAlone[] = "network.cookie.leave-secure-alone"; |
136 | | |
137 | | // For telemetry COOKIE_LEAVE_SECURE_ALONE |
138 | 0 | #define BLOCKED_SECURE_SET_FROM_HTTP 0 |
139 | 0 | #define BLOCKED_DOWNGRADE_SECURE_INEXACT 1 |
140 | 0 | #define DOWNGRADE_SECURE_FROM_SECURE_INEXACT 2 |
141 | 0 | #define EVICTED_NEWER_INSECURE 3 |
142 | 0 | #define EVICTED_OLDEST_COOKIE 4 |
143 | 0 | #define EVICTED_PREFERRED_COOKIE 5 |
144 | 0 | #define EVICTING_SECURE_BLOCKED 6 |
145 | 0 | #define BLOCKED_DOWNGRADE_SECURE_EXACT 7 |
146 | 0 | #define DOWNGRADE_SECURE_FROM_SECURE_EXACT 8 |
147 | | |
148 | | static void |
149 | | bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray, |
150 | | const nsCookieKey &aKey, |
151 | | const nsCookie *aCookie); |
152 | | |
153 | | // stores the nsCookieEntry entryclass and an index into the cookie array |
154 | | // within that entryclass, for purposes of storing an iteration state that |
155 | | // points to a certain cookie. |
156 | | struct nsListIter |
157 | | { |
158 | | // default (non-initializing) constructor. |
159 | | nsListIter() = default; |
160 | | |
161 | | // explicit constructor to a given iterator state with entryclass 'aEntry' |
162 | | // and index 'aIndex'. |
163 | | explicit |
164 | | nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex) |
165 | | : entry(aEntry) |
166 | | , index(aIndex) |
167 | 0 | { |
168 | 0 | } |
169 | | |
170 | | // get the nsCookie * the iterator currently points to. |
171 | | nsCookie * Cookie() const |
172 | 0 | { |
173 | 0 | return entry->GetCookies()[index]; |
174 | 0 | } |
175 | | |
176 | | nsCookieEntry *entry; |
177 | | nsCookieEntry::IndexType index; |
178 | | }; |
179 | | |
180 | | /****************************************************************************** |
181 | | * Cookie logging handlers |
182 | | * used for logging in nsCookieService |
183 | | ******************************************************************************/ |
184 | | |
185 | | // logging handlers |
186 | | #ifdef MOZ_LOGGING |
187 | | // in order to do logging, the following environment variables need to be set: |
188 | | // |
189 | | // set MOZ_LOG=cookie:3 -- shows rejected cookies |
190 | | // set MOZ_LOG=cookie:4 -- shows accepted and rejected cookies |
191 | | // set MOZ_LOG_FILE=cookie.log |
192 | | // |
193 | | #include "mozilla/Logging.h" |
194 | | #endif |
195 | | |
196 | | // define logging macros for convenience |
197 | | #define SET_COOKIE true |
198 | | #define GET_COOKIE false |
199 | | |
200 | | static LazyLogModule gCookieLog("cookie"); |
201 | | |
202 | 0 | #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d) |
203 | 0 | #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e) |
204 | | |
205 | | #define COOKIE_LOGEVICTED(a, details) \ |
206 | 0 | PR_BEGIN_MACRO \ |
207 | 0 | if (MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) \ |
208 | 0 | LogEvicted(a, details); \ |
209 | 0 | PR_END_MACRO |
210 | | |
211 | | #define COOKIE_LOGSTRING(lvl, fmt) \ |
212 | 0 | PR_BEGIN_MACRO \ |
213 | 0 | MOZ_LOG(gCookieLog, lvl, fmt); \ |
214 | 0 | MOZ_LOG(gCookieLog, lvl, ("\n")); \ |
215 | 0 | PR_END_MACRO |
216 | | |
217 | | static void |
218 | | LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason) |
219 | 0 | { |
220 | 0 | // if logging isn't enabled, return now to save cycles |
221 | 0 | if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) |
222 | 0 | return; |
223 | 0 | |
224 | 0 | nsAutoCString spec; |
225 | 0 | if (aHostURI) |
226 | 0 | aHostURI->GetAsciiSpec(spec); |
227 | 0 |
|
228 | 0 | MOZ_LOG(gCookieLog, LogLevel::Warning, |
229 | 0 | ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT")); |
230 | 0 | MOZ_LOG(gCookieLog, LogLevel::Warning,("request URL: %s\n", spec.get())); |
231 | 0 | if (aSetCookie) |
232 | 0 | MOZ_LOG(gCookieLog, LogLevel::Warning,("cookie string: %s\n", aCookieString)); |
233 | 0 |
|
234 | 0 | PRExplodedTime explodedTime; |
235 | 0 | PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime); |
236 | 0 | char timeString[40]; |
237 | 0 | PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
238 | 0 |
|
239 | 0 | MOZ_LOG(gCookieLog, LogLevel::Warning,("current time: %s", timeString)); |
240 | 0 | MOZ_LOG(gCookieLog, LogLevel::Warning,("rejected because %s\n", aReason)); |
241 | 0 | MOZ_LOG(gCookieLog, LogLevel::Warning,("\n")); |
242 | 0 | } |
243 | | |
244 | | static void |
245 | | LogCookie(nsCookie *aCookie) |
246 | 0 | { |
247 | 0 | PRExplodedTime explodedTime; |
248 | 0 | PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime); |
249 | 0 | char timeString[40]; |
250 | 0 | PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
251 | 0 |
|
252 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("current time: %s", timeString)); |
253 | 0 |
|
254 | 0 | if (aCookie) { |
255 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("----------------\n")); |
256 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("name: %s\n", aCookie->Name().get())); |
257 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("value: %s\n", aCookie->Value().get())); |
258 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get())); |
259 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("path: %s\n", aCookie->Path().get())); |
260 | 0 |
|
261 | 0 | PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC), |
262 | 0 | PR_GMTParameters, &explodedTime); |
263 | 0 | PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
264 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug, |
265 | 0 | ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : "")); |
266 | 0 |
|
267 | 0 | PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime); |
268 | 0 | PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
269 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("created: %s", timeString)); |
270 | 0 |
|
271 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false")); |
272 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false")); |
273 | 0 |
|
274 | 0 | nsAutoCString suffix; |
275 | 0 | aCookie->OriginAttributesRef().CreateSuffix(suffix); |
276 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("origin attributes: %s\n", |
277 | 0 | suffix.IsEmpty() ? "{empty}" : suffix.get())); |
278 | 0 | } |
279 | 0 | } |
280 | | |
281 | | static void |
282 | | LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing) |
283 | 0 | { |
284 | 0 | // if logging isn't enabled, return now to save cycles |
285 | 0 | if (!MOZ_LOG_TEST(gCookieLog, LogLevel::Debug)) { |
286 | 0 | return; |
287 | 0 | } |
288 | 0 | |
289 | 0 | nsAutoCString spec; |
290 | 0 | if (aHostURI) |
291 | 0 | aHostURI->GetAsciiSpec(spec); |
292 | 0 |
|
293 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug, |
294 | 0 | ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT")); |
295 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("request URL: %s\n", spec.get())); |
296 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("cookie string: %s\n", aCookieString)); |
297 | 0 | if (aSetCookie) |
298 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("replaces existing cookie: %s\n", aReplacing ? "true" : "false")); |
299 | 0 |
|
300 | 0 | LogCookie(aCookie); |
301 | 0 |
|
302 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("\n")); |
303 | 0 | } |
304 | | |
305 | | static void |
306 | | LogEvicted(nsCookie *aCookie, const char* details) |
307 | 0 | { |
308 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("===== COOKIE EVICTED =====\n")); |
309 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("%s\n", details)); |
310 | 0 |
|
311 | 0 | LogCookie(aCookie); |
312 | 0 |
|
313 | 0 | MOZ_LOG(gCookieLog, LogLevel::Debug,("\n")); |
314 | 0 | } |
315 | | |
316 | | // inline wrappers to make passing in nsCStrings easier |
317 | | static inline void |
318 | | LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsCString& aCookieString, const char *aReason) |
319 | 0 | { |
320 | 0 | LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason); |
321 | 0 | } |
322 | | |
323 | | static inline void |
324 | | LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsCString& aCookieString, nsCookie *aCookie, bool aReplacing) |
325 | 0 | { |
326 | 0 | LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing); |
327 | 0 | } |
328 | | |
329 | | #ifdef DEBUG |
330 | | #define NS_ASSERT_SUCCESS(res) \ |
331 | | PR_BEGIN_MACRO \ |
332 | | nsresult __rv = res; /* Do not evaluate |res| more than once! */ \ |
333 | | if (NS_FAILED(__rv)) { \ |
334 | | SmprintfPointer msg = mozilla::Smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%" PRIX32, \ |
335 | | #res, static_cast<uint32_t>(__rv)); \ |
336 | | NS_ASSERTION(NS_SUCCEEDED(__rv), msg.get()); \ |
337 | | } \ |
338 | | PR_END_MACRO |
339 | | #else |
340 | 0 | #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO |
341 | | #endif |
342 | | |
343 | | /****************************************************************************** |
344 | | * DBListenerErrorHandler impl: |
345 | | * Parent class for our async storage listeners that handles the logging of |
346 | | * errors. |
347 | | ******************************************************************************/ |
348 | | class DBListenerErrorHandler : public mozIStorageStatementCallback |
349 | | { |
350 | | protected: |
351 | 0 | explicit DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { } |
352 | | RefPtr<DBState> mDBState; |
353 | | virtual const char *GetOpType() = 0; |
354 | | |
355 | | public: |
356 | | NS_IMETHOD HandleError(mozIStorageError* aError) override |
357 | 0 | { |
358 | 0 | if (MOZ_LOG_TEST(gCookieLog, LogLevel::Warning)) { |
359 | 0 | int32_t result = -1; |
360 | 0 | aError->GetResult(&result); |
361 | 0 |
|
362 | 0 | nsAutoCString message; |
363 | 0 | aError->GetMessage(message); |
364 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
365 | 0 | ("DBListenerErrorHandler::HandleError(): Error %d occurred while " |
366 | 0 | "performing operation '%s' with message '%s'; rebuilding database.", |
367 | 0 | result, GetOpType(), message.get())); |
368 | 0 | } |
369 | 0 |
|
370 | 0 | // Rebuild the database. |
371 | 0 | gCookieService->HandleCorruptDB(mDBState); |
372 | 0 |
|
373 | 0 | return NS_OK; |
374 | 0 | } |
375 | | }; |
376 | | |
377 | | /****************************************************************************** |
378 | | * InsertCookieDBListener impl: |
379 | | * mozIStorageStatementCallback used to track asynchronous insertion operations. |
380 | | ******************************************************************************/ |
381 | | class InsertCookieDBListener final : public DBListenerErrorHandler |
382 | | { |
383 | | private: |
384 | 0 | const char *GetOpType() override { return "INSERT"; } |
385 | | |
386 | | ~InsertCookieDBListener() = default; |
387 | | |
388 | | public: |
389 | | NS_DECL_ISUPPORTS |
390 | | |
391 | 0 | explicit InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { } |
392 | | NS_IMETHOD HandleResult(mozIStorageResultSet*) override |
393 | 0 | { |
394 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected call to " |
395 | 0 | "InsertCookieDBListener::HandleResult"); |
396 | 0 | return NS_OK; |
397 | 0 | } |
398 | | NS_IMETHOD HandleCompletion(uint16_t aReason) override |
399 | 0 | { |
400 | 0 | // If we were rebuilding the db and we succeeded, make our corruptFlag say |
401 | 0 | // so. |
402 | 0 | if (mDBState->corruptFlag == DBState::REBUILDING && |
403 | 0 | aReason == mozIStorageStatementCallback::REASON_FINISHED) { |
404 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
405 | 0 | ("InsertCookieDBListener::HandleCompletion(): rebuild complete")); |
406 | 0 | mDBState->corruptFlag = DBState::OK; |
407 | 0 | } |
408 | 0 | return NS_OK; |
409 | 0 | } |
410 | | }; |
411 | | |
412 | | NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback) |
413 | | |
414 | | /****************************************************************************** |
415 | | * UpdateCookieDBListener impl: |
416 | | * mozIStorageStatementCallback used to track asynchronous update operations. |
417 | | ******************************************************************************/ |
418 | | class UpdateCookieDBListener final : public DBListenerErrorHandler |
419 | | { |
420 | | private: |
421 | 0 | const char *GetOpType() override { return "UPDATE"; } |
422 | | |
423 | | ~UpdateCookieDBListener() = default; |
424 | | |
425 | | public: |
426 | | NS_DECL_ISUPPORTS |
427 | | |
428 | 0 | explicit UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { } |
429 | | NS_IMETHOD HandleResult(mozIStorageResultSet*) override |
430 | 0 | { |
431 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected call to " |
432 | 0 | "UpdateCookieDBListener::HandleResult"); |
433 | 0 | return NS_OK; |
434 | 0 | } |
435 | | NS_IMETHOD HandleCompletion(uint16_t aReason) override |
436 | 0 | { |
437 | 0 | return NS_OK; |
438 | 0 | } |
439 | | }; |
440 | | |
441 | | NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback) |
442 | | |
443 | | /****************************************************************************** |
444 | | * RemoveCookieDBListener impl: |
445 | | * mozIStorageStatementCallback used to track asynchronous removal operations. |
446 | | ******************************************************************************/ |
447 | | class RemoveCookieDBListener final : public DBListenerErrorHandler |
448 | | { |
449 | | private: |
450 | 0 | const char *GetOpType() override { return "REMOVE"; } |
451 | | |
452 | | ~RemoveCookieDBListener() = default; |
453 | | |
454 | | public: |
455 | | NS_DECL_ISUPPORTS |
456 | | |
457 | 0 | explicit RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { } |
458 | | NS_IMETHOD HandleResult(mozIStorageResultSet*) override |
459 | 0 | { |
460 | 0 | MOZ_ASSERT_UNREACHABLE("Unexpected call to " |
461 | 0 | "RemoveCookieDBListener::HandleResult"); |
462 | 0 | return NS_OK; |
463 | 0 | } |
464 | | NS_IMETHOD HandleCompletion(uint16_t aReason) override |
465 | 0 | { |
466 | 0 | return NS_OK; |
467 | 0 | } |
468 | | }; |
469 | | |
470 | | NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback) |
471 | | |
472 | | /****************************************************************************** |
473 | | * CloseCookieDBListener imp: |
474 | | * Static mozIStorageCompletionCallback used to notify when the database is |
475 | | * successfully closed. |
476 | | ******************************************************************************/ |
477 | | class CloseCookieDBListener final : public mozIStorageCompletionCallback |
478 | | { |
479 | 0 | ~CloseCookieDBListener() = default; |
480 | | |
481 | | public: |
482 | 0 | explicit CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { } |
483 | | RefPtr<DBState> mDBState; |
484 | | NS_DECL_ISUPPORTS |
485 | | |
486 | | NS_IMETHOD Complete(nsresult, nsISupports*) override |
487 | 0 | { |
488 | 0 | gCookieService->HandleDBClosed(mDBState); |
489 | 0 | return NS_OK; |
490 | 0 | } |
491 | | }; |
492 | | |
493 | | NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback) |
494 | | |
495 | | namespace { |
496 | | |
497 | | class AppClearDataObserver final : public nsIObserver { |
498 | | |
499 | | ~AppClearDataObserver() = default; |
500 | | |
501 | | public: |
502 | | NS_DECL_ISUPPORTS |
503 | | |
504 | | // nsIObserver implementation. |
505 | | NS_IMETHOD |
506 | | Observe(nsISupports *aSubject, const char *aTopic, const char16_t *aData) override |
507 | 0 | { |
508 | 0 | MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_CLEAR_ORIGIN_DATA)); |
509 | 0 |
|
510 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
511 | 0 |
|
512 | 0 | nsCOMPtr<nsICookieManager> cookieManager |
513 | 0 | = do_GetService(NS_COOKIEMANAGER_CONTRACTID); |
514 | 0 | MOZ_ASSERT(cookieManager); |
515 | 0 |
|
516 | 0 | return cookieManager->RemoveCookiesWithOriginAttributes(nsDependentString(aData), EmptyCString()); |
517 | 0 | } |
518 | | }; |
519 | | |
520 | | NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver) |
521 | | |
522 | | // comparator class for sorting cookies by entry and index. |
523 | | class CompareCookiesByIndex { |
524 | | public: |
525 | | bool Equals(const nsListIter &a, const nsListIter &b) const |
526 | 0 | { |
527 | 0 | NS_ASSERTION(a.entry != b.entry || a.index != b.index, |
528 | 0 | "cookie indexes should never be equal"); |
529 | 0 | return false; |
530 | 0 | } |
531 | | |
532 | | bool LessThan(const nsListIter &a, const nsListIter &b) const |
533 | 0 | { |
534 | 0 | // compare by entryclass pointer, then by index. |
535 | 0 | if (a.entry != b.entry) |
536 | 0 | return a.entry < b.entry; |
537 | 0 | |
538 | 0 | return a.index < b.index; |
539 | 0 | } |
540 | | }; |
541 | | |
542 | | } // namespace |
543 | | |
544 | | size_t |
545 | | nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
546 | 0 | { |
547 | 0 | size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf); |
548 | 0 |
|
549 | 0 | amount += mCookies.ShallowSizeOfExcludingThis(aMallocSizeOf); |
550 | 0 | for (uint32_t i = 0; i < mCookies.Length(); ++i) { |
551 | 0 | amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf); |
552 | 0 | } |
553 | 0 |
|
554 | 0 | return amount; |
555 | 0 | } |
556 | | |
557 | | size_t |
558 | | DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
559 | 0 | { |
560 | 0 | size_t amount = 0; |
561 | 0 |
|
562 | 0 | amount += aMallocSizeOf(this); |
563 | 0 | amount += hostTable.SizeOfExcludingThis(aMallocSizeOf); |
564 | 0 |
|
565 | 0 | return amount; |
566 | 0 | } |
567 | | |
568 | | /****************************************************************************** |
569 | | * nsCookieService impl: |
570 | | * singleton instance ctor/dtor methods |
571 | | ******************************************************************************/ |
572 | | |
573 | | already_AddRefed<nsICookieService> |
574 | | nsCookieService::GetXPCOMSingleton() |
575 | 0 | { |
576 | 0 | if (IsNeckoChild()) |
577 | 0 | return CookieServiceChild::GetSingleton(); |
578 | 0 | |
579 | 0 | return GetSingleton(); |
580 | 0 | } |
581 | | |
582 | | already_AddRefed<nsCookieService> |
583 | | nsCookieService::GetSingleton() |
584 | 0 | { |
585 | 0 | NS_ASSERTION(!IsNeckoChild(), "not a parent process"); |
586 | 0 |
|
587 | 0 | if (gCookieService) { |
588 | 0 | return do_AddRef(gCookieService); |
589 | 0 | } |
590 | 0 | |
591 | 0 | // Create a new singleton nsCookieService. |
592 | 0 | // We AddRef only once since XPCOM has rules about the ordering of module |
593 | 0 | // teardowns - by the time our module destructor is called, it's too late to |
594 | 0 | // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC |
595 | 0 | // cycles have already been completed and would result in serious leaks. |
596 | 0 | // See bug 209571. |
597 | 0 | gCookieService = new nsCookieService(); |
598 | 0 | if (gCookieService) { |
599 | 0 | if (NS_SUCCEEDED(gCookieService->Init())) { |
600 | 0 | ClearOnShutdown(&gCookieService); |
601 | 0 | } else { |
602 | 0 | gCookieService = nullptr; |
603 | 0 | } |
604 | 0 | } |
605 | 0 |
|
606 | 0 | return do_AddRef(gCookieService); |
607 | 0 | } |
608 | | |
609 | | /* static */ void |
610 | | nsCookieService::AppClearDataObserverInit() |
611 | 3 | { |
612 | 3 | nsCOMPtr<nsIObserverService> observerService = services::GetObserverService(); |
613 | 3 | nsCOMPtr<nsIObserver> obs = new AppClearDataObserver(); |
614 | 3 | observerService->AddObserver(obs, TOPIC_CLEAR_ORIGIN_DATA, |
615 | 3 | /* ownsWeak= */ false); |
616 | 3 | } |
617 | | |
618 | | /****************************************************************************** |
619 | | * nsCookieService impl: |
620 | | * public methods |
621 | | ******************************************************************************/ |
622 | | |
623 | | NS_IMPL_ISUPPORTS(nsCookieService, |
624 | | nsICookieService, |
625 | | nsICookieManager, |
626 | | nsIObserver, |
627 | | nsISupportsWeakReference, |
628 | | nsIMemoryReporter) |
629 | | |
630 | | nsCookieService::nsCookieService() |
631 | | : mDBState(nullptr) |
632 | | , mCookieBehavior(nsICookieService::BEHAVIOR_ACCEPT) |
633 | | , mThirdPartySession(false) |
634 | | , mThirdPartyNonsecureSession(false) |
635 | | , mLeaveSecureAlone(true) |
636 | | , mMaxNumberOfCookies(kMaxNumberOfCookies) |
637 | | , mMaxCookiesPerHost(kMaxCookiesPerHost) |
638 | | , mCookieQuotaPerHost(kCookieQuotaPerHost) |
639 | | , mCookiePurgeAge(kCookiePurgeAge) |
640 | | , mThread(nullptr) |
641 | | , mMonitor("CookieThread") |
642 | | , mInitializedDBStates(false) |
643 | | , mInitializedDBConn(false) |
644 | 0 | { |
645 | 0 | } |
646 | | |
647 | | nsresult |
648 | | nsCookieService::Init() |
649 | 0 | { |
650 | 0 | nsresult rv; |
651 | 0 | mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); |
652 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
653 | 0 |
|
654 | 0 | mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv); |
655 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
656 | 0 |
|
657 | 0 | mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID); |
658 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
659 | 0 |
|
660 | 0 | // init our pref and observer |
661 | 0 | nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); |
662 | 0 | if (prefBranch) { |
663 | 0 | prefBranch->AddObserver(kPrefCookieBehavior, this, true); |
664 | 0 | prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true); |
665 | 0 | prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true); |
666 | 0 | prefBranch->AddObserver(kPrefCookiePurgeAge, this, true); |
667 | 0 | prefBranch->AddObserver(kPrefThirdPartySession, this, true); |
668 | 0 | prefBranch->AddObserver(kPrefThirdPartyNonsecureSession, this, true); |
669 | 0 | prefBranch->AddObserver(kCookieLeaveSecurityAlone, this, true); |
670 | 0 | PrefChanged(prefBranch); |
671 | 0 | } |
672 | 0 |
|
673 | 0 | mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv); |
674 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
675 | 0 |
|
676 | 0 | // Init our default, and possibly private DBStates. |
677 | 0 | InitDBStates(); |
678 | 0 |
|
679 | 0 | RegisterWeakMemoryReporter(this); |
680 | 0 |
|
681 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
682 | 0 | NS_ENSURE_STATE(os); |
683 | 0 | os->AddObserver(this, "profile-before-change", true); |
684 | 0 | os->AddObserver(this, "profile-do-change", true); |
685 | 0 | os->AddObserver(this, "last-pb-context-exited", true); |
686 | 0 |
|
687 | 0 | mPermissionService = nsCookiePermission::GetOrCreate(); |
688 | 0 |
|
689 | 0 | return NS_OK; |
690 | 0 | } |
691 | | |
692 | | void |
693 | | nsCookieService::InitDBStates() |
694 | 0 | { |
695 | 0 | NS_ASSERTION(!mDBState, "already have a DBState"); |
696 | 0 | NS_ASSERTION(!mDefaultDBState, "already have a default DBState"); |
697 | 0 | NS_ASSERTION(!mPrivateDBState, "already have a private DBState"); |
698 | 0 | NS_ASSERTION(!mInitializedDBStates, "already initialized"); |
699 | 0 | NS_ASSERTION(!mThread, "already have a cookie thread"); |
700 | 0 |
|
701 | 0 | // Create a new default DBState and set our current one. |
702 | 0 | mDefaultDBState = new DBState(); |
703 | 0 | mDBState = mDefaultDBState; |
704 | 0 |
|
705 | 0 | mPrivateDBState = new DBState(); |
706 | 0 |
|
707 | 0 | // Get our cookie file. |
708 | 0 | nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, |
709 | 0 | getter_AddRefs(mDefaultDBState->cookieFile)); |
710 | 0 | if (NS_FAILED(rv)) { |
711 | 0 | // We've already set up our DBStates appropriately; nothing more to do. |
712 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
713 | 0 | ("InitDBStates(): couldn't get cookie file")); |
714 | 0 |
|
715 | 0 | mInitializedDBConn = true; |
716 | 0 | mInitializedDBStates = true; |
717 | 0 | return; |
718 | 0 | } |
719 | 0 | mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE)); |
720 | 0 |
|
721 | 0 | NS_ENSURE_SUCCESS_VOID(NS_NewNamedThread("Cookie", getter_AddRefs(mThread))); |
722 | 0 |
|
723 | 0 | nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction("InitDBStates.TryInitDB", [] { |
724 | 0 | NS_ENSURE_TRUE_VOID(gCookieService && |
725 | 0 | gCookieService->mDBState && |
726 | 0 | gCookieService->mDefaultDBState); |
727 | 0 |
|
728 | 0 | MonitorAutoLock lock(gCookieService->mMonitor); |
729 | 0 |
|
730 | 0 | // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY, |
731 | 0 | // do so. |
732 | 0 | OpenDBResult result = gCookieService->TryInitDB(false); |
733 | 0 | if (result == RESULT_RETRY) { |
734 | 0 | // Database may be corrupt. Synchronously close the connection, clean up the |
735 | 0 | // default DBState, and try again. |
736 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBStates(): retrying TryInitDB()")); |
737 | 0 | gCookieService->CleanupCachedStatements(); |
738 | 0 | gCookieService->CleanupDefaultDBConnection(); |
739 | 0 | result = gCookieService->TryInitDB(true); |
740 | 0 | if (result == RESULT_RETRY) { |
741 | 0 | // We're done. Change the code to failure so we clean up below. |
742 | 0 | result = RESULT_FAILURE; |
743 | 0 | } |
744 | 0 | } |
745 | 0 |
|
746 | 0 | if (result == RESULT_FAILURE) { |
747 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
748 | 0 | ("InitDBStates(): TryInitDB() failed, closing connection")); |
749 | 0 |
|
750 | 0 | // Connection failure is unrecoverable. Clean up our connection. We can run |
751 | 0 | // fine without persistent storage -- e.g. if there's no profile. |
752 | 0 | gCookieService->CleanupCachedStatements(); |
753 | 0 | gCookieService->CleanupDefaultDBConnection(); |
754 | 0 |
|
755 | 0 | // No need to initialize dbConn |
756 | 0 | gCookieService->mInitializedDBConn = true; |
757 | 0 | } |
758 | 0 |
|
759 | 0 | gCookieService->mInitializedDBStates = true; |
760 | 0 |
|
761 | 0 | NS_DispatchToMainThread( |
762 | 0 | NS_NewRunnableFunction("TryInitDB.InitDBConn", [] { |
763 | 0 | NS_ENSURE_TRUE_VOID(gCookieService); |
764 | 0 | gCookieService->InitDBConn(); |
765 | 0 | }) |
766 | 0 | ); |
767 | 0 | gCookieService->mMonitor.Notify(); |
768 | 0 | }); |
769 | 0 |
|
770 | 0 | mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
771 | 0 | } |
772 | | |
773 | | namespace { |
774 | | |
775 | | class ConvertAppIdToOriginAttrsSQLFunction final : public mozIStorageFunction |
776 | | { |
777 | | ~ConvertAppIdToOriginAttrsSQLFunction() = default; |
778 | | |
779 | | NS_DECL_ISUPPORTS |
780 | | NS_DECL_MOZISTORAGEFUNCTION |
781 | | }; |
782 | | |
783 | | NS_IMPL_ISUPPORTS(ConvertAppIdToOriginAttrsSQLFunction, mozIStorageFunction); |
784 | | |
785 | | NS_IMETHODIMP |
786 | | ConvertAppIdToOriginAttrsSQLFunction::OnFunctionCall( |
787 | | mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) |
788 | 0 | { |
789 | 0 | nsresult rv; |
790 | 0 | int32_t inIsolatedMozBrowser; |
791 | 0 |
|
792 | 0 | rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser); |
793 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
794 | 0 |
|
795 | 0 | // Create an originAttributes object by inIsolatedMozBrowser. |
796 | 0 | // Then create the originSuffix string from this object. |
797 | 0 | OriginAttributes attrs(nsIScriptSecurityManager::NO_APP_ID, |
798 | 0 | (inIsolatedMozBrowser ? true : false)); |
799 | 0 | nsAutoCString suffix; |
800 | 0 | attrs.CreateSuffix(suffix); |
801 | 0 |
|
802 | 0 | RefPtr<nsVariant> outVar(new nsVariant()); |
803 | 0 | rv = outVar->SetAsAUTF8String(suffix); |
804 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
805 | 0 |
|
806 | 0 | outVar.forget(aResult); |
807 | 0 | return NS_OK; |
808 | 0 | } |
809 | | |
810 | | class SetAppIdFromOriginAttributesSQLFunction final : public mozIStorageFunction |
811 | | { |
812 | | ~SetAppIdFromOriginAttributesSQLFunction() = default; |
813 | | |
814 | | NS_DECL_ISUPPORTS |
815 | | NS_DECL_MOZISTORAGEFUNCTION |
816 | | }; |
817 | | |
818 | | NS_IMPL_ISUPPORTS(SetAppIdFromOriginAttributesSQLFunction, mozIStorageFunction); |
819 | | |
820 | | NS_IMETHODIMP |
821 | | SetAppIdFromOriginAttributesSQLFunction::OnFunctionCall( |
822 | | mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) |
823 | 0 | { |
824 | 0 | nsresult rv; |
825 | 0 | nsAutoCString suffix; |
826 | 0 | OriginAttributes attrs; |
827 | 0 |
|
828 | 0 | rv = aFunctionArguments->GetUTF8String(0, suffix); |
829 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
830 | 0 | bool success = attrs.PopulateFromSuffix(suffix); |
831 | 0 | NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); |
832 | 0 |
|
833 | 0 | RefPtr<nsVariant> outVar(new nsVariant()); |
834 | 0 | rv = outVar->SetAsInt32(attrs.mAppId); |
835 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
836 | 0 |
|
837 | 0 | outVar.forget(aResult); |
838 | 0 | return NS_OK; |
839 | 0 | } |
840 | | |
841 | | class SetInBrowserFromOriginAttributesSQLFunction final : |
842 | | public mozIStorageFunction |
843 | | { |
844 | | ~SetInBrowserFromOriginAttributesSQLFunction() = default; |
845 | | |
846 | | NS_DECL_ISUPPORTS |
847 | | NS_DECL_MOZISTORAGEFUNCTION |
848 | | }; |
849 | | |
850 | | NS_IMPL_ISUPPORTS(SetInBrowserFromOriginAttributesSQLFunction, |
851 | | mozIStorageFunction); |
852 | | |
853 | | NS_IMETHODIMP |
854 | | SetInBrowserFromOriginAttributesSQLFunction::OnFunctionCall( |
855 | | mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult) |
856 | 0 | { |
857 | 0 | nsresult rv; |
858 | 0 | nsAutoCString suffix; |
859 | 0 | OriginAttributes attrs; |
860 | 0 |
|
861 | 0 | rv = aFunctionArguments->GetUTF8String(0, suffix); |
862 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
863 | 0 | bool success = attrs.PopulateFromSuffix(suffix); |
864 | 0 | NS_ENSURE_TRUE(success, NS_ERROR_FAILURE); |
865 | 0 |
|
866 | 0 | RefPtr<nsVariant> outVar(new nsVariant()); |
867 | 0 | rv = outVar->SetAsInt32(attrs.mInIsolatedMozBrowser); |
868 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
869 | 0 |
|
870 | 0 | outVar.forget(aResult); |
871 | 0 | return NS_OK; |
872 | 0 | } |
873 | | |
874 | | } // namespace |
875 | | |
876 | | /* Attempt to open and read the database. If 'aRecreateDB' is true, try to |
877 | | * move the existing database file out of the way and create a new one. |
878 | | * |
879 | | * @returns RESULT_OK if opening or creating the database succeeded; |
880 | | * RESULT_RETRY if the database cannot be opened, is corrupt, or some |
881 | | * other failure occurred that might be resolved by recreating the |
882 | | * database; or RESULT_FAILED if there was an unrecoverable error and |
883 | | * we must run without a database. |
884 | | * |
885 | | * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform |
886 | | * cleanup of the default DBState. |
887 | | */ |
888 | | OpenDBResult |
889 | | nsCookieService::TryInitDB(bool aRecreateDB) |
890 | 0 | { |
891 | 0 | NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn"); |
892 | 0 | NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert"); |
893 | 0 | NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener"); |
894 | 0 | NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn"); |
895 | 0 | NS_ASSERTION(NS_GetCurrentThread() == mThread, "non cookie thread"); |
896 | 0 |
|
897 | 0 | // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't |
898 | 0 | // want to delete it outright, since it may be useful for debugging purposes, |
899 | 0 | // so we move it out of the way. |
900 | 0 | nsresult rv; |
901 | 0 | if (aRecreateDB) { |
902 | 0 | nsCOMPtr<nsIFile> backupFile; |
903 | 0 | mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile)); |
904 | 0 | rv = backupFile->MoveToNative(nullptr, |
905 | 0 | NS_LITERAL_CSTRING(COOKIES_FILE ".bak")); |
906 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_FAILURE); |
907 | 0 | } |
908 | 0 |
|
909 | 0 | // This block provides scope for the Telemetry AutoTimer |
910 | 0 | { |
911 | 0 | Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS> |
912 | 0 | telemetry; |
913 | 0 | ReadAheadFile(mDefaultDBState->cookieFile); |
914 | 0 |
|
915 | 0 | // open a connection to the cookie database, and only cache our connection |
916 | 0 | // and statements upon success. The connection is opened unshared to eliminate |
917 | 0 | // cache contention between the main and background threads. |
918 | 0 | rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile, |
919 | 0 | getter_AddRefs(mDefaultDBState->syncConn)); |
920 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
921 | 0 | } |
922 | 0 |
|
923 | 0 | auto guard = MakeScopeExit([&] { |
924 | 0 | mDefaultDBState->syncConn = nullptr; |
925 | 0 | }); |
926 | 0 |
|
927 | 0 | bool tableExists = false; |
928 | 0 | mDefaultDBState->syncConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"), |
929 | 0 | &tableExists); |
930 | 0 | if (!tableExists) { |
931 | 0 | rv = CreateTable(); |
932 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
933 | 0 |
|
934 | 0 | } else { |
935 | 0 | // table already exists; check the schema version before reading |
936 | 0 | int32_t dbSchemaVersion; |
937 | 0 | rv = mDefaultDBState->syncConn->GetSchemaVersion(&dbSchemaVersion); |
938 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
939 | 0 |
|
940 | 0 | // Start a transaction for the whole migration block. |
941 | 0 | mozStorageTransaction transaction(mDefaultDBState->syncConn, true); |
942 | 0 |
|
943 | 0 | switch (dbSchemaVersion) { |
944 | 0 | // Upgrading. |
945 | 0 | // Every time you increment the database schema, you need to implement |
946 | 0 | // the upgrading code from the previous version to the new one. If migration |
947 | 0 | // fails for any reason, it's a bug -- so we return RESULT_RETRY such that |
948 | 0 | // the original database will be saved, in the hopes that we might one day |
949 | 0 | // see it and fix it. |
950 | 0 | case 1: |
951 | 0 | { |
952 | 0 | // Add the lastAccessed column to the table. |
953 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
954 | 0 | "ALTER TABLE moz_cookies ADD lastAccessed INTEGER")); |
955 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
956 | 0 | } |
957 | 0 | // Fall through to the next upgrade. |
958 | 0 | MOZ_FALLTHROUGH; |
959 | 0 |
|
960 | 0 | case 2: |
961 | 0 | { |
962 | 0 | // Add the baseDomain column and index to the table. |
963 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
964 | 0 | "ALTER TABLE moz_cookies ADD baseDomain TEXT")); |
965 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
966 | 0 |
|
967 | 0 | // Compute the baseDomains for the table. This must be done eagerly |
968 | 0 | // otherwise we won't be able to synchronously read in individual |
969 | 0 | // domains on demand. |
970 | 0 | const int64_t SCHEMA2_IDX_ID = 0; |
971 | 0 | const int64_t SCHEMA2_IDX_HOST = 1; |
972 | 0 | nsCOMPtr<mozIStorageStatement> select; |
973 | 0 | rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
974 | 0 | "SELECT id, host FROM moz_cookies"), getter_AddRefs(select)); |
975 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
976 | 0 |
|
977 | 0 | nsCOMPtr<mozIStorageStatement> update; |
978 | 0 | rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
979 | 0 | "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"), |
980 | 0 | getter_AddRefs(update)); |
981 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
982 | 0 |
|
983 | 0 | nsCString baseDomain, host; |
984 | 0 | bool hasResult; |
985 | 0 | while (true) { |
986 | 0 | rv = select->ExecuteStep(&hasResult); |
987 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
988 | 0 |
|
989 | 0 | if (!hasResult) |
990 | 0 | break; |
991 | 0 | |
992 | 0 | int64_t id = select->AsInt64(SCHEMA2_IDX_ID); |
993 | 0 | select->GetUTF8String(SCHEMA2_IDX_HOST, host); |
994 | 0 |
|
995 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
996 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
997 | 0 |
|
998 | 0 | mozStorageStatementScoper scoper(update); |
999 | 0 |
|
1000 | 0 | rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"), |
1001 | 0 | baseDomain); |
1002 | 0 | NS_ASSERT_SUCCESS(rv); |
1003 | 0 | rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"), |
1004 | 0 | id); |
1005 | 0 | NS_ASSERT_SUCCESS(rv); |
1006 | 0 |
|
1007 | 0 | rv = update->ExecuteStep(&hasResult); |
1008 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1009 | 0 | } |
1010 | 0 |
|
1011 | 0 | // Create an index on baseDomain. |
1012 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1013 | 0 | "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)")); |
1014 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1015 | 0 | } |
1016 | 0 | // Fall through to the next upgrade. |
1017 | 0 | MOZ_FALLTHROUGH; |
1018 | 0 |
|
1019 | 0 | case 3: |
1020 | 0 | { |
1021 | 0 | // Add the creationTime column to the table, and create a unique index |
1022 | 0 | // on (name, host, path). Before we do this, we have to purge the table |
1023 | 0 | // of expired cookies such that we know that the (name, host, path) |
1024 | 0 | // index is truly unique -- otherwise we can't create the index. Note |
1025 | 0 | // that we can't just execute a statement to delete all rows where the |
1026 | 0 | // expiry column is in the past -- doing so would rely on the clock |
1027 | 0 | // (both now and when previous cookies were set) being monotonic. |
1028 | 0 |
|
1029 | 0 | // Select the whole table, and order by the fields we're interested in. |
1030 | 0 | // This means we can simply do a linear traversal of the results and |
1031 | 0 | // check for duplicates as we go. |
1032 | 0 | const int64_t SCHEMA3_IDX_ID = 0; |
1033 | 0 | const int64_t SCHEMA3_IDX_NAME = 1; |
1034 | 0 | const int64_t SCHEMA3_IDX_HOST = 2; |
1035 | 0 | const int64_t SCHEMA3_IDX_PATH = 3; |
1036 | 0 | nsCOMPtr<mozIStorageStatement> select; |
1037 | 0 | rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
1038 | 0 | "SELECT id, name, host, path FROM moz_cookies " |
1039 | 0 | "ORDER BY name ASC, host ASC, path ASC, expiry ASC"), |
1040 | 0 | getter_AddRefs(select)); |
1041 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1042 | 0 |
|
1043 | 0 | nsCOMPtr<mozIStorageStatement> deleteExpired; |
1044 | 0 | rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
1045 | 0 | "DELETE FROM moz_cookies WHERE id = :id"), |
1046 | 0 | getter_AddRefs(deleteExpired)); |
1047 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1048 | 0 |
|
1049 | 0 | // Read the first row. |
1050 | 0 | bool hasResult; |
1051 | 0 | rv = select->ExecuteStep(&hasResult); |
1052 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1053 | 0 |
|
1054 | 0 | if (hasResult) { |
1055 | 0 | nsCString name1, host1, path1; |
1056 | 0 | int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID); |
1057 | 0 | select->GetUTF8String(SCHEMA3_IDX_NAME, name1); |
1058 | 0 | select->GetUTF8String(SCHEMA3_IDX_HOST, host1); |
1059 | 0 | select->GetUTF8String(SCHEMA3_IDX_PATH, path1); |
1060 | 0 |
|
1061 | 0 | nsCString name2, host2, path2; |
1062 | 0 | while (true) { |
1063 | 0 | // Read the second row. |
1064 | 0 | rv = select->ExecuteStep(&hasResult); |
1065 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1066 | 0 |
|
1067 | 0 | if (!hasResult) |
1068 | 0 | break; |
1069 | 0 | |
1070 | 0 | int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID); |
1071 | 0 | select->GetUTF8String(SCHEMA3_IDX_NAME, name2); |
1072 | 0 | select->GetUTF8String(SCHEMA3_IDX_HOST, host2); |
1073 | 0 | select->GetUTF8String(SCHEMA3_IDX_PATH, path2); |
1074 | 0 |
|
1075 | 0 | // If the two rows match in (name, host, path), we know the earlier |
1076 | 0 | // row has an earlier expiry time. Delete it. |
1077 | 0 | if (name1 == name2 && host1 == host2 && path1 == path2) { |
1078 | 0 | mozStorageStatementScoper scoper(deleteExpired); |
1079 | 0 |
|
1080 | 0 | rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"), |
1081 | 0 | id1); |
1082 | 0 | NS_ASSERT_SUCCESS(rv); |
1083 | 0 |
|
1084 | 0 | rv = deleteExpired->ExecuteStep(&hasResult); |
1085 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1086 | 0 | } |
1087 | 0 |
|
1088 | 0 | // Make the second row the first for the next iteration. |
1089 | 0 | name1 = name2; |
1090 | 0 | host1 = host2; |
1091 | 0 | path1 = path2; |
1092 | 0 | id1 = id2; |
1093 | 0 | } |
1094 | 0 | } |
1095 | 0 |
|
1096 | 0 | // Add the creationTime column to the table. |
1097 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1098 | 0 | "ALTER TABLE moz_cookies ADD creationTime INTEGER")); |
1099 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1100 | 0 |
|
1101 | 0 | // Copy the id of each row into the new creationTime column. |
1102 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1103 | 0 | "UPDATE moz_cookies SET creationTime = " |
1104 | 0 | "(SELECT id WHERE id = moz_cookies.id)")); |
1105 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1106 | 0 |
|
1107 | 0 | // Create a unique index on (name, host, path) to allow fast lookup. |
1108 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1109 | 0 | "CREATE UNIQUE INDEX moz_uniqueid " |
1110 | 0 | "ON moz_cookies (name, host, path)")); |
1111 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1112 | 0 | } |
1113 | 0 | // Fall through to the next upgrade. |
1114 | 0 | MOZ_FALLTHROUGH; |
1115 | 0 |
|
1116 | 0 | case 4: |
1117 | 0 | { |
1118 | 0 | // We need to add appId/inBrowserElement, plus change a constraint on |
1119 | 0 | // the table (unique entries now include appId/inBrowserElement): |
1120 | 0 | // this requires creating a new table and copying the data to it. We |
1121 | 0 | // then rename the new table to the old name. |
1122 | 0 | // |
1123 | 0 | // Why we made this change: appId/inBrowserElement allow "cookie jars" |
1124 | 0 | // for Firefox OS. We create a separate cookie namespace per {appId, |
1125 | 0 | // inBrowserElement}. When upgrading, we convert existing cookies |
1126 | 0 | // (which imply we're on desktop/mobile) to use {0, false}, as that is |
1127 | 0 | // the only namespace used by a non-Firefox-OS implementation. |
1128 | 0 |
|
1129 | 0 | // Rename existing table |
1130 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1131 | 0 | "ALTER TABLE moz_cookies RENAME TO moz_cookies_old")); |
1132 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1133 | 0 |
|
1134 | 0 | // Drop existing index (CreateTable will create new one for new table) |
1135 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1136 | 0 | "DROP INDEX moz_basedomain")); |
1137 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1138 | 0 |
|
1139 | 0 | // Create new table (with new fields and new unique constraint) |
1140 | 0 | rv = CreateTableForSchemaVersion5(); |
1141 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1142 | 0 |
|
1143 | 0 | // Copy data from old table, using appId/inBrowser=0 for existing rows |
1144 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1145 | 0 | "INSERT INTO moz_cookies " |
1146 | 0 | "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry," |
1147 | 0 | " lastAccessed, creationTime, isSecure, isHttpOnly) " |
1148 | 0 | "SELECT baseDomain, 0, 0, name, value, host, path, expiry," |
1149 | 0 | " lastAccessed, creationTime, isSecure, isHttpOnly " |
1150 | 0 | "FROM moz_cookies_old")); |
1151 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1152 | 0 |
|
1153 | 0 | // Drop old table |
1154 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1155 | 0 | "DROP TABLE moz_cookies_old")); |
1156 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1157 | 0 |
|
1158 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1159 | 0 | ("Upgraded database to schema version 5")); |
1160 | 0 | } |
1161 | 0 | // Fall through to the next upgrade. |
1162 | 0 | MOZ_FALLTHROUGH; |
1163 | 0 |
|
1164 | 0 | case 5: |
1165 | 0 | { |
1166 | 0 | // Change in the version: Replace the columns |appId| and |
1167 | 0 | // |inBrowserElement| by a single column |originAttributes|. |
1168 | 0 | // |
1169 | 0 | // Why we made this change: FxOS new security model (NSec) encapsulates |
1170 | 0 | // "appId/inIsolatedMozBrowser" in nsIPrincipal::originAttributes to make |
1171 | 0 | // it easier to modify the contents of this structure in the future. |
1172 | 0 | // |
1173 | 0 | // We do the migration in several steps: |
1174 | 0 | // 1. Rename the old table. |
1175 | 0 | // 2. Create a new table. |
1176 | 0 | // 3. Copy data from the old table to the new table; convert appId and |
1177 | 0 | // inBrowserElement to originAttributes in the meantime. |
1178 | 0 |
|
1179 | 0 | // Rename existing table. |
1180 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1181 | 0 | "ALTER TABLE moz_cookies RENAME TO moz_cookies_old")); |
1182 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1183 | 0 |
|
1184 | 0 | // Drop existing index (CreateTable will create new one for new table). |
1185 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1186 | 0 | "DROP INDEX moz_basedomain")); |
1187 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1188 | 0 |
|
1189 | 0 | // Create new table with new fields and new unique constraint. |
1190 | 0 | rv = CreateTableForSchemaVersion6(); |
1191 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1192 | 0 |
|
1193 | 0 | // Copy data from old table without the two deprecated columns appId and |
1194 | 0 | // inBrowserElement. |
1195 | 0 | nsCOMPtr<mozIStorageFunction> |
1196 | 0 | convertToOriginAttrs(new ConvertAppIdToOriginAttrsSQLFunction()); |
1197 | 0 | NS_ENSURE_TRUE(convertToOriginAttrs, RESULT_RETRY); |
1198 | 0 |
|
1199 | 0 | NS_NAMED_LITERAL_CSTRING(convertToOriginAttrsName, |
1200 | 0 | "CONVERT_TO_ORIGIN_ATTRIBUTES"); |
1201 | 0 |
|
1202 | 0 | rv = mDefaultDBState->syncConn->CreateFunction(convertToOriginAttrsName, |
1203 | 0 | 2, convertToOriginAttrs); |
1204 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1205 | 0 |
|
1206 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1207 | 0 | "INSERT INTO moz_cookies " |
1208 | 0 | "(baseDomain, originAttributes, name, value, host, path, expiry," |
1209 | 0 | " lastAccessed, creationTime, isSecure, isHttpOnly) " |
1210 | 0 | "SELECT baseDomain, " |
1211 | 0 | " CONVERT_TO_ORIGIN_ATTRIBUTES(appId, inBrowserElement)," |
1212 | 0 | " name, value, host, path, expiry, lastAccessed, creationTime, " |
1213 | 0 | " isSecure, isHttpOnly " |
1214 | 0 | "FROM moz_cookies_old")); |
1215 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1216 | 0 |
|
1217 | 0 | rv = mDefaultDBState->syncConn->RemoveFunction(convertToOriginAttrsName); |
1218 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1219 | 0 |
|
1220 | 0 | // Drop old table |
1221 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1222 | 0 | "DROP TABLE moz_cookies_old")); |
1223 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1224 | 0 |
|
1225 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1226 | 0 | ("Upgraded database to schema version 6")); |
1227 | 0 | } |
1228 | 0 | MOZ_FALLTHROUGH; |
1229 | 0 |
|
1230 | 0 | case 6: |
1231 | 0 | { |
1232 | 0 | // We made a mistake in schema version 6. We cannot remove expected |
1233 | 0 | // columns of any version (checked in the default case) from cookie |
1234 | 0 | // database, because doing this would destroy the possibility of |
1235 | 0 | // downgrading database. |
1236 | 0 | // |
1237 | 0 | // This version simply restores appId and inBrowserElement columns in |
1238 | 0 | // order to fix downgrading issue even though these two columns are no |
1239 | 0 | // longer used in the latest schema. |
1240 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1241 | 0 | "ALTER TABLE moz_cookies ADD appId INTEGER DEFAULT 0;")); |
1242 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1243 | 0 |
|
1244 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1245 | 0 | "ALTER TABLE moz_cookies ADD inBrowserElement INTEGER DEFAULT 0;")); |
1246 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1247 | 0 |
|
1248 | 0 | // Compute and populate the values of appId and inBrwoserElement from |
1249 | 0 | // originAttributes. |
1250 | 0 | nsCOMPtr<mozIStorageFunction> |
1251 | 0 | setAppId(new SetAppIdFromOriginAttributesSQLFunction()); |
1252 | 0 | NS_ENSURE_TRUE(setAppId, RESULT_RETRY); |
1253 | 0 |
|
1254 | 0 | NS_NAMED_LITERAL_CSTRING(setAppIdName, "SET_APP_ID"); |
1255 | 0 |
|
1256 | 0 | rv = mDefaultDBState->syncConn->CreateFunction(setAppIdName, 1, setAppId); |
1257 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1258 | 0 |
|
1259 | 0 | nsCOMPtr<mozIStorageFunction> |
1260 | 0 | setInBrowser(new SetInBrowserFromOriginAttributesSQLFunction()); |
1261 | 0 | NS_ENSURE_TRUE(setInBrowser, RESULT_RETRY); |
1262 | 0 |
|
1263 | 0 | NS_NAMED_LITERAL_CSTRING(setInBrowserName, "SET_IN_BROWSER"); |
1264 | 0 |
|
1265 | 0 | rv = mDefaultDBState->syncConn->CreateFunction(setInBrowserName, 1, |
1266 | 0 | setInBrowser); |
1267 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1268 | 0 |
|
1269 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1270 | 0 | "UPDATE moz_cookies SET appId = SET_APP_ID(originAttributes), " |
1271 | 0 | "inBrowserElement = SET_IN_BROWSER(originAttributes);" |
1272 | 0 | )); |
1273 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1274 | 0 |
|
1275 | 0 | rv = mDefaultDBState->syncConn->RemoveFunction(setAppIdName); |
1276 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1277 | 0 |
|
1278 | 0 | rv = mDefaultDBState->syncConn->RemoveFunction(setInBrowserName); |
1279 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1280 | 0 |
|
1281 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1282 | 0 | ("Upgraded database to schema version 7")); |
1283 | 0 | } |
1284 | 0 | MOZ_FALLTHROUGH; |
1285 | 0 |
|
1286 | 0 | case 7: |
1287 | 0 | { |
1288 | 0 | // Remove the appId field from moz_cookies. |
1289 | 0 | // |
1290 | 0 | // Unfortunately sqlite doesn't support dropping columns using ALTER |
1291 | 0 | // TABLE, so we need to go through the procedure documented in |
1292 | 0 | // https://www.sqlite.org/lang_altertable.html. |
1293 | 0 |
|
1294 | 0 | // Drop existing index |
1295 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1296 | 0 | "DROP INDEX moz_basedomain")); |
1297 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1298 | 0 |
|
1299 | 0 | // Create a new_moz_cookies table without the appId field. |
1300 | 0 | rv = CreateTableWorker("new_moz_cookies"); |
1301 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1302 | 0 |
|
1303 | 0 | // Move the data over. |
1304 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1305 | 0 | "INSERT INTO new_moz_cookies (" |
1306 | 0 | "id, " |
1307 | 0 | "baseDomain, " |
1308 | 0 | "originAttributes, " |
1309 | 0 | "name, " |
1310 | 0 | "value, " |
1311 | 0 | "host, " |
1312 | 0 | "path, " |
1313 | 0 | "expiry, " |
1314 | 0 | "lastAccessed, " |
1315 | 0 | "creationTime, " |
1316 | 0 | "isSecure, " |
1317 | 0 | "isHttpOnly, " |
1318 | 0 | "inBrowserElement " |
1319 | 0 | ") SELECT " |
1320 | 0 | "id, " |
1321 | 0 | "baseDomain, " |
1322 | 0 | "originAttributes, " |
1323 | 0 | "name, " |
1324 | 0 | "value, " |
1325 | 0 | "host, " |
1326 | 0 | "path, " |
1327 | 0 | "expiry, " |
1328 | 0 | "lastAccessed, " |
1329 | 0 | "creationTime, " |
1330 | 0 | "isSecure, " |
1331 | 0 | "isHttpOnly, " |
1332 | 0 | "inBrowserElement " |
1333 | 0 | "FROM moz_cookies;")); |
1334 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1335 | 0 |
|
1336 | 0 | // Drop the old table |
1337 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1338 | 0 | "DROP TABLE moz_cookies;")); |
1339 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1340 | 0 |
|
1341 | 0 | // Rename new_moz_cookies to moz_cookies. |
1342 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1343 | 0 | "ALTER TABLE new_moz_cookies RENAME TO moz_cookies;")); |
1344 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1345 | 0 |
|
1346 | 0 | // Recreate our index. |
1347 | 0 | rv = CreateIndex(); |
1348 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1349 | 0 |
|
1350 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1351 | 0 | ("Upgraded database to schema version 8")); |
1352 | 0 | } |
1353 | 0 | MOZ_FALLTHROUGH; |
1354 | 0 |
|
1355 | 0 | case 8: |
1356 | 0 | { |
1357 | 0 | // Add the sameSite column to the table. |
1358 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL( |
1359 | 0 | NS_LITERAL_CSTRING("ALTER TABLE moz_cookies ADD sameSite INTEGER")); |
1360 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1361 | 0 | ("Upgraded database to schema version 9")); |
1362 | 0 | } |
1363 | 0 |
|
1364 | 0 | // No more upgrades. Update the schema version. |
1365 | 0 | rv = mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); |
1366 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1367 | 0 |
|
1368 | 0 | MOZ_FALLTHROUGH; |
1369 | 0 |
|
1370 | 0 | case COOKIES_SCHEMA_VERSION: |
1371 | 0 | break; |
1372 | 0 |
|
1373 | 0 | case 0: |
1374 | 0 | { |
1375 | 0 | NS_WARNING("couldn't get schema version!"); |
1376 | 0 |
|
1377 | 0 | // the table may be usable; someone might've just clobbered the schema |
1378 | 0 | // version. we can treat this case like a downgrade using the codepath |
1379 | 0 | // below, by verifying the columns we care about are all there. for now, |
1380 | 0 | // re-set the schema version in the db, in case the checks succeed (if |
1381 | 0 | // they don't, we're dropping the table anyway). |
1382 | 0 | rv = mDefaultDBState->syncConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); |
1383 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1384 | 0 | } |
1385 | 0 | // fall through to downgrade check |
1386 | 0 | MOZ_FALLTHROUGH; |
1387 | 0 |
|
1388 | 0 | // downgrading. |
1389 | 0 | // if columns have been added to the table, we can still use the ones we |
1390 | 0 | // understand safely. if columns have been deleted or altered, just |
1391 | 0 | // blow away the table and start from scratch! if you change the way |
1392 | 0 | // a column is interpreted, make sure you also change its name so this |
1393 | 0 | // check will catch it. |
1394 | 0 | default: |
1395 | 0 | { |
1396 | 0 | // check if all the expected columns exist |
1397 | 0 | nsCOMPtr<mozIStorageStatement> stmt; |
1398 | 0 | rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
1399 | 0 | "SELECT " |
1400 | 0 | "id, " |
1401 | 0 | "baseDomain, " |
1402 | 0 | "originAttributes, " |
1403 | 0 | "name, " |
1404 | 0 | "value, " |
1405 | 0 | "host, " |
1406 | 0 | "path, " |
1407 | 0 | "expiry, " |
1408 | 0 | "lastAccessed, " |
1409 | 0 | "creationTime, " |
1410 | 0 | "isSecure, " |
1411 | 0 | "isHttpOnly, " |
1412 | 0 | "sameSite " |
1413 | 0 | "FROM moz_cookies"), getter_AddRefs(stmt)); |
1414 | 0 | if (NS_SUCCEEDED(rv)) |
1415 | 0 | break; |
1416 | 0 | |
1417 | 0 | // our columns aren't there - drop the table! |
1418 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1419 | 0 | "DROP TABLE moz_cookies")); |
1420 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1421 | 0 |
|
1422 | 0 | rv = CreateTable(); |
1423 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
1424 | 0 | } |
1425 | 0 | break; |
1426 | 0 | } |
1427 | 0 | } |
1428 | 0 | |
1429 | 0 | // if we deleted a corrupt db, don't attempt to import - return now |
1430 | 0 | if (aRecreateDB) { |
1431 | 0 | return RESULT_OK; |
1432 | 0 | } |
1433 | 0 | |
1434 | 0 | // check whether to import or just read in the db |
1435 | 0 | if (tableExists) { |
1436 | 0 | return Read(); |
1437 | 0 | } |
1438 | 0 | |
1439 | 0 | nsCOMPtr<nsIRunnable> runnable = |
1440 | 0 | NS_NewRunnableFunction("TryInitDB.ImportCookies", [] { |
1441 | 0 | NS_ENSURE_TRUE_VOID(gCookieService); |
1442 | 0 | NS_ENSURE_TRUE_VOID(gCookieService->mDefaultDBState); |
1443 | 0 | nsCOMPtr<nsIFile> oldCookieFile; |
1444 | 0 | nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, |
1445 | 0 | getter_AddRefs(oldCookieFile)); |
1446 | 0 | if (NS_FAILED(rv)) { |
1447 | 0 | return; |
1448 | 0 | } |
1449 | 0 | |
1450 | 0 | // Import cookies, and clean up the old file regardless of success or failure. |
1451 | 0 | // Note that we have to switch out our DBState temporarily, in case we're in |
1452 | 0 | // private browsing mode; otherwise ImportCookies() won't be happy. |
1453 | 0 | DBState* initialState = gCookieService->mDBState; |
1454 | 0 | gCookieService->mDBState = gCookieService->mDefaultDBState; |
1455 | 0 | oldCookieFile->AppendNative(NS_LITERAL_CSTRING(OLD_COOKIE_FILE_NAME)); |
1456 | 0 | gCookieService->ImportCookies(oldCookieFile); |
1457 | 0 | oldCookieFile->Remove(false); |
1458 | 0 | gCookieService->mDBState = initialState; |
1459 | 0 | }); |
1460 | 0 |
|
1461 | 0 | NS_DispatchToMainThread(runnable); |
1462 | 0 |
|
1463 | 0 | return RESULT_OK; |
1464 | 0 | } |
1465 | | |
1466 | | void |
1467 | | nsCookieService::InitDBConn() |
1468 | 0 | { |
1469 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1470 | 0 |
|
1471 | 0 | // We should skip InitDBConn if we close profile during initializing DBStates |
1472 | 0 | // and then InitDBConn is called after we close the DBStates. |
1473 | 0 | if (!mInitializedDBStates || mInitializedDBConn || !mDefaultDBState) { |
1474 | 0 | return; |
1475 | 0 | } |
1476 | 0 | |
1477 | 0 | for (uint32_t i = 0; i < mReadArray.Length(); ++i) { |
1478 | 0 | CookieDomainTuple& tuple = mReadArray[i]; |
1479 | 0 | RefPtr<nsCookie> cookie = nsCookie::Create(tuple.cookie->name, |
1480 | 0 | tuple.cookie->value, |
1481 | 0 | tuple.cookie->host, |
1482 | 0 | tuple.cookie->path, |
1483 | 0 | tuple.cookie->expiry, |
1484 | 0 | tuple.cookie->lastAccessed, |
1485 | 0 | tuple.cookie->creationTime, |
1486 | 0 | false, |
1487 | 0 | tuple.cookie->isSecure, |
1488 | 0 | tuple.cookie->isHttpOnly, |
1489 | 0 | tuple.cookie->originAttributes, |
1490 | 0 | tuple.cookie->sameSite); |
1491 | 0 |
|
1492 | 0 | AddCookieToList(tuple.key, cookie, mDefaultDBState, nullptr, false); |
1493 | 0 | } |
1494 | 0 |
|
1495 | 0 | if (NS_FAILED(InitDBConnInternal())) { |
1496 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, ("InitDBConn(): retrying InitDBConnInternal()")); |
1497 | 0 | CleanupCachedStatements(); |
1498 | 0 | CleanupDefaultDBConnection(); |
1499 | 0 | if (NS_FAILED(InitDBConnInternal())) { |
1500 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
1501 | 0 | ("InitDBConn(): InitDBConnInternal() failed, closing connection")); |
1502 | 0 |
|
1503 | 0 | // Game over, clean the connections. |
1504 | 0 | CleanupCachedStatements(); |
1505 | 0 | CleanupDefaultDBConnection(); |
1506 | 0 | } |
1507 | 0 | } |
1508 | 0 | mInitializedDBConn = true; |
1509 | 0 |
|
1510 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, ("InitDBConn(): mInitializedDBConn = true")); |
1511 | 0 | mEndInitDBConn = mozilla::TimeStamp::Now(); |
1512 | 0 |
|
1513 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
1514 | 0 | if (os) { |
1515 | 0 | os->NotifyObservers(nullptr, "cookie-db-read", nullptr); |
1516 | 0 | mReadArray.Clear(); |
1517 | 0 | } |
1518 | 0 | } |
1519 | | |
1520 | | nsresult |
1521 | | nsCookieService::InitDBConnInternal() |
1522 | 0 | { |
1523 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
1524 | 0 |
|
1525 | 0 | nsresult rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile, |
1526 | 0 | getter_AddRefs(mDefaultDBState->dbConn)); |
1527 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1528 | 0 |
|
1529 | 0 | // Set up our listeners. |
1530 | 0 | mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState); |
1531 | 0 | mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState); |
1532 | 0 | mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState); |
1533 | 0 | mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState); |
1534 | 0 |
|
1535 | 0 | // Grow cookie db in 512KB increments |
1536 | 0 | mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString()); |
1537 | 0 |
|
1538 | 0 | // make operations on the table asynchronous, for performance |
1539 | 0 | mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1540 | 0 | "PRAGMA synchronous = OFF")); |
1541 | 0 |
|
1542 | 0 | // Use write-ahead-logging for performance. We cap the autocheckpoint limit at |
1543 | 0 | // 16 pages (around 500KB). |
1544 | 0 | mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1545 | 0 | MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL")); |
1546 | 0 | mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1547 | 0 | "PRAGMA wal_autocheckpoint = 16")); |
1548 | 0 |
|
1549 | 0 | // cache frequently used statements (for insertion, deletion, and updating) |
1550 | 0 | rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
1551 | 0 | "INSERT INTO moz_cookies (" |
1552 | 0 | "baseDomain, " |
1553 | 0 | "originAttributes, " |
1554 | 0 | "name, " |
1555 | 0 | "value, " |
1556 | 0 | "host, " |
1557 | 0 | "path, " |
1558 | 0 | "expiry, " |
1559 | 0 | "lastAccessed, " |
1560 | 0 | "creationTime, " |
1561 | 0 | "isSecure, " |
1562 | 0 | "isHttpOnly, " |
1563 | 0 | "sameSite " |
1564 | 0 | ") VALUES (" |
1565 | 0 | ":baseDomain, " |
1566 | 0 | ":originAttributes, " |
1567 | 0 | ":name, " |
1568 | 0 | ":value, " |
1569 | 0 | ":host, " |
1570 | 0 | ":path, " |
1571 | 0 | ":expiry, " |
1572 | 0 | ":lastAccessed, " |
1573 | 0 | ":creationTime, " |
1574 | 0 | ":isSecure, " |
1575 | 0 | ":isHttpOnly, " |
1576 | 0 | ":sameSite" |
1577 | 0 | ")"), |
1578 | 0 | getter_AddRefs(mDefaultDBState->stmtInsert)); |
1579 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1580 | 0 |
|
1581 | 0 | rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
1582 | 0 | "DELETE FROM moz_cookies " |
1583 | 0 | "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"), |
1584 | 0 | getter_AddRefs(mDefaultDBState->stmtDelete)); |
1585 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
1586 | 0 |
|
1587 | 0 | rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
1588 | 0 | "UPDATE moz_cookies SET lastAccessed = :lastAccessed " |
1589 | 0 | "WHERE name = :name AND host = :host AND path = :path AND originAttributes = :originAttributes"), |
1590 | 0 | getter_AddRefs(mDefaultDBState->stmtUpdate)); |
1591 | 0 | return rv; |
1592 | 0 | } |
1593 | | |
1594 | | // Sets the schema version and creates the moz_cookies table. |
1595 | | nsresult |
1596 | | nsCookieService::CreateTableWorker(const char* aName) |
1597 | 0 | { |
1598 | 0 | // Create the table. |
1599 | 0 | // We default originAttributes to empty string: this is so if users revert to |
1600 | 0 | // an older Firefox version that doesn't know about this field, any cookies |
1601 | 0 | // set will still work once they upgrade back. |
1602 | 0 | nsAutoCString command("CREATE TABLE "); |
1603 | 0 | command.Append(aName); |
1604 | 0 | command.AppendLiteral(" (" |
1605 | 0 | "id INTEGER PRIMARY KEY, " |
1606 | 0 | "baseDomain TEXT, " |
1607 | 0 | "originAttributes TEXT NOT NULL DEFAULT '', " |
1608 | 0 | "name TEXT, " |
1609 | 0 | "value TEXT, " |
1610 | 0 | "host TEXT, " |
1611 | 0 | "path TEXT, " |
1612 | 0 | "expiry INTEGER, " |
1613 | 0 | "lastAccessed INTEGER, " |
1614 | 0 | "creationTime INTEGER, " |
1615 | 0 | "isSecure INTEGER, " |
1616 | 0 | "isHttpOnly INTEGER, " |
1617 | 0 | "inBrowserElement INTEGER DEFAULT 0, " |
1618 | 0 | "sameSite INTEGER DEFAULT 0, " |
1619 | 0 | "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" |
1620 | 0 | ")"); |
1621 | 0 | return mDefaultDBState->syncConn->ExecuteSimpleSQL(command); |
1622 | 0 | } |
1623 | | |
1624 | | // Sets the schema version and creates the moz_cookies table. |
1625 | | nsresult |
1626 | | nsCookieService::CreateTable() |
1627 | 0 | { |
1628 | 0 | // Set the schema version, before creating the table. |
1629 | 0 | nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion( |
1630 | 0 | COOKIES_SCHEMA_VERSION); |
1631 | 0 | if (NS_FAILED(rv)) return rv; |
1632 | 0 | |
1633 | 0 | rv = CreateTableWorker("moz_cookies"); |
1634 | 0 | if (NS_FAILED(rv)) return rv; |
1635 | 0 | |
1636 | 0 | return CreateIndex(); |
1637 | 0 | } |
1638 | | |
1639 | | nsresult |
1640 | | nsCookieService::CreateIndex() |
1641 | 0 | { |
1642 | 0 | // Create an index on baseDomain. |
1643 | 0 | return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1644 | 0 | "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " |
1645 | 0 | "originAttributes)")); |
1646 | 0 | } |
1647 | | |
1648 | | // Sets the schema version and creates the moz_cookies table. |
1649 | | nsresult |
1650 | | nsCookieService::CreateTableForSchemaVersion6() |
1651 | 0 | { |
1652 | 0 | // Set the schema version, before creating the table. |
1653 | 0 | nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(6); |
1654 | 0 | if (NS_FAILED(rv)) return rv; |
1655 | 0 | |
1656 | 0 | // Create the table. |
1657 | 0 | // We default originAttributes to empty string: this is so if users revert to |
1658 | 0 | // an older Firefox version that doesn't know about this field, any cookies |
1659 | 0 | // set will still work once they upgrade back. |
1660 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1661 | 0 | "CREATE TABLE moz_cookies (" |
1662 | 0 | "id INTEGER PRIMARY KEY, " |
1663 | 0 | "baseDomain TEXT, " |
1664 | 0 | "originAttributes TEXT NOT NULL DEFAULT '', " |
1665 | 0 | "name TEXT, " |
1666 | 0 | "value TEXT, " |
1667 | 0 | "host TEXT, " |
1668 | 0 | "path TEXT, " |
1669 | 0 | "expiry INTEGER, " |
1670 | 0 | "lastAccessed INTEGER, " |
1671 | 0 | "creationTime INTEGER, " |
1672 | 0 | "isSecure INTEGER, " |
1673 | 0 | "isHttpOnly INTEGER, " |
1674 | 0 | "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, originAttributes)" |
1675 | 0 | ")")); |
1676 | 0 | if (NS_FAILED(rv)) return rv; |
1677 | 0 | |
1678 | 0 | // Create an index on baseDomain. |
1679 | 0 | return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1680 | 0 | "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " |
1681 | 0 | "originAttributes)")); |
1682 | 0 | } |
1683 | | |
1684 | | // Sets the schema version and creates the moz_cookies table. |
1685 | | nsresult |
1686 | | nsCookieService::CreateTableForSchemaVersion5() |
1687 | 0 | { |
1688 | 0 | // Set the schema version, before creating the table. |
1689 | 0 | nsresult rv = mDefaultDBState->syncConn->SetSchemaVersion(5); |
1690 | 0 | if (NS_FAILED(rv)) return rv; |
1691 | 0 | |
1692 | 0 | // Create the table. We default appId/inBrowserElement to 0: this is so if |
1693 | 0 | // users revert to an older Firefox version that doesn't know about these |
1694 | 0 | // fields, any cookies set will still work once they upgrade back. |
1695 | 0 | rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1696 | 0 | "CREATE TABLE moz_cookies (" |
1697 | 0 | "id INTEGER PRIMARY KEY, " |
1698 | 0 | "baseDomain TEXT, " |
1699 | 0 | "appId INTEGER DEFAULT 0, " |
1700 | 0 | "inBrowserElement INTEGER DEFAULT 0, " |
1701 | 0 | "name TEXT, " |
1702 | 0 | "value TEXT, " |
1703 | 0 | "host TEXT, " |
1704 | 0 | "path TEXT, " |
1705 | 0 | "expiry INTEGER, " |
1706 | 0 | "lastAccessed INTEGER, " |
1707 | 0 | "creationTime INTEGER, " |
1708 | 0 | "isSecure INTEGER, " |
1709 | 0 | "isHttpOnly INTEGER, " |
1710 | 0 | "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)" |
1711 | 0 | ")")); |
1712 | 0 | if (NS_FAILED(rv)) return rv; |
1713 | 0 | |
1714 | 0 | // Create an index on baseDomain. |
1715 | 0 | return mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
1716 | 0 | "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " |
1717 | 0 | "appId, " |
1718 | 0 | "inBrowserElement)")); |
1719 | 0 | } |
1720 | | |
1721 | | void |
1722 | | nsCookieService::CloseDBStates() |
1723 | 0 | { |
1724 | 0 | // return if we already closed |
1725 | 0 | if (!mDBState) { |
1726 | 0 | return; |
1727 | 0 | } |
1728 | 0 | |
1729 | 0 | if (mThread) { |
1730 | 0 | mThread->Shutdown(); |
1731 | 0 | mThread = nullptr; |
1732 | 0 | } |
1733 | 0 |
|
1734 | 0 | // Null out our private and pointer DBStates regardless. |
1735 | 0 | mPrivateDBState = nullptr; |
1736 | 0 | mDBState = nullptr; |
1737 | 0 |
|
1738 | 0 | // If we don't have a default DBState, we're done. |
1739 | 0 | if (!mDefaultDBState) |
1740 | 0 | return; |
1741 | 0 | |
1742 | 0 | // Cleanup cached statements before we can close anything. |
1743 | 0 | CleanupCachedStatements(); |
1744 | 0 |
|
1745 | 0 | if (mDefaultDBState->dbConn) { |
1746 | 0 | // Asynchronously close the connection. We will null it below. |
1747 | 0 | mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener); |
1748 | 0 | } |
1749 | 0 |
|
1750 | 0 | CleanupDefaultDBConnection(); |
1751 | 0 |
|
1752 | 0 | mDefaultDBState = nullptr; |
1753 | 0 | mInitializedDBConn = false; |
1754 | 0 | mInitializedDBStates = false; |
1755 | 0 | } |
1756 | | |
1757 | | // Null out the statements. |
1758 | | // This must be done before closing the connection. |
1759 | | void |
1760 | | nsCookieService::CleanupCachedStatements() |
1761 | 0 | { |
1762 | 0 | mDefaultDBState->stmtInsert = nullptr; |
1763 | 0 | mDefaultDBState->stmtDelete = nullptr; |
1764 | 0 | mDefaultDBState->stmtUpdate = nullptr; |
1765 | 0 | } |
1766 | | |
1767 | | // Null out the listeners, and the database connection itself. This |
1768 | | // will not null out the statements, cancel a pending read or |
1769 | | // asynchronously close the connection -- these must be done |
1770 | | // beforehand if necessary. |
1771 | | void |
1772 | | nsCookieService::CleanupDefaultDBConnection() |
1773 | 0 | { |
1774 | 0 | MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up"); |
1775 | 0 | MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up"); |
1776 | 0 | MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up"); |
1777 | 0 |
|
1778 | 0 | // Null out the database connections. If 'dbConn' has not been used for any |
1779 | 0 | // asynchronous operations yet, this will synchronously close it; otherwise, |
1780 | 0 | // it's expected that the caller has performed an AsyncClose prior. |
1781 | 0 | mDefaultDBState->dbConn = nullptr; |
1782 | 0 |
|
1783 | 0 | // Manually null out our listeners. This is necessary because they hold a |
1784 | 0 | // strong ref to the DBState itself. They'll stay alive until whatever |
1785 | 0 | // statements are still executing complete. |
1786 | 0 | mDefaultDBState->insertListener = nullptr; |
1787 | 0 | mDefaultDBState->updateListener = nullptr; |
1788 | 0 | mDefaultDBState->removeListener = nullptr; |
1789 | 0 | mDefaultDBState->closeListener = nullptr; |
1790 | 0 | } |
1791 | | |
1792 | | void |
1793 | | nsCookieService::HandleDBClosed(DBState* aDBState) |
1794 | 0 | { |
1795 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1796 | 0 | ("HandleDBClosed(): DBState %p closed", aDBState)); |
1797 | 0 |
|
1798 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
1799 | 0 |
|
1800 | 0 | switch (aDBState->corruptFlag) { |
1801 | 0 | case DBState::OK: { |
1802 | 0 | // Database is healthy. Notify of closure. |
1803 | 0 | if (os) { |
1804 | 0 | os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
1805 | 0 | } |
1806 | 0 | break; |
1807 | 0 | } |
1808 | 0 | case DBState::CLOSING_FOR_REBUILD: { |
1809 | 0 | // Our close finished. Start the rebuild, and notify of db closure later. |
1810 | 0 | RebuildCorruptDB(aDBState); |
1811 | 0 | break; |
1812 | 0 | } |
1813 | 0 | case DBState::REBUILDING: { |
1814 | 0 | // We encountered an error during rebuild, closed the database, and now |
1815 | 0 | // here we are. We already have a 'cookies.sqlite.bak' from the original |
1816 | 0 | // dead database; we don't want to overwrite it, so let's move this one to |
1817 | 0 | // 'cookies.sqlite.bak-rebuild'. |
1818 | 0 | nsCOMPtr<nsIFile> backupFile; |
1819 | 0 | aDBState->cookieFile->Clone(getter_AddRefs(backupFile)); |
1820 | 0 | nsresult rv = backupFile->MoveToNative(nullptr, |
1821 | 0 | NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild")); |
1822 | 0 |
|
1823 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
1824 | 0 | ("HandleDBClosed(): DBState %p encountered error rebuilding db; move to " |
1825 | 0 | "'cookies.sqlite.bak-rebuild' gave rv 0x%" PRIx32, |
1826 | 0 | aDBState, static_cast<uint32_t>(rv))); |
1827 | 0 | if (os) { |
1828 | 0 | os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
1829 | 0 | } |
1830 | 0 | break; |
1831 | 0 | } |
1832 | 0 | } |
1833 | 0 | } |
1834 | | |
1835 | | void |
1836 | | nsCookieService::HandleCorruptDB(DBState* aDBState) |
1837 | 0 | { |
1838 | 0 | if (mDefaultDBState != aDBState) { |
1839 | 0 | // We've either closed the state or we've switched profiles. It's getting |
1840 | 0 | // a bit late to rebuild -- bail instead. |
1841 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
1842 | 0 | ("HandleCorruptDB(): DBState %p is already closed, aborting", aDBState)); |
1843 | 0 | return; |
1844 | 0 | } |
1845 | 0 |
|
1846 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1847 | 0 | ("HandleCorruptDB(): DBState %p has corruptFlag %u", aDBState, |
1848 | 0 | aDBState->corruptFlag)); |
1849 | 0 |
|
1850 | 0 | // Mark the database corrupt, so the close listener can begin reconstructing |
1851 | 0 | // it. |
1852 | 0 | switch (mDefaultDBState->corruptFlag) { |
1853 | 0 | case DBState::OK: { |
1854 | 0 | // Move to 'closing' state. |
1855 | 0 | mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD; |
1856 | 0 |
|
1857 | 0 | CleanupCachedStatements(); |
1858 | 0 | mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener); |
1859 | 0 | CleanupDefaultDBConnection(); |
1860 | 0 | break; |
1861 | 0 | } |
1862 | 0 | case DBState::CLOSING_FOR_REBUILD: { |
1863 | 0 | // We had an error while waiting for close completion. That's OK, just |
1864 | 0 | // ignore it -- we're rebuilding anyway. |
1865 | 0 | return; |
1866 | 0 | } |
1867 | 0 | case DBState::REBUILDING: { |
1868 | 0 | // We had an error while rebuilding the DB. Game over. Close the database |
1869 | 0 | // and let the close handler do nothing; then we'll move it out of the way. |
1870 | 0 | CleanupCachedStatements(); |
1871 | 0 | if (mDefaultDBState->dbConn) { |
1872 | 0 | mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener); |
1873 | 0 | } |
1874 | 0 | CleanupDefaultDBConnection(); |
1875 | 0 | break; |
1876 | 0 | } |
1877 | 0 | } |
1878 | 0 | } |
1879 | | |
1880 | | void |
1881 | | nsCookieService::RebuildCorruptDB(DBState* aDBState) |
1882 | 0 | { |
1883 | 0 | NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection"); |
1884 | 0 | NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD, |
1885 | 0 | "should be in CLOSING_FOR_REBUILD state"); |
1886 | 0 |
|
1887 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
1888 | 0 |
|
1889 | 0 | aDBState->corruptFlag = DBState::REBUILDING; |
1890 | 0 |
|
1891 | 0 | if (mDefaultDBState != aDBState) { |
1892 | 0 | // We've either closed the state or we've switched profiles. It's getting |
1893 | 0 | // a bit late to rebuild -- bail instead. In any case, we were waiting |
1894 | 0 | // on rebuild completion to notify of the db closure, which won't happen -- |
1895 | 0 | // do so now. |
1896 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
1897 | 0 | ("RebuildCorruptDB(): DBState %p is stale, aborting", aDBState)); |
1898 | 0 | if (os) { |
1899 | 0 | os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
1900 | 0 | } |
1901 | 0 | return; |
1902 | 0 | } |
1903 | 0 |
|
1904 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1905 | 0 | ("RebuildCorruptDB(): creating new database")); |
1906 | 0 |
|
1907 | 0 | nsCOMPtr<nsIRunnable> runnable = |
1908 | 0 | NS_NewRunnableFunction("RebuildCorruptDB.TryInitDB", [] { |
1909 | 0 | NS_ENSURE_TRUE_VOID(gCookieService && gCookieService->mDefaultDBState); |
1910 | 0 |
|
1911 | 0 | // The database has been closed, and we're ready to rebuild. Open a |
1912 | 0 | // connection. |
1913 | 0 | OpenDBResult result = gCookieService->TryInitDB(true); |
1914 | 0 |
|
1915 | 0 | nsCOMPtr<nsIRunnable> innerRunnable = |
1916 | 0 | NS_NewRunnableFunction("RebuildCorruptDB.TryInitDBComplete", [result] { |
1917 | 0 | NS_ENSURE_TRUE_VOID(gCookieService && gCookieService->mDefaultDBState); |
1918 | 0 |
|
1919 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
1920 | 0 | if (result != RESULT_OK) { |
1921 | 0 | // We're done. Reset our DB connection and statements, and notify of |
1922 | 0 | // closure. |
1923 | 0 | COOKIE_LOGSTRING(LogLevel::Warning, |
1924 | 0 | ("RebuildCorruptDB(): TryInitDB() failed with result %u", result)); |
1925 | 0 | gCookieService->CleanupCachedStatements(); |
1926 | 0 | gCookieService->CleanupDefaultDBConnection(); |
1927 | 0 | gCookieService->mDefaultDBState->corruptFlag = DBState::OK; |
1928 | 0 | if (os) { |
1929 | 0 | os->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
1930 | 0 | } |
1931 | 0 | return; |
1932 | 0 | } |
1933 | 0 |
|
1934 | 0 | // Notify observers that we're beginning the rebuild. |
1935 | 0 | if (os) { |
1936 | 0 | os->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr); |
1937 | 0 | } |
1938 | 0 |
|
1939 | 0 | gCookieService->InitDBConnInternal(); |
1940 | 0 |
|
1941 | 0 | // Enumerate the hash, and add cookies to the params array. |
1942 | 0 | mozIStorageAsyncStatement* stmt = gCookieService->mDefaultDBState->stmtInsert; |
1943 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
1944 | 0 | stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
1945 | 0 | for (auto iter = gCookieService->mDefaultDBState->hostTable.Iter(); |
1946 | 0 | !iter.Done(); |
1947 | 0 | iter.Next()) { |
1948 | 0 | nsCookieEntry* entry = iter.Get(); |
1949 | 0 |
|
1950 | 0 | const nsCookieEntry::ArrayType& cookies = entry->GetCookies(); |
1951 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
1952 | 0 | nsCookie* cookie = cookies[i]; |
1953 | 0 |
|
1954 | 0 | if (!cookie->IsSession()) { |
1955 | 0 | bindCookieParameters(paramsArray, nsCookieKey(entry), cookie); |
1956 | 0 | } |
1957 | 0 | } |
1958 | 0 | } |
1959 | 0 |
|
1960 | 0 | // Make sure we've got something to write. If we don't, we're done. |
1961 | 0 | uint32_t length; |
1962 | 0 | paramsArray->GetLength(&length); |
1963 | 0 | if (length == 0) { |
1964 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
1965 | 0 | ("RebuildCorruptDB(): nothing to write, rebuild complete")); |
1966 | 0 | gCookieService->mDefaultDBState->corruptFlag = DBState::OK; |
1967 | 0 | return; |
1968 | 0 | } |
1969 | 0 |
|
1970 | 0 | // Execute the statement. If any errors crop up, we won't try again. |
1971 | 0 | DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
1972 | 0 | NS_ASSERT_SUCCESS(rv); |
1973 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
1974 | 0 | rv = stmt->ExecuteAsync(gCookieService->mDefaultDBState->insertListener, |
1975 | 0 | getter_AddRefs(handle)); |
1976 | 0 | NS_ASSERT_SUCCESS(rv); |
1977 | 0 | }); |
1978 | 0 | NS_DispatchToMainThread(innerRunnable); |
1979 | 0 | }); |
1980 | 0 | mThread->Dispatch(runnable, NS_DISPATCH_NORMAL); |
1981 | 0 | } |
1982 | | |
1983 | | nsCookieService::~nsCookieService() |
1984 | 0 | { |
1985 | 0 | CloseDBStates(); |
1986 | 0 |
|
1987 | 0 | UnregisterWeakMemoryReporter(this); |
1988 | 0 |
|
1989 | 0 | gCookieService = nullptr; |
1990 | 0 | } |
1991 | | |
1992 | | NS_IMETHODIMP |
1993 | | nsCookieService::Observe(nsISupports *aSubject, |
1994 | | const char *aTopic, |
1995 | | const char16_t *aData) |
1996 | 0 | { |
1997 | 0 | // check the topic |
1998 | 0 | if (!strcmp(aTopic, "profile-before-change")) { |
1999 | 0 | // The profile is about to change, |
2000 | 0 | // or is going away because the application is shutting down. |
2001 | 0 |
|
2002 | 0 | // Close the default DB connection and null out our DBStates before |
2003 | 0 | // changing. |
2004 | 0 | CloseDBStates(); |
2005 | 0 |
|
2006 | 0 | } else if (!strcmp(aTopic, "profile-do-change")) { |
2007 | 0 | NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState"); |
2008 | 0 | NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState"); |
2009 | 0 |
|
2010 | 0 | // the profile has already changed; init the db from the new location. |
2011 | 0 | // if we are in the private browsing state, however, we do not want to read |
2012 | 0 | // data into it - we should instead put it into the default state, so it's |
2013 | 0 | // ready for us if and when we switch back to it. |
2014 | 0 | InitDBStates(); |
2015 | 0 |
|
2016 | 0 | } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { |
2017 | 0 | nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject); |
2018 | 0 | if (prefBranch) |
2019 | 0 | PrefChanged(prefBranch); |
2020 | 0 |
|
2021 | 0 | } else if (!strcmp(aTopic, "last-pb-context-exited")) { |
2022 | 0 | // Flush all the cookies stored by private browsing contexts |
2023 | 0 | mozilla::OriginAttributesPattern pattern; |
2024 | 0 | pattern.mPrivateBrowsingId.Construct(1); |
2025 | 0 | RemoveCookiesWithOriginAttributes(pattern, EmptyCString()); |
2026 | 0 | mPrivateDBState = new DBState(); |
2027 | 0 | } |
2028 | 0 |
|
2029 | 0 |
|
2030 | 0 | return NS_OK; |
2031 | 0 | } |
2032 | | |
2033 | | NS_IMETHODIMP |
2034 | | nsCookieService::GetCookieString(nsIURI *aHostURI, |
2035 | | nsIChannel *aChannel, |
2036 | | char **aCookie) |
2037 | 0 | { |
2038 | 0 | return GetCookieStringCommon(aHostURI, aChannel, false, aCookie); |
2039 | 0 | } |
2040 | | |
2041 | | NS_IMETHODIMP |
2042 | | nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI, |
2043 | | nsIURI *aFirstURI, |
2044 | | nsIChannel *aChannel, |
2045 | | char **aCookie) |
2046 | 0 | { |
2047 | 0 | return GetCookieStringCommon(aHostURI, aChannel, true, aCookie); |
2048 | 0 | } |
2049 | | |
2050 | | nsresult |
2051 | | nsCookieService::GetCookieStringCommon(nsIURI *aHostURI, |
2052 | | nsIChannel *aChannel, |
2053 | | bool aHttpBound, |
2054 | | char** aCookie) |
2055 | 0 | { |
2056 | 0 | NS_ENSURE_ARG(aHostURI); |
2057 | 0 | NS_ENSURE_ARG(aCookie); |
2058 | 0 |
|
2059 | 0 | // Determine whether the request is foreign. Failure is acceptable. |
2060 | 0 | bool isForeign = true; |
2061 | 0 | mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); |
2062 | 0 |
|
2063 | 0 | bool isTrackingResource = false; |
2064 | 0 | bool firstPartyStorageAccessGranted = false; |
2065 | 0 | nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); |
2066 | 0 | if (httpChannel) { |
2067 | 0 | isTrackingResource = httpChannel->GetIsTrackingResource(); |
2068 | 0 |
|
2069 | 0 | // Check first-party storage access even for non-tracking resources, since |
2070 | 0 | // we will need the result when computing the access rights for the reject |
2071 | 0 | // foreign cookie behavior mode. |
2072 | 0 | if (isForeign && |
2073 | 0 | AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(httpChannel, |
2074 | 0 | aHostURI, |
2075 | 0 | nullptr)) { |
2076 | 0 | firstPartyStorageAccessGranted = true; |
2077 | 0 | } |
2078 | 0 | } |
2079 | 0 |
|
2080 | 0 | OriginAttributes attrs; |
2081 | 0 | if (aChannel) { |
2082 | 0 | NS_GetOriginAttributes(aChannel, attrs); |
2083 | 0 | } |
2084 | 0 |
|
2085 | 0 | bool isSafeTopLevelNav = NS_IsSafeTopLevelNav(aChannel); |
2086 | 0 | bool isSameSiteForeign = NS_IsSameSiteForeign(aChannel, aHostURI); |
2087 | 0 | nsAutoCString result; |
2088 | 0 | GetCookieStringInternal(aHostURI, isForeign, isTrackingResource, |
2089 | 0 | firstPartyStorageAccessGranted, isSafeTopLevelNav, |
2090 | 0 | isSameSiteForeign, aHttpBound, attrs, result); |
2091 | 0 | *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result); |
2092 | 0 | return NS_OK; |
2093 | 0 | } |
2094 | | |
2095 | | NS_IMETHODIMP |
2096 | | nsCookieService::SetCookieString(nsIURI *aHostURI, |
2097 | | nsIPrompt *aPrompt, |
2098 | | const char *aCookieHeader, |
2099 | | nsIChannel *aChannel) |
2100 | 0 | { |
2101 | 0 | // The aPrompt argument is deprecated and unused. Avoid introducing new |
2102 | 0 | // code that uses this argument by warning if the value is non-null. |
2103 | 0 | MOZ_ASSERT(!aPrompt); |
2104 | 0 | if (aPrompt) { |
2105 | 0 | nsCOMPtr<nsIConsoleService> aConsoleService = |
2106 | 0 | do_GetService("@mozilla.org/consoleservice;1"); |
2107 | 0 | if (aConsoleService) { |
2108 | 0 | aConsoleService->LogStringMessage( |
2109 | 0 | u"Non-null prompt ignored by nsCookieService."); |
2110 | 0 | } |
2111 | 0 | } |
2112 | 0 | return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel, |
2113 | 0 | false); |
2114 | 0 | } |
2115 | | |
2116 | | NS_IMETHODIMP |
2117 | | nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI, |
2118 | | nsIURI *aFirstURI, |
2119 | | nsIPrompt *aPrompt, |
2120 | | const char *aCookieHeader, |
2121 | | const char *aServerTime, |
2122 | | nsIChannel *aChannel) |
2123 | 0 | { |
2124 | 0 | // The aPrompt argument is deprecated and unused. Avoid introducing new |
2125 | 0 | // code that uses this argument by warning if the value is non-null. |
2126 | 0 | MOZ_ASSERT(!aPrompt); |
2127 | 0 | if (aPrompt) { |
2128 | 0 | nsCOMPtr<nsIConsoleService> aConsoleService = |
2129 | 0 | do_GetService("@mozilla.org/consoleservice;1"); |
2130 | 0 | if (aConsoleService) { |
2131 | 0 | aConsoleService->LogStringMessage( |
2132 | 0 | u"Non-null prompt ignored by nsCookieService."); |
2133 | 0 | } |
2134 | 0 | } |
2135 | 0 | return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel, |
2136 | 0 | true); |
2137 | 0 | } |
2138 | | |
2139 | | int64_t |
2140 | | nsCookieService::ParseServerTime(const nsCString &aServerTime) |
2141 | 0 | { |
2142 | 0 | // parse server local time. this is not just done here for efficiency |
2143 | 0 | // reasons - if there's an error parsing it, and we need to default it |
2144 | 0 | // to the current time, we must do it here since the current time in |
2145 | 0 | // SetCookieInternal() will change for each cookie processed (e.g. if the |
2146 | 0 | // user is prompted). |
2147 | 0 | PRTime tempServerTime; |
2148 | 0 | int64_t serverTime; |
2149 | 0 | PRStatus result = PR_ParseTimeString(aServerTime.get(), true, |
2150 | 0 | &tempServerTime); |
2151 | 0 | if (result == PR_SUCCESS) { |
2152 | 0 | serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC); |
2153 | 0 | } else { |
2154 | 0 | serverTime = PR_Now() / PR_USEC_PER_SEC; |
2155 | 0 | } |
2156 | 0 |
|
2157 | 0 | return serverTime; |
2158 | 0 | } |
2159 | | |
2160 | | nsresult |
2161 | | nsCookieService::SetCookieStringCommon(nsIURI *aHostURI, |
2162 | | const char *aCookieHeader, |
2163 | | const char *aServerTime, |
2164 | | nsIChannel *aChannel, |
2165 | | bool aFromHttp) |
2166 | 0 | { |
2167 | 0 | NS_ENSURE_ARG(aHostURI); |
2168 | 0 | NS_ENSURE_ARG(aCookieHeader); |
2169 | 0 |
|
2170 | 0 | // Determine whether the request is foreign. Failure is acceptable. |
2171 | 0 | bool isForeign = true; |
2172 | 0 | mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); |
2173 | 0 |
|
2174 | 0 | bool isTrackingResource = false; |
2175 | 0 | bool firstPartyStorageAccessGranted = false; |
2176 | 0 | nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aChannel); |
2177 | 0 | if (httpChannel) { |
2178 | 0 | isTrackingResource = httpChannel->GetIsTrackingResource(); |
2179 | 0 |
|
2180 | 0 | // Check first-party storage access even for non-tracking resources, since |
2181 | 0 | // we will need the result when computing the access rights for the reject |
2182 | 0 | // foreign cookie behavior mode. |
2183 | 0 | if (isForeign && |
2184 | 0 | AntiTrackingCommon::IsFirstPartyStorageAccessGrantedFor(httpChannel, |
2185 | 0 | aHostURI, |
2186 | 0 | nullptr)) { |
2187 | 0 | firstPartyStorageAccessGranted = true; |
2188 | 0 | } |
2189 | 0 | } |
2190 | 0 |
|
2191 | 0 | OriginAttributes attrs; |
2192 | 0 | if (aChannel) { |
2193 | 0 | NS_GetOriginAttributes(aChannel, attrs); |
2194 | 0 | } |
2195 | 0 |
|
2196 | 0 | nsDependentCString cookieString(aCookieHeader); |
2197 | 0 | nsDependentCString serverTime(aServerTime ? aServerTime : ""); |
2198 | 0 | SetCookieStringInternal(aHostURI, isForeign, isTrackingResource, |
2199 | 0 | firstPartyStorageAccessGranted, cookieString, |
2200 | 0 | serverTime, aFromHttp, attrs, aChannel); |
2201 | 0 | return NS_OK; |
2202 | 0 | } |
2203 | | |
2204 | | void |
2205 | | nsCookieService::SetCookieStringInternal(nsIURI *aHostURI, |
2206 | | bool aIsForeign, |
2207 | | bool aIsTrackingResource, |
2208 | | bool aFirstPartyStorageAccessGranted, |
2209 | | nsDependentCString &aCookieHeader, |
2210 | | const nsCString &aServerTime, |
2211 | | bool aFromHttp, |
2212 | | const OriginAttributes &aOriginAttrs, |
2213 | | nsIChannel *aChannel) |
2214 | 0 | { |
2215 | 0 | NS_ASSERTION(aHostURI, "null host!"); |
2216 | 0 |
|
2217 | 0 | if (!mDBState) { |
2218 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2219 | 0 | return; |
2220 | 0 | } |
2221 | 0 |
|
2222 | 0 | EnsureReadComplete(true); |
2223 | 0 |
|
2224 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
2225 | 0 | mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState; |
2226 | 0 |
|
2227 | 0 | // get the base domain for the host URI. |
2228 | 0 | // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". |
2229 | 0 | // file:// URI's (i.e. with an empty host) are allowed, but any other |
2230 | 0 | // scheme must have a non-empty host. A trailing dot in the host |
2231 | 0 | // is acceptable. |
2232 | 0 | bool requireHostMatch; |
2233 | 0 | nsAutoCString baseDomain; |
2234 | 0 | nsresult rv = GetBaseDomain(mTLDService, aHostURI, baseDomain, requireHostMatch); |
2235 | 0 | if (NS_FAILED(rv)) { |
2236 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
2237 | 0 | "couldn't get base domain from URI"); |
2238 | 0 | return; |
2239 | 0 | } |
2240 | 0 |
|
2241 | 0 | nsCookieKey key(baseDomain, aOriginAttrs); |
2242 | 0 |
|
2243 | 0 | // check default prefs |
2244 | 0 | uint32_t priorCookieCount = 0; |
2245 | 0 | uint32_t rejectedReason = 0; |
2246 | 0 | nsAutoCString hostFromURI; |
2247 | 0 | aHostURI->GetHost(hostFromURI); |
2248 | 0 | CountCookiesFromHost(hostFromURI, &priorCookieCount); |
2249 | 0 | CookieStatus cookieStatus = CheckPrefs(mPermissionService, mCookieBehavior, |
2250 | 0 | mThirdPartySession, |
2251 | 0 | mThirdPartyNonsecureSession, aHostURI, |
2252 | 0 | aIsForeign, aIsTrackingResource, |
2253 | 0 | aFirstPartyStorageAccessGranted, |
2254 | 0 | aCookieHeader.get(), priorCookieCount, |
2255 | 0 | aOriginAttrs, &rejectedReason); |
2256 | 0 |
|
2257 | 0 | MOZ_ASSERT_IF(rejectedReason, cookieStatus == STATUS_REJECTED); |
2258 | 0 |
|
2259 | 0 | // fire a notification if third party or if cookie was rejected |
2260 | 0 | // (but not if there was an error) |
2261 | 0 | switch (cookieStatus) { |
2262 | 0 | case STATUS_REJECTED: |
2263 | 0 | NotifyRejected(aHostURI, aChannel, rejectedReason); |
2264 | 0 | if (aIsForeign) { |
2265 | 0 | NotifyThirdParty(aHostURI, false, aChannel); |
2266 | 0 | } |
2267 | 0 | return; // Stop here |
2268 | 0 | case STATUS_REJECTED_WITH_ERROR: |
2269 | 0 | return; |
2270 | 0 | case STATUS_ACCEPTED: // Fallthrough |
2271 | 0 | case STATUS_ACCEPT_SESSION: |
2272 | 0 | if (aIsForeign) { |
2273 | 0 | NotifyThirdParty(aHostURI, true, aChannel); |
2274 | 0 | } |
2275 | 0 | break; |
2276 | 0 | default: |
2277 | 0 | break; |
2278 | 0 | } |
2279 | 0 | |
2280 | 0 | int64_t serverTime = ParseServerTime(aServerTime); |
2281 | 0 |
|
2282 | 0 | // process each cookie in the header |
2283 | 0 | while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus, |
2284 | 0 | aCookieHeader, serverTime, aFromHttp, aChannel)) { |
2285 | 0 | // document.cookie can only set one cookie at a time |
2286 | 0 | if (!aFromHttp) |
2287 | 0 | break; |
2288 | 0 | } |
2289 | 0 | } |
2290 | | |
2291 | | // notify observers that a cookie was rejected due to the users' prefs. |
2292 | | void |
2293 | | nsCookieService::NotifyRejected(nsIURI *aHostURI, nsIChannel* aChannel, |
2294 | | uint32_t aRejectedReason) |
2295 | 0 | { |
2296 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
2297 | 0 | if (os) { |
2298 | 0 | os->NotifyObservers(aHostURI, "cookie-rejected", nullptr); |
2299 | 0 | } |
2300 | 0 |
|
2301 | 0 | AntiTrackingCommon::NotifyRejection(aChannel, aRejectedReason); |
2302 | 0 | } |
2303 | | |
2304 | | // notify observers that a third-party cookie was accepted/rejected |
2305 | | // if the cookie issuer is unknown, it defaults to "?" |
2306 | | void |
2307 | | nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel) |
2308 | 0 | { |
2309 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
2310 | 0 | if (!os) { |
2311 | 0 | return; |
2312 | 0 | } |
2313 | 0 | |
2314 | 0 | const char* topic; |
2315 | 0 |
|
2316 | 0 | if (mDBState != mPrivateDBState) { |
2317 | 0 | // Regular (non-private) browsing |
2318 | 0 | if (aIsAccepted) { |
2319 | 0 | topic = "third-party-cookie-accepted"; |
2320 | 0 | } else { |
2321 | 0 | topic = "third-party-cookie-rejected"; |
2322 | 0 | } |
2323 | 0 | } else { |
2324 | 0 | // Private browsing |
2325 | 0 | if (aIsAccepted) { |
2326 | 0 | topic = "private-third-party-cookie-accepted"; |
2327 | 0 | } else { |
2328 | 0 | topic = "private-third-party-cookie-rejected"; |
2329 | 0 | } |
2330 | 0 | } |
2331 | 0 |
|
2332 | 0 | do { |
2333 | 0 | // Attempt to find the host of aChannel. |
2334 | 0 | if (!aChannel) { |
2335 | 0 | break; |
2336 | 0 | } |
2337 | 0 | nsCOMPtr<nsIURI> channelURI; |
2338 | 0 | nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI)); |
2339 | 0 | if (NS_FAILED(rv)) { |
2340 | 0 | break; |
2341 | 0 | } |
2342 | 0 | |
2343 | 0 | nsAutoCString referringHost; |
2344 | 0 | rv = channelURI->GetHost(referringHost); |
2345 | 0 | if (NS_FAILED(rv)) { |
2346 | 0 | break; |
2347 | 0 | } |
2348 | 0 | |
2349 | 0 | nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost); |
2350 | 0 | os->NotifyObservers(aHostURI, topic, referringHostUTF16.get()); |
2351 | 0 | return; |
2352 | 0 | } while (false); |
2353 | 0 |
|
2354 | 0 | // This can fail for a number of reasons, in which kind we fallback to "?" |
2355 | 0 | os->NotifyObservers(aHostURI, topic, u"?"); |
2356 | 0 | } |
2357 | | |
2358 | | // notify observers that the cookie list changed. there are five possible |
2359 | | // values for aData: |
2360 | | // "deleted" means a cookie was deleted. aSubject is the deleted cookie. |
2361 | | // "added" means a cookie was added. aSubject is the added cookie. |
2362 | | // "changed" means a cookie was altered. aSubject is the new cookie. |
2363 | | // "cleared" means the entire cookie list was cleared. aSubject is null. |
2364 | | // "batch-deleted" means a set of cookies was purged. aSubject is the list of |
2365 | | // cookies. |
2366 | | void |
2367 | | nsCookieService::NotifyChanged(nsISupports *aSubject, |
2368 | | const char16_t *aData, |
2369 | | bool aOldCookieIsSession, |
2370 | | bool aFromHttp) |
2371 | 0 | { |
2372 | 0 | const char* topic = mDBState == mPrivateDBState ? |
2373 | 0 | "private-cookie-changed" : "cookie-changed"; |
2374 | 0 | nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); |
2375 | 0 | if (!os) { |
2376 | 0 | return; |
2377 | 0 | } |
2378 | 0 | // Notify for topic "private-cookie-changed" or "cookie-changed" |
2379 | 0 | os->NotifyObservers(aSubject, topic, aData); |
2380 | 0 |
|
2381 | 0 | // Notify for topic "session-cookie-changed" to update the copy of session |
2382 | 0 | // cookies in session restore component. |
2383 | 0 | // Ignore private session cookies since they will not be restored. |
2384 | 0 | if (mDBState == mPrivateDBState) { |
2385 | 0 | return; |
2386 | 0 | } |
2387 | 0 | // Filter out notifications for individual non-session cookies. |
2388 | 0 | if (NS_LITERAL_STRING("changed").Equals(aData) || |
2389 | 0 | NS_LITERAL_STRING("deleted").Equals(aData) || |
2390 | 0 | NS_LITERAL_STRING("added").Equals(aData)) { |
2391 | 0 | nsCOMPtr<nsICookie> xpcCookie = do_QueryInterface(aSubject); |
2392 | 0 | MOZ_ASSERT(xpcCookie); |
2393 | 0 | auto cookie = static_cast<nsCookie*>(xpcCookie.get()); |
2394 | 0 | if (!cookie->IsSession() && !aOldCookieIsSession) { |
2395 | 0 | return; |
2396 | 0 | } |
2397 | 0 | } |
2398 | 0 | os->NotifyObservers(aSubject, "session-cookie-changed", aData); |
2399 | 0 | } |
2400 | | |
2401 | | already_AddRefed<nsIArray> |
2402 | | nsCookieService::CreatePurgeList(nsICookie2* aCookie) |
2403 | 0 | { |
2404 | 0 | nsCOMPtr<nsIMutableArray> removedList = |
2405 | 0 | do_CreateInstance(NS_ARRAY_CONTRACTID); |
2406 | 0 | removedList->AppendElement(aCookie); |
2407 | 0 | return removedList.forget(); |
2408 | 0 | } |
2409 | | |
2410 | | void |
2411 | | nsCookieService::CreateOrUpdatePurgeList(nsIArray** aPurgedList, nsICookie2* aCookie) |
2412 | 0 | { |
2413 | 0 | if (!*aPurgedList) { |
2414 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, ("Creating new purge list")); |
2415 | 0 | nsCOMPtr<nsIArray> purgedList = CreatePurgeList(aCookie); |
2416 | 0 | purgedList.forget(aPurgedList); |
2417 | 0 | return; |
2418 | 0 | } |
2419 | 0 |
|
2420 | 0 | nsCOMPtr<nsIMutableArray> purgedList = do_QueryInterface(*aPurgedList); |
2421 | 0 | if (purgedList) { |
2422 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, ("Updating existing purge list")); |
2423 | 0 | purgedList->AppendElement(aCookie); |
2424 | 0 | } else { |
2425 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, ("Could not QI aPurgedList!")); |
2426 | 0 | } |
2427 | 0 | } |
2428 | | |
2429 | | /****************************************************************************** |
2430 | | * nsCookieService: |
2431 | | * public transaction helper impl |
2432 | | ******************************************************************************/ |
2433 | | |
2434 | | NS_IMETHODIMP |
2435 | | nsCookieService::RunInTransaction(nsICookieTransactionCallback* aCallback) |
2436 | 0 | { |
2437 | 0 | NS_ENSURE_ARG(aCallback); |
2438 | 0 | if (!mDBState) { |
2439 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2440 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2441 | 0 | } |
2442 | 0 |
|
2443 | 0 | EnsureReadComplete(true); |
2444 | 0 |
|
2445 | 0 | if (NS_WARN_IF(!mDefaultDBState->dbConn)) { |
2446 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2447 | 0 | } |
2448 | 0 | mozStorageTransaction transaction(mDefaultDBState->dbConn, true); |
2449 | 0 |
|
2450 | 0 | if (NS_FAILED(aCallback->Callback())) { |
2451 | 0 | Unused << transaction.Rollback(); |
2452 | 0 | return NS_ERROR_FAILURE; |
2453 | 0 | } |
2454 | 0 | return NS_OK; |
2455 | 0 | } |
2456 | | |
2457 | | /****************************************************************************** |
2458 | | * nsCookieService: |
2459 | | * pref observer impl |
2460 | | ******************************************************************************/ |
2461 | | |
2462 | | void |
2463 | | nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch) |
2464 | 0 | { |
2465 | 0 | int32_t val; |
2466 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val))) |
2467 | 0 | mCookieBehavior = (uint8_t) LIMIT(val, 0, nsICookieService::BEHAVIOR_LAST, 0); |
2468 | 0 |
|
2469 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) |
2470 | 0 | mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies); |
2471 | 0 |
|
2472 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieQuotaPerHost, &val))) { |
2473 | 0 | mCookieQuotaPerHost = |
2474 | 0 | (uint16_t) LIMIT(val, 1, mMaxCookiesPerHost - 1, kCookieQuotaPerHost); |
2475 | 0 | } |
2476 | 0 |
|
2477 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) { |
2478 | 0 | mMaxCookiesPerHost = |
2479 | 0 | (uint16_t) LIMIT(val, mCookieQuotaPerHost + 1, 0xFFFF, kMaxCookiesPerHost); |
2480 | 0 | } |
2481 | 0 |
|
2482 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) { |
2483 | 0 | mCookiePurgeAge = |
2484 | 0 | int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC; |
2485 | 0 | } |
2486 | 0 |
|
2487 | 0 | bool boolval; |
2488 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval))) |
2489 | 0 | mThirdPartySession = boolval; |
2490 | 0 |
|
2491 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartyNonsecureSession, &boolval))) |
2492 | 0 | mThirdPartyNonsecureSession = boolval; |
2493 | 0 |
|
2494 | 0 | if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kCookieLeaveSecurityAlone, &boolval))) |
2495 | 0 | mLeaveSecureAlone = boolval; |
2496 | 0 | } |
2497 | | |
2498 | | /****************************************************************************** |
2499 | | * nsICookieManager impl: |
2500 | | * nsICookieManager |
2501 | | ******************************************************************************/ |
2502 | | |
2503 | | NS_IMETHODIMP |
2504 | | nsCookieService::RemoveAll() |
2505 | 0 | { |
2506 | 0 | if (!mDBState) { |
2507 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2508 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2509 | 0 | } |
2510 | 0 |
|
2511 | 0 | EnsureReadComplete(true); |
2512 | 0 |
|
2513 | 0 | RemoveAllFromMemory(); |
2514 | 0 |
|
2515 | 0 | // clear the cookie file |
2516 | 0 | if (mDBState->dbConn) { |
2517 | 0 | NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state"); |
2518 | 0 |
|
2519 | 0 | nsCOMPtr<mozIStorageAsyncStatement> stmt; |
2520 | 0 | nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
2521 | 0 | "DELETE FROM moz_cookies"), getter_AddRefs(stmt)); |
2522 | 0 | if (NS_SUCCEEDED(rv)) { |
2523 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
2524 | 0 | rv = stmt->ExecuteAsync(mDefaultDBState->removeListener, |
2525 | 0 | getter_AddRefs(handle)); |
2526 | 0 | NS_ASSERT_SUCCESS(rv); |
2527 | 0 | } else { |
2528 | 0 | // Recreate the database. |
2529 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
2530 | 0 | ("RemoveAll(): corruption detected with rv 0x%" PRIx32, static_cast<uint32_t>(rv))); |
2531 | 0 | HandleCorruptDB(mDefaultDBState); |
2532 | 0 | } |
2533 | 0 | } |
2534 | 0 |
|
2535 | 0 | NotifyChanged(nullptr, u"cleared"); |
2536 | 0 | return NS_OK; |
2537 | 0 | } |
2538 | | |
2539 | | NS_IMETHODIMP |
2540 | | nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator) |
2541 | 0 | { |
2542 | 0 | if (!mDBState) { |
2543 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2544 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2545 | 0 | } |
2546 | 0 |
|
2547 | 0 | EnsureReadComplete(true); |
2548 | 0 |
|
2549 | 0 | nsCOMArray<nsICookie> cookieList(mDBState->cookieCount); |
2550 | 0 | for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) { |
2551 | 0 | const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies(); |
2552 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
2553 | 0 | cookieList.AppendObject(cookies[i]); |
2554 | 0 | } |
2555 | 0 | } |
2556 | 0 |
|
2557 | 0 | return NS_NewArrayEnumerator(aEnumerator, cookieList, NS_GET_IID(nsICookie2)); |
2558 | 0 | } |
2559 | | |
2560 | | NS_IMETHODIMP |
2561 | | nsCookieService::GetSessionEnumerator(nsISimpleEnumerator **aEnumerator) |
2562 | 0 | { |
2563 | 0 | if (!mDBState) { |
2564 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2565 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2566 | 0 | } |
2567 | 0 |
|
2568 | 0 | EnsureReadComplete(true); |
2569 | 0 |
|
2570 | 0 | nsCOMArray<nsICookie> cookieList(mDBState->cookieCount); |
2571 | 0 | for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) { |
2572 | 0 | const nsCookieEntry::ArrayType& cookies = iter.Get()->GetCookies(); |
2573 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
2574 | 0 | nsCookie* cookie = cookies[i]; |
2575 | 0 | // Filter out non-session cookies. |
2576 | 0 | if (cookie->IsSession()) { |
2577 | 0 | cookieList.AppendObject(cookie); |
2578 | 0 | } |
2579 | 0 | } |
2580 | 0 | } |
2581 | 0 |
|
2582 | 0 | return NS_NewArrayEnumerator(aEnumerator, cookieList, NS_GET_IID(nsICookie2)); |
2583 | 0 | } |
2584 | | |
2585 | | NS_IMETHODIMP |
2586 | | nsCookieService::Add(const nsACString &aHost, |
2587 | | const nsACString &aPath, |
2588 | | const nsACString &aName, |
2589 | | const nsACString &aValue, |
2590 | | bool aIsSecure, |
2591 | | bool aIsHttpOnly, |
2592 | | bool aIsSession, |
2593 | | int64_t aExpiry, |
2594 | | JS::HandleValue aOriginAttributes, |
2595 | | int32_t aSameSite, |
2596 | | JSContext* aCx) |
2597 | 0 | { |
2598 | 0 | OriginAttributes attrs; |
2599 | 0 |
|
2600 | 0 | if (!aOriginAttributes.isObject() || |
2601 | 0 | !attrs.Init(aCx, aOriginAttributes)) { |
2602 | 0 | return NS_ERROR_INVALID_ARG; |
2603 | 0 | } |
2604 | 0 | |
2605 | 0 | return AddNative(aHost, aPath, aName, aValue, aIsSecure, aIsHttpOnly, |
2606 | 0 | aIsSession, aExpiry, &attrs, aSameSite); |
2607 | 0 | } |
2608 | | |
2609 | | NS_IMETHODIMP_(nsresult) |
2610 | | nsCookieService::AddNative(const nsACString &aHost, |
2611 | | const nsACString &aPath, |
2612 | | const nsACString &aName, |
2613 | | const nsACString &aValue, |
2614 | | bool aIsSecure, |
2615 | | bool aIsHttpOnly, |
2616 | | bool aIsSession, |
2617 | | int64_t aExpiry, |
2618 | | OriginAttributes* aOriginAttributes, |
2619 | | int32_t aSameSite) |
2620 | 0 | { |
2621 | 0 | if (NS_WARN_IF(!aOriginAttributes)) { |
2622 | 0 | return NS_ERROR_FAILURE; |
2623 | 0 | } |
2624 | 0 | |
2625 | 0 | if (!mDBState) { |
2626 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2627 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2628 | 0 | } |
2629 | 0 |
|
2630 | 0 | EnsureReadComplete(true); |
2631 | 0 |
|
2632 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
2633 | 0 | mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState; |
2634 | 0 |
|
2635 | 0 | // first, normalize the hostname, and fail if it contains illegal characters. |
2636 | 0 | nsAutoCString host(aHost); |
2637 | 0 | nsresult rv = NormalizeHost(host); |
2638 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2639 | 0 |
|
2640 | 0 | // get the base domain for the host URI. |
2641 | 0 | // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". |
2642 | 0 | nsAutoCString baseDomain; |
2643 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
2644 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2645 | 0 |
|
2646 | 0 | int64_t currentTimeInUsec = PR_Now(); |
2647 | 0 | nsCookieKey key = nsCookieKey(baseDomain, *aOriginAttributes); |
2648 | 0 |
|
2649 | 0 | RefPtr<nsCookie> cookie = |
2650 | 0 | nsCookie::Create(aName, aValue, host, aPath, |
2651 | 0 | aExpiry, |
2652 | 0 | currentTimeInUsec, |
2653 | 0 | nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), |
2654 | 0 | aIsSession, |
2655 | 0 | aIsSecure, |
2656 | 0 | aIsHttpOnly, |
2657 | 0 | key.mOriginAttributes, |
2658 | 0 | aSameSite); |
2659 | 0 | if (!cookie) { |
2660 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
2661 | 0 | } |
2662 | 0 | |
2663 | 0 | AddInternal(key, cookie, currentTimeInUsec, nullptr, nullptr, true); |
2664 | 0 | return NS_OK; |
2665 | 0 | } |
2666 | | |
2667 | | |
2668 | | nsresult |
2669 | | nsCookieService::Remove(const nsACString& aHost, const OriginAttributes& aAttrs, |
2670 | | const nsACString& aName, const nsACString& aPath, |
2671 | | bool aBlocked) |
2672 | 0 | { |
2673 | 0 | if (!mDBState) { |
2674 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2675 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2676 | 0 | } |
2677 | 0 |
|
2678 | 0 | EnsureReadComplete(true); |
2679 | 0 |
|
2680 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
2681 | 0 | mDBState = (aAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState; |
2682 | 0 |
|
2683 | 0 | // first, normalize the hostname, and fail if it contains illegal characters. |
2684 | 0 | nsAutoCString host(aHost); |
2685 | 0 | nsresult rv = NormalizeHost(host); |
2686 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2687 | 0 |
|
2688 | 0 | nsAutoCString baseDomain; |
2689 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
2690 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
2691 | 0 |
|
2692 | 0 | nsListIter matchIter; |
2693 | 0 | RefPtr<nsCookie> cookie; |
2694 | 0 | if (FindCookie(nsCookieKey(baseDomain, aAttrs), |
2695 | 0 | host, |
2696 | 0 | PromiseFlatCString(aName), |
2697 | 0 | PromiseFlatCString(aPath), |
2698 | 0 | matchIter)) { |
2699 | 0 | cookie = matchIter.Cookie(); |
2700 | 0 | RemoveCookieFromList(matchIter); |
2701 | 0 | } |
2702 | 0 |
|
2703 | 0 | // check if we need to add the host to the permissions blacklist. |
2704 | 0 | if (aBlocked && mPermissionService) { |
2705 | 0 | // strip off the domain dot, if necessary |
2706 | 0 | if (!host.IsEmpty() && host.First() == '.') |
2707 | 0 | host.Cut(0, 1); |
2708 | 0 |
|
2709 | 0 | host.InsertLiteral("http://", 0); |
2710 | 0 |
|
2711 | 0 | nsCOMPtr<nsIURI> uri; |
2712 | 0 | NS_NewURI(getter_AddRefs(uri), host); |
2713 | 0 |
|
2714 | 0 | if (uri) |
2715 | 0 | mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY); |
2716 | 0 | } |
2717 | 0 |
|
2718 | 0 | if (cookie) { |
2719 | 0 | // Everything's done. Notify observers. |
2720 | 0 | NotifyChanged(cookie, u"deleted"); |
2721 | 0 | } |
2722 | 0 |
|
2723 | 0 | return NS_OK; |
2724 | 0 | } |
2725 | | |
2726 | | NS_IMETHODIMP |
2727 | | nsCookieService::Remove(const nsACString &aHost, |
2728 | | const nsACString &aName, |
2729 | | const nsACString &aPath, |
2730 | | bool aBlocked, |
2731 | | JS::HandleValue aOriginAttributes, |
2732 | | JSContext* aCx) |
2733 | 0 | { |
2734 | 0 | OriginAttributes attrs; |
2735 | 0 |
|
2736 | 0 | if (!aOriginAttributes.isObject() || |
2737 | 0 | !attrs.Init(aCx, aOriginAttributes)) { |
2738 | 0 | return NS_ERROR_INVALID_ARG; |
2739 | 0 | } |
2740 | 0 | |
2741 | 0 | return RemoveNative(aHost, aName, aPath, aBlocked, &attrs); |
2742 | 0 | } |
2743 | | |
2744 | | NS_IMETHODIMP_(nsresult) |
2745 | | nsCookieService::RemoveNative(const nsACString &aHost, |
2746 | | const nsACString &aName, |
2747 | | const nsACString &aPath, |
2748 | | bool aBlocked, |
2749 | | OriginAttributes* aOriginAttributes) |
2750 | 0 | { |
2751 | 0 | if (NS_WARN_IF(!aOriginAttributes)) { |
2752 | 0 | return NS_ERROR_FAILURE; |
2753 | 0 | } |
2754 | 0 | |
2755 | 0 | nsresult rv = Remove(aHost, *aOriginAttributes, aName, aPath, aBlocked); |
2756 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2757 | 0 | return rv; |
2758 | 0 | } |
2759 | 0 | |
2760 | 0 | return NS_OK; |
2761 | 0 | } |
2762 | | |
2763 | | /****************************************************************************** |
2764 | | * nsCookieService impl: |
2765 | | * private file I/O functions |
2766 | | ******************************************************************************/ |
2767 | | |
2768 | | // Extract data from a single result row and create an nsCookie. |
2769 | | mozilla::UniquePtr<ConstCookie> |
2770 | | nsCookieService::GetCookieFromRow(mozIStorageStatement *aRow, |
2771 | | const OriginAttributes &aOriginAttributes) |
2772 | 0 | { |
2773 | 0 | // Skip reading 'baseDomain' -- up to the caller. |
2774 | 0 | nsCString name, value, host, path; |
2775 | 0 | DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name); |
2776 | 0 | NS_ASSERT_SUCCESS(rv); |
2777 | 0 | rv = aRow->GetUTF8String(IDX_VALUE, value); |
2778 | 0 | NS_ASSERT_SUCCESS(rv); |
2779 | 0 | rv = aRow->GetUTF8String(IDX_HOST, host); |
2780 | 0 | NS_ASSERT_SUCCESS(rv); |
2781 | 0 | rv = aRow->GetUTF8String(IDX_PATH, path); |
2782 | 0 | NS_ASSERT_SUCCESS(rv); |
2783 | 0 |
|
2784 | 0 | int64_t expiry = aRow->AsInt64(IDX_EXPIRY); |
2785 | 0 | int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED); |
2786 | 0 | int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME); |
2787 | 0 | bool isSecure = 0 != aRow->AsInt32(IDX_SECURE); |
2788 | 0 | bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY); |
2789 | 0 | int32_t sameSite = aRow->AsInt32(IDX_SAME_SITE); |
2790 | 0 |
|
2791 | 0 | // Create a new constCookie and assign the data. |
2792 | 0 | return mozilla::MakeUnique<ConstCookie>(name, |
2793 | 0 | value, |
2794 | 0 | host, |
2795 | 0 | path, |
2796 | 0 | expiry, |
2797 | 0 | lastAccessed, |
2798 | 0 | creationTime, |
2799 | 0 | isSecure, |
2800 | 0 | isHttpOnly, |
2801 | 0 | aOriginAttributes, |
2802 | 0 | sameSite); |
2803 | 0 | } |
2804 | | |
2805 | | void |
2806 | | nsCookieService::EnsureReadComplete(bool aInitDBConn) |
2807 | 0 | { |
2808 | 0 | MOZ_ASSERT(NS_IsMainThread()); |
2809 | 0 |
|
2810 | 0 | bool isAccumulated = false; |
2811 | 0 |
|
2812 | 0 | if (!mInitializedDBStates) { |
2813 | 0 | TimeStamp startBlockTime = TimeStamp::Now(); |
2814 | 0 | MonitorAutoLock lock(mMonitor); |
2815 | 0 |
|
2816 | 0 | while (!mInitializedDBStates) { |
2817 | 0 | mMonitor.Wait(); |
2818 | 0 | } |
2819 | 0 | Telemetry::AccumulateTimeDelta(Telemetry::MOZ_SQLITE_COOKIES_BLOCK_MAIN_THREAD_MS_V2, |
2820 | 0 | startBlockTime); |
2821 | 0 | Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0); |
2822 | 0 | isAccumulated = true; |
2823 | 0 | } else if (!mEndInitDBConn.IsNull()) { |
2824 | 0 | // We didn't block main thread, and here comes the first cookie request. |
2825 | 0 | // Collect how close we're going to block main thread. |
2826 | 0 | Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, |
2827 | 0 | (TimeStamp::Now() - mEndInitDBConn).ToMilliseconds()); |
2828 | 0 | // Nullify the timestamp so wo don't accumulate this telemetry probe again. |
2829 | 0 | mEndInitDBConn = TimeStamp(); |
2830 | 0 | isAccumulated = true; |
2831 | 0 | } else if (!mInitializedDBConn && aInitDBConn) { |
2832 | 0 | // A request comes while we finished cookie thread task and InitDBConn is |
2833 | 0 | // on the way from cookie thread to main thread. We're very close to block |
2834 | 0 | // main thread. |
2835 | 0 | Telemetry::Accumulate(Telemetry::MOZ_SQLITE_COOKIES_TIME_TO_BLOCK_MAIN_THREAD_MS, 0); |
2836 | 0 | isAccumulated = true; |
2837 | 0 | } |
2838 | 0 |
|
2839 | 0 | if (!mInitializedDBConn && aInitDBConn && mDefaultDBState) { |
2840 | 0 | InitDBConn(); |
2841 | 0 | if (isAccumulated) { |
2842 | 0 | // Nullify the timestamp so wo don't accumulate this telemetry probe again. |
2843 | 0 | mEndInitDBConn = TimeStamp(); |
2844 | 0 | } |
2845 | 0 | } |
2846 | 0 | } |
2847 | | |
2848 | | OpenDBResult |
2849 | | nsCookieService::Read() |
2850 | 0 | { |
2851 | 0 | MOZ_ASSERT(NS_GetCurrentThread() == mThread); |
2852 | 0 |
|
2853 | 0 | // Set up a statement to delete any rows with a nullptr 'baseDomain' |
2854 | 0 | // column. This takes care of any cookies set by browsers that don't |
2855 | 0 | // understand the 'baseDomain' column, where the database schema version |
2856 | 0 | // is from one that does. (This would occur when downgrading.) |
2857 | 0 | nsresult rv = mDefaultDBState->syncConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
2858 | 0 | "DELETE FROM moz_cookies WHERE baseDomain ISNULL")); |
2859 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
2860 | 0 |
|
2861 | 0 | // Read in the data synchronously. |
2862 | 0 | // see IDX_NAME, etc. for parameter indexes |
2863 | 0 | nsCOMPtr<mozIStorageStatement> stmt; |
2864 | 0 | rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
2865 | 0 | "SELECT " |
2866 | 0 | "name, " |
2867 | 0 | "value, " |
2868 | 0 | "host, " |
2869 | 0 | "path, " |
2870 | 0 | "expiry, " |
2871 | 0 | "lastAccessed, " |
2872 | 0 | "creationTime, " |
2873 | 0 | "isSecure, " |
2874 | 0 | "isHttpOnly, " |
2875 | 0 | "baseDomain, " |
2876 | 0 | "originAttributes, " |
2877 | 0 | "sameSite " |
2878 | 0 | "FROM moz_cookies " |
2879 | 0 | "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt)); |
2880 | 0 |
|
2881 | 0 | NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
2882 | 0 |
|
2883 | 0 | if (NS_WARN_IF(!mReadArray.IsEmpty())) { |
2884 | 0 | mReadArray.Clear(); |
2885 | 0 | } |
2886 | 0 | mReadArray.SetCapacity(kMaxNumberOfCookies); |
2887 | 0 |
|
2888 | 0 | nsCString baseDomain, name, value, host, path; |
2889 | 0 | bool hasResult; |
2890 | 0 | while (true) { |
2891 | 0 | rv = stmt->ExecuteStep(&hasResult); |
2892 | 0 | if (NS_WARN_IF(NS_FAILED(rv))) { |
2893 | 0 | mReadArray.Clear(); |
2894 | 0 | return RESULT_RETRY; |
2895 | 0 | } |
2896 | 0 | |
2897 | 0 | if (!hasResult) |
2898 | 0 | break; |
2899 | 0 | |
2900 | 0 | // Make sure we haven't already read the data. |
2901 | 0 | stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain); |
2902 | 0 |
|
2903 | 0 | nsAutoCString suffix; |
2904 | 0 | OriginAttributes attrs; |
2905 | 0 | stmt->GetUTF8String(IDX_ORIGIN_ATTRIBUTES, suffix); |
2906 | 0 | // If PopulateFromSuffix failed we just ignore the OA attributes |
2907 | 0 | // that we don't support |
2908 | 0 | Unused << attrs.PopulateFromSuffix(suffix); |
2909 | 0 |
|
2910 | 0 | nsCookieKey key(baseDomain, attrs); |
2911 | 0 | CookieDomainTuple* tuple = mReadArray.AppendElement(); |
2912 | 0 | tuple->key = std::move(key); |
2913 | 0 | tuple->cookie = GetCookieFromRow(stmt, attrs); |
2914 | 0 | } |
2915 | 0 |
|
2916 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, ("Read(): %zu cookies read", mReadArray.Length())); |
2917 | 0 |
|
2918 | 0 | return RESULT_OK; |
2919 | 0 | } |
2920 | | |
2921 | | NS_IMETHODIMP |
2922 | | nsCookieService::ImportCookies(nsIFile *aCookieFile) |
2923 | 0 | { |
2924 | 0 | if (!mDBState) { |
2925 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
2926 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2927 | 0 | } |
2928 | 0 |
|
2929 | 0 | EnsureReadComplete(true); |
2930 | 0 |
|
2931 | 0 | // Make sure we're in the default DB state. We don't want people importing |
2932 | 0 | // cookies into a private browsing session! |
2933 | 0 | if (mDBState != mDefaultDBState) { |
2934 | 0 | NS_WARNING("Trying to import cookies in a private browsing session!"); |
2935 | 0 | return NS_ERROR_NOT_AVAILABLE; |
2936 | 0 | } |
2937 | 0 |
|
2938 | 0 | nsresult rv; |
2939 | 0 | nsCOMPtr<nsIInputStream> fileInputStream; |
2940 | 0 | rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile); |
2941 | 0 | if (NS_FAILED(rv)) return rv; |
2942 | 0 | |
2943 | 0 | nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv); |
2944 | 0 | if (NS_FAILED(rv)) return rv; |
2945 | 0 | |
2946 | 0 | static const char kTrue[] = "TRUE"; |
2947 | 0 |
|
2948 | 0 | nsAutoCString buffer, baseDomain; |
2949 | 0 | bool isMore = true; |
2950 | 0 | int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex; |
2951 | 0 | int32_t numInts; |
2952 | 0 | int64_t expires; |
2953 | 0 | bool isDomain, isHttpOnly = false; |
2954 | 0 | uint32_t originalCookieCount = mDefaultDBState->cookieCount; |
2955 | 0 |
|
2956 | 0 | int64_t currentTimeInUsec = PR_Now(); |
2957 | 0 | int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; |
2958 | 0 | // we use lastAccessedCounter to keep cookies in recently-used order, |
2959 | 0 | // so we start by initializing to currentTime (somewhat arbitrary) |
2960 | 0 | int64_t lastAccessedCounter = currentTimeInUsec; |
2961 | 0 |
|
2962 | 0 | /* file format is: |
2963 | 0 | * |
2964 | 0 | * host \t isDomain \t path \t secure \t expires \t name \t cookie |
2965 | 0 | * |
2966 | 0 | * if this format isn't respected we move onto the next line in the file. |
2967 | 0 | * isDomain is "TRUE" or "FALSE" (default to "FALSE") |
2968 | 0 | * isSecure is "TRUE" or "FALSE" (default to "TRUE") |
2969 | 0 | * expires is a int64_t integer |
2970 | 0 | * note 1: cookie can contain tabs. |
2971 | 0 | * note 2: cookies will be stored in order of lastAccessed time: |
2972 | 0 | * most-recently used come first; least-recently-used come last. |
2973 | 0 | */ |
2974 | 0 |
|
2975 | 0 | /* |
2976 | 0 | * ...but due to bug 178933, we hide HttpOnly cookies from older code |
2977 | 0 | * in a comment, so they don't expose HttpOnly cookies to JS. |
2978 | 0 | * |
2979 | 0 | * The format for HttpOnly cookies is |
2980 | 0 | * |
2981 | 0 | * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie |
2982 | 0 | * |
2983 | 0 | */ |
2984 | 0 |
|
2985 | 0 | // We will likely be adding a bunch of cookies to the DB, so we use async |
2986 | 0 | // batching with storage to make this super fast. |
2987 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
2988 | 0 | if (originalCookieCount == 0 && mDefaultDBState->dbConn) { |
2989 | 0 | mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
2990 | 0 | } |
2991 | 0 |
|
2992 | 0 | while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) { |
2993 | 0 | if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(HTTP_ONLY_PREFIX))) { |
2994 | 0 | isHttpOnly = true; |
2995 | 0 | hostIndex = sizeof(HTTP_ONLY_PREFIX) - 1; |
2996 | 0 | } else if (buffer.IsEmpty() || buffer.First() == '#') { |
2997 | 0 | continue; |
2998 | 0 | } else { |
2999 | 0 | isHttpOnly = false; |
3000 | 0 | hostIndex = 0; |
3001 | 0 | } |
3002 | 0 |
|
3003 | 0 | // this is a cheap, cheesy way of parsing a tab-delimited line into |
3004 | 0 | // string indexes, which can be lopped off into substrings. just for |
3005 | 0 | // purposes of obfuscation, it also checks that each token was found. |
3006 | 0 | // todo: use iterators? |
3007 | 0 | if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 || |
3008 | 0 | (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 || |
3009 | 0 | (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 || |
3010 | 0 | (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 || |
3011 | 0 | (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 || |
3012 | 0 | (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) { |
3013 | 0 | continue; |
3014 | 0 | } |
3015 | 0 | |
3016 | 0 | // check the expirytime first - if it's expired, ignore |
3017 | 0 | // nullstomp the trailing tab, to avoid copying the string |
3018 | 0 | auto iter = buffer.BeginWriting() + nameIndex - 1; |
3019 | 0 | *iter = char(0); |
3020 | 0 | numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires); |
3021 | 0 | if (numInts != 1 || expires < currentTime) { |
3022 | 0 | continue; |
3023 | 0 | } |
3024 | 0 | |
3025 | 0 | isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue); |
3026 | 0 | const nsACString& host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1); |
3027 | 0 | // check for bad legacy cookies (domain not starting with a dot, or containing a port), |
3028 | 0 | // and discard |
3029 | 0 | if ((isDomain && !host.IsEmpty() && host.First() != '.') || |
3030 | 0 | host.Contains(':')) { |
3031 | 0 | continue; |
3032 | 0 | } |
3033 | 0 | |
3034 | 0 | // compute the baseDomain from the host |
3035 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
3036 | 0 | if (NS_FAILED(rv)) |
3037 | 0 | continue; |
3038 | 0 | |
3039 | 0 | // pre-existing cookies have inIsolatedMozBrowser=false set by default |
3040 | 0 | // constructor of OriginAttributes(). |
3041 | 0 | nsCookieKey key = DEFAULT_APP_KEY(baseDomain); |
3042 | 0 |
|
3043 | 0 | // Create a new nsCookie and assign the data. We don't know the cookie |
3044 | 0 | // creation time, so just use the current time to generate a unique one. |
3045 | 0 | RefPtr<nsCookie> newCookie = |
3046 | 0 | nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1), |
3047 | 0 | Substring(buffer, cookieIndex, buffer.Length() - cookieIndex), |
3048 | 0 | host, |
3049 | 0 | Substring(buffer, pathIndex, secureIndex - pathIndex - 1), |
3050 | 0 | expires, |
3051 | 0 | lastAccessedCounter, |
3052 | 0 | nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), |
3053 | 0 | false, |
3054 | 0 | Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue), |
3055 | 0 | isHttpOnly, |
3056 | 0 | key.mOriginAttributes, |
3057 | 0 | nsICookie2::SAMESITE_UNSET); |
3058 | 0 | if (!newCookie) { |
3059 | 0 | return NS_ERROR_OUT_OF_MEMORY; |
3060 | 0 | } |
3061 | 0 | |
3062 | 0 | // trick: preserve the most-recently-used cookie ordering, |
3063 | 0 | // by successively decrementing the lastAccessed time |
3064 | 0 | lastAccessedCounter--; |
3065 | 0 |
|
3066 | 0 | if (originalCookieCount == 0) { |
3067 | 0 | AddCookieToList(key, newCookie, mDefaultDBState, paramsArray); |
3068 | 0 | } |
3069 | 0 | else { |
3070 | 0 | AddInternal(key, newCookie, currentTimeInUsec, |
3071 | 0 | nullptr, nullptr, true); |
3072 | 0 | } |
3073 | 0 | } |
3074 | 0 |
|
3075 | 0 | // If we need to write to disk, do so now. |
3076 | 0 | if (paramsArray) { |
3077 | 0 | uint32_t length; |
3078 | 0 | paramsArray->GetLength(&length); |
3079 | 0 | if (length) { |
3080 | 0 | rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray); |
3081 | 0 | NS_ASSERT_SUCCESS(rv); |
3082 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
3083 | 0 | rv = mDefaultDBState->stmtInsert->ExecuteAsync( |
3084 | 0 | mDefaultDBState->insertListener, getter_AddRefs(handle)); |
3085 | 0 | NS_ASSERT_SUCCESS(rv); |
3086 | 0 | } |
3087 | 0 | } |
3088 | 0 |
|
3089 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, ("ImportCookies(): %" PRIu32 " cookies imported", |
3090 | 0 | mDefaultDBState->cookieCount)); |
3091 | 0 |
|
3092 | 0 | return NS_OK; |
3093 | 0 | } |
3094 | | |
3095 | | /****************************************************************************** |
3096 | | * nsCookieService impl: |
3097 | | * private GetCookie/SetCookie helpers |
3098 | | ******************************************************************************/ |
3099 | | |
3100 | | // helper function for GetCookieList |
3101 | 0 | static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; } |
3102 | | |
3103 | | bool |
3104 | | nsCookieService::DomainMatches(nsCookie* aCookie, |
3105 | | const nsACString& aHost) |
3106 | 0 | { |
3107 | 0 | // first, check for an exact host or domain cookie match, e.g. "google.com" |
3108 | 0 | // or ".google.com"; second a subdomain match, e.g. |
3109 | 0 | // host = "mail.google.com", cookie domain = ".google.com". |
3110 | 0 | return aCookie->RawHost() == aHost || |
3111 | 0 | (aCookie->IsDomain() && StringEndsWith(aHost, aCookie->Host())); |
3112 | 0 | } |
3113 | | |
3114 | | bool |
3115 | | nsCookieService::IsSameSiteEnabled() |
3116 | 0 | { |
3117 | 0 | static bool prefInitialized = false; |
3118 | 0 | if (!prefInitialized) { |
3119 | 0 | Preferences::AddBoolVarCache(&sSameSiteEnabled, |
3120 | 0 | "network.cookie.same-site.enabled", false); |
3121 | 0 | prefInitialized = true; |
3122 | 0 | } |
3123 | 0 | return sSameSiteEnabled; |
3124 | 0 | } |
3125 | | |
3126 | | bool |
3127 | | nsCookieService::PathMatches(nsCookie* aCookie, |
3128 | | const nsACString& aPath) |
3129 | 0 | { |
3130 | 0 | // calculate cookie path length, excluding trailing '/' |
3131 | 0 | uint32_t cookiePathLen = aCookie->Path().Length(); |
3132 | 0 | if (cookiePathLen > 0 && aCookie->Path().Last() == '/') |
3133 | 0 | --cookiePathLen; |
3134 | 0 |
|
3135 | 0 | // if the given path is shorter than the cookie path, it doesn't match |
3136 | 0 | // if the given path doesn't start with the cookie path, it doesn't match. |
3137 | 0 | if (!StringBeginsWith(aPath, Substring(aCookie->Path(), 0, cookiePathLen))) |
3138 | 0 | return false; |
3139 | 0 | |
3140 | 0 | // if the given path is longer than the cookie path, and the first char after |
3141 | 0 | // the cookie path is not a path delimiter, it doesn't match. |
3142 | 0 | if (aPath.Length() > cookiePathLen && |
3143 | 0 | !ispathdelimiter(aPath.CharAt(cookiePathLen))) { |
3144 | 0 | /* |
3145 | 0 | * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'. |
3146 | 0 | * '/' is the "standard" case; the '?' test allows a site at host/abc?def |
3147 | 0 | * to receive a cookie that has a path attribute of abc. this seems |
3148 | 0 | * strange but at least one major site (citibank, bug 156725) depends |
3149 | 0 | * on it. The test for # and ; are put in to proactively avoid problems |
3150 | 0 | * with other sites - these are the only other chars allowed in the path. |
3151 | 0 | */ |
3152 | 0 | return false; |
3153 | 0 | } |
3154 | 0 | |
3155 | 0 | // either the paths match exactly, or the cookie path is a prefix of |
3156 | 0 | // the given path. |
3157 | 0 | return true; |
3158 | 0 | } |
3159 | | |
3160 | | void |
3161 | | nsCookieService::GetCookiesForURI(nsIURI *aHostURI, |
3162 | | bool aIsForeign, |
3163 | | bool aIsTrackingResource, |
3164 | | bool aFirstPartyStorageAccessGranted, |
3165 | | bool aIsSafeTopLevelNav, |
3166 | | bool aIsSameSiteForeign, |
3167 | | bool aHttpBound, |
3168 | | const OriginAttributes& aOriginAttrs, |
3169 | | nsTArray<nsCookie*>& aCookieList) |
3170 | 0 | { |
3171 | 0 | NS_ASSERTION(aHostURI, "null host!"); |
3172 | 0 |
|
3173 | 0 | if (!mDBState) { |
3174 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
3175 | 0 | return; |
3176 | 0 | } |
3177 | 0 |
|
3178 | 0 | EnsureReadComplete(true); |
3179 | 0 |
|
3180 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
3181 | 0 | mDBState = (aOriginAttrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState; |
3182 | 0 |
|
3183 | 0 | // get the base domain, host, and path from the URI. |
3184 | 0 | // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk". |
3185 | 0 | // file:// URI's (i.e. with an empty host) are allowed, but any other |
3186 | 0 | // scheme must have a non-empty host. A trailing dot in the host |
3187 | 0 | // is acceptable. |
3188 | 0 | bool requireHostMatch; |
3189 | 0 | nsAutoCString baseDomain, hostFromURI, pathFromURI; |
3190 | 0 | nsresult rv = GetBaseDomain(mTLDService, aHostURI, baseDomain, requireHostMatch); |
3191 | 0 | if (NS_SUCCEEDED(rv)) |
3192 | 0 | rv = aHostURI->GetAsciiHost(hostFromURI); |
3193 | 0 | if (NS_SUCCEEDED(rv)) |
3194 | 0 | rv = aHostURI->GetPathQueryRef(pathFromURI); |
3195 | 0 | if (NS_FAILED(rv)) { |
3196 | 0 | COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI"); |
3197 | 0 | return; |
3198 | 0 | } |
3199 | 0 |
|
3200 | 0 | // check default prefs |
3201 | 0 | uint32_t priorCookieCount = 0; |
3202 | 0 | CountCookiesFromHost(hostFromURI, &priorCookieCount); |
3203 | 0 | CookieStatus cookieStatus = CheckPrefs(mPermissionService, mCookieBehavior, |
3204 | 0 | mThirdPartySession, |
3205 | 0 | mThirdPartyNonsecureSession, aHostURI, |
3206 | 0 | aIsForeign, aIsTrackingResource, |
3207 | 0 | aFirstPartyStorageAccessGranted, |
3208 | 0 | nullptr, priorCookieCount, |
3209 | 0 | aOriginAttrs, nullptr); |
3210 | 0 |
|
3211 | 0 | // for GetCookie(), we don't fire rejection notifications. |
3212 | 0 | switch (cookieStatus) { |
3213 | 0 | case STATUS_REJECTED: |
3214 | 0 | case STATUS_REJECTED_WITH_ERROR: |
3215 | 0 | return; |
3216 | 0 | default: |
3217 | 0 | break; |
3218 | 0 | } |
3219 | 0 | |
3220 | 0 | // Note: The following permissions logic is mirrored in |
3221 | 0 | // extensions::MatchPattern::MatchesCookie. |
3222 | 0 | // If it changes, please update that function, or file a bug for someone |
3223 | 0 | // else to do so. |
3224 | 0 | |
3225 | 0 | // check if aHostURI is using an https secure protocol. |
3226 | 0 | // if it isn't, then we can't send a secure cookie over the connection. |
3227 | 0 | // if SchemeIs fails, assume an insecure connection, to be on the safe side |
3228 | 0 | bool isSecure; |
3229 | 0 | if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) { |
3230 | 0 | isSecure = false; |
3231 | 0 | } |
3232 | 0 |
|
3233 | 0 | nsCookie *cookie; |
3234 | 0 | int64_t currentTimeInUsec = PR_Now(); |
3235 | 0 | int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; |
3236 | 0 | bool stale = false; |
3237 | 0 |
|
3238 | 0 | nsCookieKey key(baseDomain, aOriginAttrs); |
3239 | 0 |
|
3240 | 0 | // perform the hash lookup |
3241 | 0 | nsCookieEntry *entry = mDBState->hostTable.GetEntry(key); |
3242 | 0 | if (!entry) |
3243 | 0 | return; |
3244 | 0 | |
3245 | 0 | // iterate the cookies! |
3246 | 0 | const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
3247 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
3248 | 0 | cookie = cookies[i]; |
3249 | 0 |
|
3250 | 0 | // check the host, since the base domain lookup is conservative. |
3251 | 0 | if (!DomainMatches(cookie, hostFromURI)) |
3252 | 0 | continue; |
3253 | 0 | |
3254 | 0 | // if the cookie is secure and the host scheme isn't, we can't send it |
3255 | 0 | if (cookie->IsSecure() && !isSecure) |
3256 | 0 | continue; |
3257 | 0 | |
3258 | 0 | int32_t sameSiteAttr = 0; |
3259 | 0 | cookie->GetSameSite(&sameSiteAttr); |
3260 | 0 | if (aIsSameSiteForeign && IsSameSiteEnabled()) { |
3261 | 0 | // it if's a cross origin request and the cookie is same site only (strict) |
3262 | 0 | // don't send it |
3263 | 0 | if (sameSiteAttr == nsICookie2::SAMESITE_STRICT) { |
3264 | 0 | continue; |
3265 | 0 | } |
3266 | 0 | // if it's a cross origin request, the cookie is same site lax, but it's not |
3267 | 0 | // a top-level navigation, don't send it |
3268 | 0 | if (sameSiteAttr == nsICookie2::SAMESITE_LAX && !aIsSafeTopLevelNav) { |
3269 | 0 | continue; |
3270 | 0 | } |
3271 | 0 | } |
3272 | 0 | |
3273 | 0 | // if the cookie is httpOnly and it's not going directly to the HTTP |
3274 | 0 | // connection, don't send it |
3275 | 0 | if (cookie->IsHttpOnly() && !aHttpBound) |
3276 | 0 | continue; |
3277 | 0 | |
3278 | 0 | // if the nsIURI path doesn't match the cookie path, don't send it back |
3279 | 0 | if (!PathMatches(cookie, pathFromURI)) |
3280 | 0 | continue; |
3281 | 0 | |
3282 | 0 | // check if the cookie has expired |
3283 | 0 | if (cookie->Expiry() <= currentTime) { |
3284 | 0 | continue; |
3285 | 0 | } |
3286 | 0 | |
3287 | 0 | // all checks passed - add to list and check if lastAccessed stamp needs updating |
3288 | 0 | aCookieList.AppendElement(cookie); |
3289 | 0 | if (cookie->IsStale()) { |
3290 | 0 | stale = true; |
3291 | 0 | } |
3292 | 0 | } |
3293 | 0 |
|
3294 | 0 | int32_t count = aCookieList.Length(); |
3295 | 0 | if (count == 0) |
3296 | 0 | return; |
3297 | 0 | |
3298 | 0 | // update lastAccessed timestamps. we only do this if the timestamp is stale |
3299 | 0 | // by a certain amount, to avoid thrashing the db during pageload. |
3300 | 0 | if (stale) { |
3301 | 0 | // Create an array of parameters to bind to our update statement. Batching |
3302 | 0 | // is OK here since we're updating cookies with no interleaved operations. |
3303 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
3304 | 0 | mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate; |
3305 | 0 | if (mDBState->dbConn) { |
3306 | 0 | stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
3307 | 0 | } |
3308 | 0 |
|
3309 | 0 | for (int32_t i = 0; i < count; ++i) { |
3310 | 0 | cookie = aCookieList.ElementAt(i); |
3311 | 0 |
|
3312 | 0 | if (cookie->IsStale()) { |
3313 | 0 | UpdateCookieInList(cookie, currentTimeInUsec, paramsArray); |
3314 | 0 | } |
3315 | 0 | } |
3316 | 0 | // Update the database now if necessary. |
3317 | 0 | if (paramsArray) { |
3318 | 0 | uint32_t length; |
3319 | 0 | paramsArray->GetLength(&length); |
3320 | 0 | if (length) { |
3321 | 0 | DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
3322 | 0 | NS_ASSERT_SUCCESS(rv); |
3323 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
3324 | 0 | rv = stmt->ExecuteAsync(mDBState->updateListener, |
3325 | 0 | getter_AddRefs(handle)); |
3326 | 0 | NS_ASSERT_SUCCESS(rv); |
3327 | 0 | } |
3328 | 0 | } |
3329 | 0 | } |
3330 | 0 |
|
3331 | 0 | // return cookies in order of path length; longest to shortest. |
3332 | 0 | // this is required per RFC2109. if cookies match in length, |
3333 | 0 | // then sort by creation time (see bug 236772). |
3334 | 0 | aCookieList.Sort(CompareCookiesForSending()); |
3335 | 0 | } |
3336 | | |
3337 | | void |
3338 | | nsCookieService::GetCookieStringInternal(nsIURI *aHostURI, |
3339 | | bool aIsForeign, |
3340 | | bool aIsTrackingResource, |
3341 | | bool aFirstPartyStorageAccessGranted, |
3342 | | bool aIsSafeTopLevelNav, |
3343 | | bool aIsSameSiteForeign, |
3344 | | bool aHttpBound, |
3345 | | const OriginAttributes& aOriginAttrs, |
3346 | | nsCString &aCookieString) |
3347 | 0 | { |
3348 | 0 | AutoTArray<nsCookie*, 8> foundCookieList; |
3349 | 0 | GetCookiesForURI(aHostURI, aIsForeign, aIsTrackingResource, |
3350 | 0 | aFirstPartyStorageAccessGranted, aIsSafeTopLevelNav, |
3351 | 0 | aIsSameSiteForeign, aHttpBound, aOriginAttrs, |
3352 | 0 | foundCookieList); |
3353 | 0 |
|
3354 | 0 | nsCookie* cookie; |
3355 | 0 | for (uint32_t i = 0; i < foundCookieList.Length(); ++i) { |
3356 | 0 | cookie = foundCookieList.ElementAt(i); |
3357 | 0 |
|
3358 | 0 | // check if we have anything to write |
3359 | 0 | if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) { |
3360 | 0 | // if we've already added a cookie to the return list, append a "; " so |
3361 | 0 | // that subsequent cookies are delimited in the final list. |
3362 | 0 | if (!aCookieString.IsEmpty()) { |
3363 | 0 | aCookieString.AppendLiteral("; "); |
3364 | 0 | } |
3365 | 0 |
|
3366 | 0 | if (!cookie->Name().IsEmpty()) { |
3367 | 0 | // we have a name and value - write both |
3368 | 0 | aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value(); |
3369 | 0 | } else { |
3370 | 0 | // just write value |
3371 | 0 | aCookieString += cookie->Value(); |
3372 | 0 | } |
3373 | 0 | } |
3374 | 0 | } |
3375 | 0 |
|
3376 | 0 | if (!aCookieString.IsEmpty()) |
3377 | 0 | COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false); |
3378 | 0 | } |
3379 | | |
3380 | | // processes a single cookie, and returns true if there are more cookies |
3381 | | // to be processed |
3382 | | bool |
3383 | | nsCookieService::CanSetCookie(nsIURI* aHostURI, |
3384 | | const nsCookieKey& aKey, |
3385 | | nsCookieAttributes& aCookieAttributes, |
3386 | | bool aRequireHostMatch, |
3387 | | CookieStatus aStatus, |
3388 | | nsDependentCString& aCookieHeader, |
3389 | | int64_t aServerTime, |
3390 | | bool aFromHttp, |
3391 | | nsIChannel* aChannel, |
3392 | | bool aLeaveSecureAlone, |
3393 | | bool& aSetCookie, |
3394 | | mozIThirdPartyUtil* aThirdPartyUtil) |
3395 | 0 | { |
3396 | 0 | NS_ASSERTION(aHostURI, "null host!"); |
3397 | 0 |
|
3398 | 0 | aSetCookie = false; |
3399 | 0 |
|
3400 | 0 | // init expiryTime such that session cookies won't prematurely expire |
3401 | 0 | aCookieAttributes.expiryTime = INT64_MAX; |
3402 | 0 |
|
3403 | 0 | // aCookieHeader is an in/out param to point to the next cookie, if |
3404 | 0 | // there is one. Save the present value for logging purposes |
3405 | 0 | nsDependentCString savedCookieHeader(aCookieHeader); |
3406 | 0 |
|
3407 | 0 | // newCookie says whether there are multiple cookies in the header; |
3408 | 0 | // so we can handle them separately. |
3409 | 0 | bool newCookie = ParseAttributes(aCookieHeader, aCookieAttributes); |
3410 | 0 |
|
3411 | 0 | // Collect telemetry on how often secure cookies are set from non-secure |
3412 | 0 | // origins, and vice-versa. |
3413 | 0 | // |
3414 | 0 | // 0 = nonsecure and "http:" |
3415 | 0 | // 1 = nonsecure and "https:" |
3416 | 0 | // 2 = secure and "http:" |
3417 | 0 | // 3 = secure and "https:" |
3418 | 0 | bool isHTTPS; |
3419 | 0 | nsresult rv = aHostURI->SchemeIs("https", &isHTTPS); |
3420 | 0 | if (NS_SUCCEEDED(rv)) { |
3421 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_SECURITY, |
3422 | 0 | ((aCookieAttributes.isSecure)? 0x02 : 0x00) | |
3423 | 0 | ((isHTTPS)? 0x01 : 0x00)); |
3424 | 0 |
|
3425 | 0 | // Collect telemetry on how often are first- and third-party cookies set |
3426 | 0 | // from HTTPS origins: |
3427 | 0 | // |
3428 | 0 | // 0 (000) = first-party and "http:" |
3429 | 0 | // 1 (001) = first-party and "http:" with bogus Secure cookie flag?! |
3430 | 0 | // 2 (010) = first-party and "https:" |
3431 | 0 | // 3 (011) = first-party and "https:" with Secure cookie flag |
3432 | 0 | // 4 (100) = third-party and "http:" |
3433 | 0 | // 5 (101) = third-party and "http:" with bogus Secure cookie flag?! |
3434 | 0 | // 6 (110) = third-party and "https:" |
3435 | 0 | // 7 (111) = third-party and "https:" with Secure cookie flag |
3436 | 0 | if (aThirdPartyUtil) { |
3437 | 0 | bool isThirdParty = true; |
3438 | 0 | aThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isThirdParty); |
3439 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_SCHEME_HTTPS, |
3440 | 0 | (isThirdParty ? 0x04 : 0x00) | |
3441 | 0 | (isHTTPS ? 0x02 : 0x00) | |
3442 | 0 | (aCookieAttributes.isSecure ? 0x01 : 0x00)); |
3443 | 0 | } |
3444 | 0 | } |
3445 | 0 |
|
3446 | 0 | int64_t currentTimeInUsec = PR_Now(); |
3447 | 0 |
|
3448 | 0 | // calculate expiry time of cookie. |
3449 | 0 | aCookieAttributes.isSession = GetExpiry(aCookieAttributes, aServerTime, |
3450 | 0 | currentTimeInUsec / PR_USEC_PER_SEC); |
3451 | 0 | if (aStatus == STATUS_ACCEPT_SESSION) { |
3452 | 0 | // force lifetime to session. note that the expiration time, if set above, |
3453 | 0 | // will still apply. |
3454 | 0 | aCookieAttributes.isSession = true; |
3455 | 0 | } |
3456 | 0 |
|
3457 | 0 | // reject cookie if it's over the size limit, per RFC2109 |
3458 | 0 | if ((aCookieAttributes.name.Length() + aCookieAttributes.value.Length()) > kMaxBytesPerCookie) { |
3459 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)"); |
3460 | 0 | return newCookie; |
3461 | 0 | } |
3462 | 0 |
|
3463 | 0 | const char illegalNameCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, |
3464 | 0 | 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, |
3465 | 0 | 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, |
3466 | 0 | 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, |
3467 | 0 | 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, |
3468 | 0 | 0x1F, 0x00 }; |
3469 | 0 | if (aCookieAttributes.name.FindCharInSet(illegalNameCharacters, 0) != -1) { |
3470 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character"); |
3471 | 0 | return newCookie; |
3472 | 0 | } |
3473 | 0 |
|
3474 | 0 | // domain & path checks |
3475 | 0 | if (!CheckDomain(aCookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) { |
3476 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests"); |
3477 | 0 | return newCookie; |
3478 | 0 | } |
3479 | 0 | if (!CheckPath(aCookieAttributes, aHostURI)) { |
3480 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests"); |
3481 | 0 | return newCookie; |
3482 | 0 | } |
3483 | 0 | // magic prefix checks. MUST be run after CheckDomain() and CheckPath() |
3484 | 0 | if (!CheckPrefixes(aCookieAttributes, isHTTPS)) { |
3485 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the prefix tests"); |
3486 | 0 | return newCookie; |
3487 | 0 | } |
3488 | 0 |
|
3489 | 0 | // reject cookie if value contains an RFC 6265 disallowed character - see |
3490 | 0 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1191423 |
3491 | 0 | // NOTE: this is not the full set of characters disallowed by 6265 - notably |
3492 | 0 | // 0x09, 0x20, 0x22, 0x2C, 0x5C, and 0x7F are missing from this list. This is |
3493 | 0 | // for parity with Chrome. This only applies to cookies set via the Set-Cookie |
3494 | 0 | // header, as document.cookie is defined to be UTF-8. Hooray for |
3495 | 0 | // symmetry!</sarcasm> |
3496 | 0 | const char illegalCharacters[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, |
3497 | 0 | 0x08, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, |
3498 | 0 | 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, |
3499 | 0 | 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, |
3500 | 0 | 0x1E, 0x1F, 0x3B, 0x00 }; |
3501 | 0 | if (aFromHttp && (aCookieAttributes.value.FindCharInSet(illegalCharacters, 0) != -1)) { |
3502 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid value character"); |
3503 | 0 | return newCookie; |
3504 | 0 | } |
3505 | 0 |
|
3506 | 0 | // if the new cookie is httponly, make sure we're not coming from script |
3507 | 0 | if (!aFromHttp && aCookieAttributes.isHttpOnly) { |
3508 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, |
3509 | 0 | "cookie is httponly; coming from script"); |
3510 | 0 | return newCookie; |
3511 | 0 | } |
3512 | 0 |
|
3513 | 0 | bool isSecure = true; |
3514 | 0 | if (aHostURI) { |
3515 | 0 | aHostURI->SchemeIs("https", &isSecure); |
3516 | 0 | } |
3517 | 0 |
|
3518 | 0 | // If the new cookie is non-https and wants to set secure flag, |
3519 | 0 | // browser have to ignore this new cookie. |
3520 | 0 | // (draft-ietf-httpbis-cookie-alone section 3.1) |
3521 | 0 | if (aLeaveSecureAlone && aCookieAttributes.isSecure && !isSecure) { |
3522 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3523 | 0 | "non-https cookie can't set secure flag"); |
3524 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
3525 | 0 | BLOCKED_SECURE_SET_FROM_HTTP); |
3526 | 0 | return newCookie; |
3527 | 0 | } |
3528 | 0 |
|
3529 | 0 | // If the new cookie is same-site but in a cross site context, |
3530 | 0 | // browser must ignore the cookie. |
3531 | 0 | if ((aCookieAttributes.sameSite != nsICookie2::SAMESITE_UNSET) && |
3532 | 0 | aThirdPartyUtil && |
3533 | 0 | IsSameSiteEnabled()) { |
3534 | 0 |
|
3535 | 0 | // Do not treat loads triggered by web extensions as foreign |
3536 | 0 | bool addonAllowsLoad = false; |
3537 | 0 | if (aChannel) { |
3538 | 0 | nsCOMPtr<nsIURI> channelURI; |
3539 | 0 | NS_GetFinalChannelURI(aChannel, getter_AddRefs(channelURI)); |
3540 | 0 | nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); |
3541 | 0 | addonAllowsLoad = loadInfo && |
3542 | 0 | BasePrincipal::Cast(loadInfo->TriggeringPrincipal())-> |
3543 | 0 | AddonAllowsLoad(channelURI); |
3544 | 0 | } |
3545 | 0 |
|
3546 | 0 | if (!addonAllowsLoad) { |
3547 | 0 | bool isThirdParty = false; |
3548 | 0 | nsresult rv = aThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isThirdParty); |
3549 | 0 | if (NS_FAILED(rv) || isThirdParty) { |
3550 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, |
3551 | 0 | "failed the samesite tests"); |
3552 | 0 | return newCookie; |
3553 | 0 | } |
3554 | 0 | } |
3555 | 0 | } |
3556 | 0 |
|
3557 | 0 | aSetCookie = true; |
3558 | 0 | return newCookie; |
3559 | 0 | } |
3560 | | |
3561 | | // processes a single cookie, and returns true if there are more cookies |
3562 | | // to be processed |
3563 | | bool |
3564 | | nsCookieService::SetCookieInternal(nsIURI *aHostURI, |
3565 | | const mozilla::net::nsCookieKey &aKey, |
3566 | | bool aRequireHostMatch, |
3567 | | CookieStatus aStatus, |
3568 | | nsDependentCString &aCookieHeader, |
3569 | | int64_t aServerTime, |
3570 | | bool aFromHttp, |
3571 | | nsIChannel *aChannel) |
3572 | 0 | { |
3573 | 0 | NS_ASSERTION(aHostURI, "null host!"); |
3574 | 0 | bool canSetCookie = false; |
3575 | 0 | nsDependentCString savedCookieHeader(aCookieHeader); |
3576 | 0 | nsCookieAttributes cookieAttributes; |
3577 | 0 | bool newCookie = CanSetCookie(aHostURI, aKey, cookieAttributes, aRequireHostMatch, |
3578 | 0 | aStatus, aCookieHeader, aServerTime, aFromHttp, |
3579 | 0 | aChannel, mLeaveSecureAlone, canSetCookie, |
3580 | 0 | mThirdPartyUtil); |
3581 | 0 |
|
3582 | 0 | if (!canSetCookie) { |
3583 | 0 | return newCookie; |
3584 | 0 | } |
3585 | 0 | |
3586 | 0 | int64_t currentTimeInUsec = PR_Now(); |
3587 | 0 | // create a new nsCookie and copy attributes |
3588 | 0 | RefPtr<nsCookie> cookie = |
3589 | 0 | nsCookie::Create(cookieAttributes.name, |
3590 | 0 | cookieAttributes.value, |
3591 | 0 | cookieAttributes.host, |
3592 | 0 | cookieAttributes.path, |
3593 | 0 | cookieAttributes.expiryTime, |
3594 | 0 | currentTimeInUsec, |
3595 | 0 | nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), |
3596 | 0 | cookieAttributes.isSession, |
3597 | 0 | cookieAttributes.isSecure, |
3598 | 0 | cookieAttributes.isHttpOnly, |
3599 | 0 | aKey.mOriginAttributes, |
3600 | 0 | cookieAttributes.sameSite); |
3601 | 0 | if (!cookie) |
3602 | 0 | return newCookie; |
3603 | 0 | |
3604 | 0 | // check permissions from site permission list, or ask the user, |
3605 | 0 | // to determine if we can set the cookie |
3606 | 0 | if (mPermissionService) { |
3607 | 0 | bool permission; |
3608 | 0 | mPermissionService->CanSetCookie(aHostURI, |
3609 | 0 | aChannel, |
3610 | 0 | static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)), |
3611 | 0 | &cookieAttributes.isSession, |
3612 | 0 | &cookieAttributes.expiryTime, |
3613 | 0 | &permission); |
3614 | 0 | if (!permission) { |
3615 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager"); |
3616 | 0 | NotifyRejected(aHostURI, aChannel, |
3617 | 0 | nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION); |
3618 | 0 | return newCookie; |
3619 | 0 | } |
3620 | 0 |
|
3621 | 0 | // update isSession and expiry attributes, in case they changed |
3622 | 0 | cookie->SetIsSession(cookieAttributes.isSession); |
3623 | 0 | cookie->SetExpiry(cookieAttributes.expiryTime); |
3624 | 0 | } |
3625 | 0 |
|
3626 | 0 | // add the cookie to the list. AddInternal() takes care of logging. |
3627 | 0 | // we get the current time again here, since it may have changed during prompting |
3628 | 0 | AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(), |
3629 | 0 | aFromHttp); |
3630 | 0 | return newCookie; |
3631 | 0 | } |
3632 | | |
3633 | | // this is a backend function for adding a cookie to the list, via SetCookie. |
3634 | | // also used in the cookie manager, for profile migration from IE. |
3635 | | // it either replaces an existing cookie; or adds the cookie to the hashtable, |
3636 | | // and deletes a cookie (if maximum number of cookies has been |
3637 | | // reached). also performs list maintenance by removing expired cookies. |
3638 | | void |
3639 | | nsCookieService::AddInternal(const nsCookieKey &aKey, |
3640 | | nsCookie *aCookie, |
3641 | | int64_t aCurrentTimeInUsec, |
3642 | | nsIURI *aHostURI, |
3643 | | const char *aCookieHeader, |
3644 | | bool aFromHttp) |
3645 | 0 | { |
3646 | 0 | MOZ_ASSERT(mInitializedDBStates); |
3647 | 0 | MOZ_ASSERT(mInitializedDBConn); |
3648 | 0 |
|
3649 | 0 | int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC; |
3650 | 0 |
|
3651 | 0 | nsListIter exactIter; |
3652 | 0 | bool foundCookie = false; |
3653 | 0 | foundCookie = FindCookie(aKey, aCookie->Host(), |
3654 | 0 | aCookie->Name(), aCookie->Path(), exactIter); |
3655 | 0 | bool foundSecureExact = foundCookie && exactIter.Cookie()->IsSecure(); |
3656 | 0 | bool isSecure = true; |
3657 | 0 | if (aHostURI && NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) { |
3658 | 0 | isSecure = false; |
3659 | 0 | } |
3660 | 0 | bool oldCookieIsSession = false; |
3661 | 0 | if (mLeaveSecureAlone) { |
3662 | 0 | // Step1, call FindSecureCookie(). FindSecureCookie() would |
3663 | 0 | // find the existing cookie with the security flag and has |
3664 | 0 | // the same name, host and path of the new cookie, if there is any. |
3665 | 0 | // Step2, Confirm new cookie's security setting. If any targeted |
3666 | 0 | // cookie had been found in Step1, then confirm whether the |
3667 | 0 | // new cookie could modify it. If the new created cookie’s |
3668 | 0 | // "secure-only-flag" is not set, and the "scheme" component |
3669 | 0 | // of the "request-uri" does not denote a "secure" protocol, |
3670 | 0 | // then ignore the new cookie. |
3671 | 0 | // (draft-ietf-httpbis-cookie-alone section 3.2) |
3672 | 0 | if (!aCookie->IsSecure() |
3673 | 0 | && (foundSecureExact || FindSecureCookie(aKey, aCookie))) { |
3674 | 0 | if (!isSecure) { |
3675 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3676 | 0 | "cookie can't save because older cookie is secure cookie but newer cookie is non-secure cookie"); |
3677 | 0 | if (foundSecureExact) { |
3678 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
3679 | 0 | BLOCKED_DOWNGRADE_SECURE_EXACT); |
3680 | 0 | } else { |
3681 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
3682 | 0 | BLOCKED_DOWNGRADE_SECURE_INEXACT); |
3683 | 0 | } |
3684 | 0 | return; |
3685 | 0 | } |
3686 | 0 | // A secure site is allowed to downgrade a secure cookie |
3687 | 0 | // but we want to measure anyway. |
3688 | 0 | if (foundSecureExact) { |
3689 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
3690 | 0 | DOWNGRADE_SECURE_FROM_SECURE_EXACT); |
3691 | 0 | } else { |
3692 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
3693 | 0 | DOWNGRADE_SECURE_FROM_SECURE_INEXACT); |
3694 | 0 | } |
3695 | 0 | } |
3696 | 0 | } |
3697 | 0 |
|
3698 | 0 | RefPtr<nsCookie> oldCookie; |
3699 | 0 | nsCOMPtr<nsIArray> purgedList; |
3700 | 0 | if (foundCookie) { |
3701 | 0 | oldCookie = exactIter.Cookie(); |
3702 | 0 | oldCookieIsSession = oldCookie->IsSession(); |
3703 | 0 |
|
3704 | 0 | // Check if the old cookie is stale (i.e. has already expired). If so, we |
3705 | 0 | // need to be careful about the semantics of removing it and adding the new |
3706 | 0 | // cookie: we want the behavior wrt adding the new cookie to be the same as |
3707 | 0 | // if it didn't exist, but we still want to fire a removal notification. |
3708 | 0 | if (oldCookie->Expiry() <= currentTime) { |
3709 | 0 | if (aCookie->Expiry() <= currentTime) { |
3710 | 0 | // The new cookie has expired and the old one is stale. Nothing to do. |
3711 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3712 | 0 | "cookie has already expired"); |
3713 | 0 | return; |
3714 | 0 | } |
3715 | 0 |
|
3716 | 0 | // Remove the stale cookie. We save notification for later, once all list |
3717 | 0 | // modifications are complete. |
3718 | 0 | RemoveCookieFromList(exactIter); |
3719 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3720 | 0 | "stale cookie was purged"); |
3721 | 0 | purgedList = CreatePurgeList(oldCookie); |
3722 | 0 |
|
3723 | 0 | // We've done all we need to wrt removing and notifying the stale cookie. |
3724 | 0 | // From here on out, we pretend pretend it didn't exist, so that we |
3725 | 0 | // preserve expected notification semantics when adding the new cookie. |
3726 | 0 | foundCookie = false; |
3727 | 0 |
|
3728 | 0 | } else { |
3729 | 0 | // If the old cookie is httponly, make sure we're not coming from script. |
3730 | 0 | if (!aFromHttp && oldCookie->IsHttpOnly()) { |
3731 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3732 | 0 | "previously stored cookie is httponly; coming from script"); |
3733 | 0 | return; |
3734 | 0 | } |
3735 | 0 |
|
3736 | 0 | // If the new cookie has the same value, expiry date, isSecure, isSession, |
3737 | 0 | // isHttpOnly and sameSite flags then we can just keep the old one. |
3738 | 0 | // Only if any of these differ we would want to override the cookie. |
3739 | 0 | if (oldCookie->Value().Equals(aCookie->Value()) && |
3740 | 0 | oldCookie->Expiry() == aCookie->Expiry() && |
3741 | 0 | oldCookie->IsSecure() == aCookie->IsSecure() && |
3742 | 0 | oldCookie->IsSession() == aCookie->IsSession() && |
3743 | 0 | oldCookie->IsHttpOnly() == aCookie->IsHttpOnly() && |
3744 | 0 | oldCookie->SameSite() == aCookie->SameSite() && |
3745 | 0 | // We don't want to perform this optimization if the cookie is |
3746 | 0 | // considered stale, since in this case we would need to update the |
3747 | 0 | // database. |
3748 | 0 | !oldCookie->IsStale()) { |
3749 | 0 | // Update the last access time on the old cookie. |
3750 | 0 | oldCookie->SetLastAccessed(aCookie->LastAccessed()); |
3751 | 0 | UpdateCookieOldestTime(mDBState, oldCookie); |
3752 | 0 | return; |
3753 | 0 | } |
3754 | 0 | |
3755 | 0 | // Remove the old cookie. |
3756 | 0 | RemoveCookieFromList(exactIter); |
3757 | 0 |
|
3758 | 0 | // If the new cookie has expired -- i.e. the intent was simply to delete |
3759 | 0 | // the old cookie -- then we're done. |
3760 | 0 | if (aCookie->Expiry() <= currentTime) { |
3761 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3762 | 0 | "previously stored cookie was deleted"); |
3763 | 0 | NotifyChanged(oldCookie, u"deleted", oldCookieIsSession, aFromHttp); |
3764 | 0 | return; |
3765 | 0 | } |
3766 | 0 |
|
3767 | 0 | // Preserve creation time of cookie for ordering purposes. |
3768 | 0 | aCookie->SetCreationTime(oldCookie->CreationTime()); |
3769 | 0 | } |
3770 | 0 |
|
3771 | 0 | } else { |
3772 | 0 | // check if cookie has already expired |
3773 | 0 | if (aCookie->Expiry() <= currentTime) { |
3774 | 0 | COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
3775 | 0 | "cookie has already expired"); |
3776 | 0 | return; |
3777 | 0 | } |
3778 | 0 |
|
3779 | 0 | // check if we have to delete an old cookie. |
3780 | 0 | nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey); |
3781 | 0 | if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) { |
3782 | 0 | nsTArray<nsListIter> removedIterList; |
3783 | 0 | // Prioritize evicting insecure cookies. |
3784 | 0 | // (draft-ietf-httpbis-cookie-alone section 3.3) |
3785 | 0 | mozilla::Maybe<bool> optionalSecurity = mLeaveSecureAlone ? Some(false) : Nothing(); |
3786 | 0 | uint32_t limit = mMaxCookiesPerHost - mCookieQuotaPerHost; |
3787 | 0 | FindStaleCookies(entry, currentTime, optionalSecurity, removedIterList, limit); |
3788 | 0 | if (removedIterList.Length() == 0) { |
3789 | 0 | if (aCookie->IsSecure()) { |
3790 | 0 | // It's valid to evict a secure cookie for another secure cookie. |
3791 | 0 | FindStaleCookies(entry, currentTime, Some(true), removedIterList, limit); |
3792 | 0 | } else { |
3793 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
3794 | 0 | EVICTING_SECURE_BLOCKED); |
3795 | 0 | COOKIE_LOGEVICTED(aCookie, |
3796 | 0 | "Too many cookies for this domain and the new cookie is not a secure cookie"); |
3797 | 0 | return; |
3798 | 0 | } |
3799 | 0 | } |
3800 | 0 |
|
3801 | 0 | MOZ_ASSERT(!removedIterList.IsEmpty()); |
3802 | 0 | // Sort |removedIterList| by index again, since we have to remove the cookie |
3803 | 0 | // in the reverse order. |
3804 | 0 | removedIterList.Sort(CompareCookiesByIndex()); |
3805 | 0 | for (auto it = removedIterList.rbegin(); it != removedIterList.rend(); it++) { |
3806 | 0 | RefPtr<nsCookie> evictedCookie = (*it).Cookie(); |
3807 | 0 | if (mLeaveSecureAlone && evictedCookie->Expiry() <= currentTime) { |
3808 | 0 | TelemetryForEvictingStaleCookie(evictedCookie, |
3809 | 0 | evictedCookie->LastAccessed()); |
3810 | 0 | } |
3811 | 0 | COOKIE_LOGEVICTED(evictedCookie, "Too many cookies for this domain"); |
3812 | 0 | RemoveCookieFromList(*it); |
3813 | 0 | CreateOrUpdatePurgeList(getter_AddRefs(purgedList), evictedCookie); |
3814 | 0 | MOZ_ASSERT((*it).entry); |
3815 | 0 | } |
3816 | 0 |
|
3817 | 0 | } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) { |
3818 | 0 | int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime; |
3819 | 0 | int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge); |
3820 | 0 | if (maxAge >= purgeAge) { |
3821 | 0 | // we're over both size and age limits by 10%; time to purge the table! |
3822 | 0 | // do this by: |
3823 | 0 | // 1) removing expired cookies; |
3824 | 0 | // 2) evicting the balance of old cookies until we reach the size limit. |
3825 | 0 | // note that the cookieOldestTime indicator can be pessimistic - if it's |
3826 | 0 | // older than the actual oldest cookie, we'll just purge more eagerly. |
3827 | 0 | purgedList = PurgeCookies(aCurrentTimeInUsec); |
3828 | 0 | } |
3829 | 0 | } |
3830 | 0 | } |
3831 | 0 |
|
3832 | 0 | // Add the cookie to the db. We do not supply a params array for batching |
3833 | 0 | // because this might result in removals and additions being out of order. |
3834 | 0 | AddCookieToList(aKey, aCookie, mDBState, nullptr); |
3835 | 0 | COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie); |
3836 | 0 |
|
3837 | 0 | // Now that list mutations are complete, notify observers. We do it here |
3838 | 0 | // because observers may themselves attempt to mutate the list. |
3839 | 0 | if (purgedList) { |
3840 | 0 | NotifyChanged(purgedList, u"batch-deleted"); |
3841 | 0 | } |
3842 | 0 |
|
3843 | 0 | NotifyChanged(aCookie, foundCookie ? u"changed" : u"added", oldCookieIsSession, aFromHttp); |
3844 | 0 | } |
3845 | | |
3846 | | /****************************************************************************** |
3847 | | * nsCookieService impl: |
3848 | | * private cookie header parsing functions |
3849 | | ******************************************************************************/ |
3850 | | |
3851 | | // The following comment block elucidates the function of ParseAttributes. |
3852 | | /****************************************************************************** |
3853 | | ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1 |
3854 | | ** please note: this BNF deviates from both specifications, and reflects this |
3855 | | ** implementation. <bnf> indicates a reference to the defined grammar "bnf". |
3856 | | |
3857 | | ** Differences from RFC2109/2616 and explanations: |
3858 | | 1. implied *LWS |
3859 | | The grammar described by this specification is word-based. Except |
3860 | | where noted otherwise, linear white space (<LWS>) can be included |
3861 | | between any two adjacent words (token or quoted-string), and |
3862 | | between adjacent words and separators, without changing the |
3863 | | interpretation of a field. |
3864 | | <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT. |
3865 | | |
3866 | | 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in |
3867 | | common use inside values. |
3868 | | |
3869 | | 3. tokens and values have looser restrictions on allowed characters than |
3870 | | spec. This is also due to certain characters being in common use inside |
3871 | | values. We allow only '=' to separate token/value pairs, and ';' to |
3872 | | terminate tokens or values. <LWS> is allowed within tokens and values |
3873 | | (see bug 206022). |
3874 | | |
3875 | | 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to |
3876 | | reject control chars or non-ASCII chars. This is erring on the loose |
3877 | | side, since there's probably no good reason to enforce this strictness. |
3878 | | |
3879 | | 5. cookie <NAME> is optional, where spec requires it. This is a fairly |
3880 | | trivial case, but allows the flexibility of setting only a cookie <VALUE> |
3881 | | with a blank <NAME> and is required by some sites (see bug 169091). |
3882 | | |
3883 | | 6. Attribute "HttpOnly", not covered in the RFCs, is supported |
3884 | | (see bug 178993). |
3885 | | |
3886 | | ** Begin BNF: |
3887 | | token = 1*<any allowed-chars except separators> |
3888 | | value = 1*<any allowed-chars except value-sep> |
3889 | | separators = ";" | "=" |
3890 | | value-sep = ";" |
3891 | | cookie-sep = CR | LF |
3892 | | allowed-chars = <any OCTET except NUL or cookie-sep> |
3893 | | OCTET = <any 8-bit sequence of data> |
3894 | | LWS = SP | HT |
3895 | | NUL = <US-ASCII NUL, null control character (0)> |
3896 | | CR = <US-ASCII CR, carriage return (13)> |
3897 | | LF = <US-ASCII LF, linefeed (10)> |
3898 | | SP = <US-ASCII SP, space (32)> |
3899 | | HT = <US-ASCII HT, horizontal-tab (9)> |
3900 | | |
3901 | | set-cookie = "Set-Cookie:" cookies |
3902 | | cookies = cookie *( cookie-sep cookie ) |
3903 | | cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first |
3904 | | NAME = token ; cookie name |
3905 | | VALUE = value ; cookie value |
3906 | | cookie-av = token ["=" value] |
3907 | | |
3908 | | valid values for cookie-av (checked post-parsing) are: |
3909 | | cookie-av = "Path" "=" value |
3910 | | | "Domain" "=" value |
3911 | | | "Expires" "=" value |
3912 | | | "Max-Age" "=" value |
3913 | | | "Comment" "=" value |
3914 | | | "Version" "=" value |
3915 | | | "Secure" |
3916 | | | "HttpOnly" |
3917 | | |
3918 | | ******************************************************************************/ |
3919 | | |
3920 | | // helper functions for GetTokenValue |
3921 | 0 | static inline bool iswhitespace (char c) { return c == ' ' || c == '\t'; } |
3922 | 0 | static inline bool isterminator (char c) { return c == '\n' || c == '\r'; } |
3923 | 0 | static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; } |
3924 | 0 | static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; } |
3925 | | |
3926 | | // Parse a single token/value pair. |
3927 | | // Returns true if a cookie terminator is found, so caller can parse new cookie. |
3928 | | bool |
3929 | | nsCookieService::GetTokenValue(nsACString::const_char_iterator &aIter, |
3930 | | nsACString::const_char_iterator &aEndIter, |
3931 | | nsDependentCSubstring &aTokenString, |
3932 | | nsDependentCSubstring &aTokenValue, |
3933 | | bool &aEqualsFound) |
3934 | 0 | { |
3935 | 0 | nsACString::const_char_iterator start, lastSpace; |
3936 | 0 | // initialize value string to clear garbage |
3937 | 0 | aTokenValue.Rebind(aIter, aIter); |
3938 | 0 |
|
3939 | 0 | // find <token>, including any <LWS> between the end-of-token and the |
3940 | 0 | // token separator. we'll remove trailing <LWS> next |
3941 | 0 | while (aIter != aEndIter && iswhitespace(*aIter)) |
3942 | 0 | ++aIter; |
3943 | 0 | start = aIter; |
3944 | 0 | while (aIter != aEndIter && !istokenseparator(*aIter)) |
3945 | 0 | ++aIter; |
3946 | 0 |
|
3947 | 0 | // remove trailing <LWS>; first check we're not at the beginning |
3948 | 0 | lastSpace = aIter; |
3949 | 0 | if (lastSpace != start) { |
3950 | 0 | while (--lastSpace != start && iswhitespace(*lastSpace)) |
3951 | 0 | continue; |
3952 | 0 | ++lastSpace; |
3953 | 0 | } |
3954 | 0 | aTokenString.Rebind(start, lastSpace); |
3955 | 0 |
|
3956 | 0 | aEqualsFound = (*aIter == '='); |
3957 | 0 | if (aEqualsFound) { |
3958 | 0 | // find <value> |
3959 | 0 | while (++aIter != aEndIter && iswhitespace(*aIter)) |
3960 | 0 | continue; |
3961 | 0 |
|
3962 | 0 | start = aIter; |
3963 | 0 |
|
3964 | 0 | // process <token> |
3965 | 0 | // just look for ';' to terminate ('=' allowed) |
3966 | 0 | while (aIter != aEndIter && !isvalueseparator(*aIter)) |
3967 | 0 | ++aIter; |
3968 | 0 |
|
3969 | 0 | // remove trailing <LWS>; first check we're not at the beginning |
3970 | 0 | if (aIter != start) { |
3971 | 0 | lastSpace = aIter; |
3972 | 0 | while (--lastSpace != start && iswhitespace(*lastSpace)) |
3973 | 0 | continue; |
3974 | 0 | aTokenValue.Rebind(start, ++lastSpace); |
3975 | 0 | } |
3976 | 0 | } |
3977 | 0 |
|
3978 | 0 | // aIter is on ';', or terminator, or EOS |
3979 | 0 | if (aIter != aEndIter) { |
3980 | 0 | // if on terminator, increment past & return true to process new cookie |
3981 | 0 | if (isterminator(*aIter)) { |
3982 | 0 | ++aIter; |
3983 | 0 | return true; |
3984 | 0 | } |
3985 | 0 | // fall-through: aIter is on ';', increment and return false |
3986 | 0 | ++aIter; |
3987 | 0 | } |
3988 | 0 | return false; |
3989 | 0 | } |
3990 | | |
3991 | | // Parses attributes from cookie header. expires/max-age attributes aren't folded into the |
3992 | | // cookie struct here, because we don't know which one to use until we've parsed the header. |
3993 | | bool |
3994 | | nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader, |
3995 | | nsCookieAttributes &aCookieAttributes) |
3996 | 0 | { |
3997 | 0 | static const char kPath[] = "path"; |
3998 | 0 | static const char kDomain[] = "domain"; |
3999 | 0 | static const char kExpires[] = "expires"; |
4000 | 0 | static const char kMaxage[] = "max-age"; |
4001 | 0 | static const char kSecure[] = "secure"; |
4002 | 0 | static const char kHttpOnly[] = "httponly"; |
4003 | 0 | static const char kSameSite[] = "samesite"; |
4004 | 0 | static const char kSameSiteLax[] = "lax"; |
4005 | 0 | static const char kSameSiteStrict[] = "strict"; |
4006 | 0 |
|
4007 | 0 | nsACString::const_char_iterator tempBegin, tempEnd; |
4008 | 0 | nsACString::const_char_iterator cookieStart, cookieEnd; |
4009 | 0 | aCookieHeader.BeginReading(cookieStart); |
4010 | 0 | aCookieHeader.EndReading(cookieEnd); |
4011 | 0 |
|
4012 | 0 | aCookieAttributes.isSecure = false; |
4013 | 0 | aCookieAttributes.isHttpOnly = false; |
4014 | 0 | aCookieAttributes.sameSite = nsICookie2::SAMESITE_UNSET; |
4015 | 0 |
|
4016 | 0 | nsDependentCSubstring tokenString(cookieStart, cookieStart); |
4017 | 0 | nsDependentCSubstring tokenValue (cookieStart, cookieStart); |
4018 | 0 | bool newCookie, equalsFound; |
4019 | 0 |
|
4020 | 0 | // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings. |
4021 | 0 | // if we find multiple cookies, return for processing |
4022 | 0 | // note: if there's no '=', we assume token is <VALUE>. this is required by |
4023 | 0 | // some sites (see bug 169091). |
4024 | 0 | // XXX fix the parser to parse according to <VALUE> grammar for this case |
4025 | 0 | newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); |
4026 | 0 | if (equalsFound) { |
4027 | 0 | aCookieAttributes.name = tokenString; |
4028 | 0 | aCookieAttributes.value = tokenValue; |
4029 | 0 | } else { |
4030 | 0 | aCookieAttributes.value = tokenString; |
4031 | 0 | } |
4032 | 0 |
|
4033 | 0 | // extract remaining attributes |
4034 | 0 | while (cookieStart != cookieEnd && !newCookie) { |
4035 | 0 | newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); |
4036 | 0 |
|
4037 | 0 | if (!tokenValue.IsEmpty()) { |
4038 | 0 | tokenValue.BeginReading(tempBegin); |
4039 | 0 | tokenValue.EndReading(tempEnd); |
4040 | 0 | } |
4041 | 0 |
|
4042 | 0 | // decide which attribute we have, and copy the string |
4043 | 0 | if (tokenString.LowerCaseEqualsLiteral(kPath)) |
4044 | 0 | aCookieAttributes.path = tokenValue; |
4045 | 0 |
|
4046 | 0 | else if (tokenString.LowerCaseEqualsLiteral(kDomain)) |
4047 | 0 | aCookieAttributes.host = tokenValue; |
4048 | 0 |
|
4049 | 0 | else if (tokenString.LowerCaseEqualsLiteral(kExpires)) |
4050 | 0 | aCookieAttributes.expires = tokenValue; |
4051 | 0 |
|
4052 | 0 | else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) |
4053 | 0 | aCookieAttributes.maxage = tokenValue; |
4054 | 0 |
|
4055 | 0 | // ignore any tokenValue for isSecure; just set the boolean |
4056 | 0 | else if (tokenString.LowerCaseEqualsLiteral(kSecure)) |
4057 | 0 | aCookieAttributes.isSecure = true; |
4058 | 0 |
|
4059 | 0 | // ignore any tokenValue for isHttpOnly (see bug 178993); |
4060 | 0 | // just set the boolean |
4061 | 0 | else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) |
4062 | 0 | aCookieAttributes.isHttpOnly = true; |
4063 | 0 |
|
4064 | 0 | else if (tokenString.LowerCaseEqualsLiteral(kSameSite)) { |
4065 | 0 | if (tokenValue.LowerCaseEqualsLiteral(kSameSiteLax)) { |
4066 | 0 | aCookieAttributes.sameSite = nsICookie2::SAMESITE_LAX; |
4067 | 0 | } else if (tokenValue.LowerCaseEqualsLiteral(kSameSiteStrict)) { |
4068 | 0 | aCookieAttributes.sameSite = nsICookie2::SAMESITE_STRICT; |
4069 | 0 | } |
4070 | 0 | } |
4071 | 0 | } |
4072 | 0 |
|
4073 | 0 | // rebind aCookieHeader, in case we need to process another cookie |
4074 | 0 | aCookieHeader.Rebind(cookieStart, cookieEnd); |
4075 | 0 | return newCookie; |
4076 | 0 | } |
4077 | | |
4078 | | /****************************************************************************** |
4079 | | * nsCookieService impl: |
4080 | | * private domain & permission compliance enforcement functions |
4081 | | ******************************************************************************/ |
4082 | | |
4083 | | // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be |
4084 | | // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing |
4085 | | // dot may be present. If aHostURI is an IP address, an alias such as |
4086 | | // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will |
4087 | | // be the exact host, and aRequireHostMatch will be true to indicate that |
4088 | | // substring matches should not be performed. |
4089 | | nsresult |
4090 | | nsCookieService::GetBaseDomain(nsIEffectiveTLDService *aTLDService, |
4091 | | nsIURI *aHostURI, |
4092 | | nsCString &aBaseDomain, |
4093 | | bool &aRequireHostMatch) |
4094 | 0 | { |
4095 | 0 | // get the base domain. this will fail if the host contains a leading dot, |
4096 | 0 | // more than one trailing dot, or is otherwise malformed. |
4097 | 0 | nsresult rv = aTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain); |
4098 | 0 | aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS || |
4099 | 0 | rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS; |
4100 | 0 | if (aRequireHostMatch) { |
4101 | 0 | // aHostURI is either an IP address, an alias such as 'localhost', an eTLD |
4102 | 0 | // such as 'co.uk', or the empty string. use the host as a key in such |
4103 | 0 | // cases. |
4104 | 0 | rv = aHostURI->GetAsciiHost(aBaseDomain); |
4105 | 0 | } |
4106 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4107 | 0 |
|
4108 | 0 | // aHost (and thus aBaseDomain) may be the string '.'. If so, fail. |
4109 | 0 | if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') |
4110 | 0 | return NS_ERROR_INVALID_ARG; |
4111 | 0 | |
4112 | 0 | // block any URIs without a host that aren't file:// URIs. |
4113 | 0 | if (aBaseDomain.IsEmpty()) { |
4114 | 0 | bool isFileURI = false; |
4115 | 0 | aHostURI->SchemeIs("file", &isFileURI); |
4116 | 0 | if (!isFileURI) |
4117 | 0 | return NS_ERROR_INVALID_ARG; |
4118 | 0 | } |
4119 | 0 | |
4120 | 0 | return NS_OK; |
4121 | 0 | } |
4122 | | |
4123 | | // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be |
4124 | | // "bbc.co.uk". This is done differently than GetBaseDomain(mTLDService, ): it is assumed |
4125 | | // that aHost is already normalized, and it may contain a leading dot |
4126 | | // (indicating that it represents a domain). A trailing dot may be present. |
4127 | | // If aHost is an IP address, an alias such as 'localhost', an eTLD such as |
4128 | | // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a |
4129 | | // leading dot will be treated as an error. |
4130 | | nsresult |
4131 | | nsCookieService::GetBaseDomainFromHost(nsIEffectiveTLDService *aTLDService, |
4132 | | const nsACString &aHost, |
4133 | | nsCString &aBaseDomain) |
4134 | 0 | { |
4135 | 0 | // aHost must not be the string '.'. |
4136 | 0 | if (aHost.Length() == 1 && aHost.Last() == '.') |
4137 | 0 | return NS_ERROR_INVALID_ARG; |
4138 | 0 | |
4139 | 0 | // aHost may contain a leading dot; if so, strip it now. |
4140 | 0 | bool domain = !aHost.IsEmpty() && aHost.First() == '.'; |
4141 | 0 |
|
4142 | 0 | // get the base domain. this will fail if the host contains a leading dot, |
4143 | 0 | // more than one trailing dot, or is otherwise malformed. |
4144 | 0 | nsresult rv = aTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain); |
4145 | 0 | if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || |
4146 | 0 | rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { |
4147 | 0 | // aHost is either an IP address, an alias such as 'localhost', an eTLD |
4148 | 0 | // such as 'co.uk', or the empty string. use the host as a key in such |
4149 | 0 | // cases; however, we reject any such hosts with a leading dot, since it |
4150 | 0 | // doesn't make sense for them to be domain cookies. |
4151 | 0 | if (domain) |
4152 | 0 | return NS_ERROR_INVALID_ARG; |
4153 | 0 | |
4154 | 0 | aBaseDomain = aHost; |
4155 | 0 | return NS_OK; |
4156 | 0 | } |
4157 | 0 | return rv; |
4158 | 0 | } |
4159 | | |
4160 | | // Normalizes the given hostname, component by component. ASCII/ACE |
4161 | | // components are lower-cased, and UTF-8 components are normalized per |
4162 | | // RFC 3454 and converted to ACE. |
4163 | | nsresult |
4164 | | nsCookieService::NormalizeHost(nsCString &aHost) |
4165 | 0 | { |
4166 | 0 | if (!IsASCII(aHost)) { |
4167 | 0 | nsAutoCString host; |
4168 | 0 | nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host); |
4169 | 0 | if (NS_FAILED(rv)) |
4170 | 0 | return rv; |
4171 | 0 | |
4172 | 0 | aHost = host; |
4173 | 0 | } |
4174 | 0 |
|
4175 | 0 | ToLowerCase(aHost); |
4176 | 0 | return NS_OK; |
4177 | 0 | } |
4178 | | |
4179 | | // returns true if 'a' is equal to or a subdomain of 'b', |
4180 | | // assuming no leading dots are present. |
4181 | | static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b) |
4182 | 0 | { |
4183 | 0 | if (a == b) |
4184 | 0 | return true; |
4185 | 0 | if (a.Length() > b.Length()) |
4186 | 0 | return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b); |
4187 | 0 | return false; |
4188 | 0 | } |
4189 | | |
4190 | | CookieStatus |
4191 | | nsCookieService::CheckPrefs(nsICookiePermission *aPermissionService, |
4192 | | uint8_t aCookieBehavior, |
4193 | | bool aThirdPartySession, |
4194 | | bool aThirdPartyNonsecureSession, |
4195 | | nsIURI *aHostURI, |
4196 | | bool aIsForeign, |
4197 | | bool aIsTrackingResource, |
4198 | | bool aFirstPartyStorageAccessGranted, |
4199 | | const char *aCookieHeader, |
4200 | | const int aNumOfCookies, |
4201 | | const OriginAttributes &aOriginAttrs, |
4202 | | uint32_t *aRejectedReason) |
4203 | 0 | { |
4204 | 0 | nsresult rv; |
4205 | 0 |
|
4206 | 0 | // Let's use a internal value in order to avoid a null check on |
4207 | 0 | // aRejectedReason everywhere. |
4208 | 0 | uint32_t rejectedReason = 0; |
4209 | 0 | if (!aRejectedReason) { |
4210 | 0 | aRejectedReason = &rejectedReason; |
4211 | 0 | } |
4212 | 0 |
|
4213 | 0 | *aRejectedReason = 0; |
4214 | 0 |
|
4215 | 0 | // don't let ftp sites get/set cookies (could be a security issue) |
4216 | 0 | bool ftp; |
4217 | 0 | if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) { |
4218 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies"); |
4219 | 0 | return STATUS_REJECTED_WITH_ERROR; |
4220 | 0 | } |
4221 | 0 |
|
4222 | 0 | nsCOMPtr<nsIPrincipal> principal = |
4223 | 0 | BasePrincipal::CreateCodebasePrincipal(aHostURI, aOriginAttrs); |
4224 | 0 |
|
4225 | 0 | if (!principal) { |
4226 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "non-codebase principals cannot get/set cookies"); |
4227 | 0 | return STATUS_REJECTED_WITH_ERROR; |
4228 | 0 | } |
4229 | 0 |
|
4230 | 0 | // check the permission list first; if we find an entry, it overrides |
4231 | 0 | // default prefs. see bug 184059. |
4232 | 0 | if (aPermissionService) { |
4233 | 0 | nsCookieAccess access; |
4234 | 0 | // Not passing an nsIChannel here is probably OK; our implementation |
4235 | 0 | // doesn't do anything with it anyway. |
4236 | 0 | rv = aPermissionService->CanAccess(principal, &access); |
4237 | 0 |
|
4238 | 0 | // if we found an entry, use it |
4239 | 0 | if (NS_SUCCEEDED(rv)) { |
4240 | 0 | switch (access) { |
4241 | 0 | case nsICookiePermission::ACCESS_DENY: |
4242 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, |
4243 | 0 | aCookieHeader, "cookies are blocked for this site"); |
4244 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; |
4245 | 0 | return STATUS_REJECTED; |
4246 | 0 |
|
4247 | 0 | case nsICookiePermission::ACCESS_ALLOW: |
4248 | 0 | return STATUS_ACCEPTED; |
4249 | 0 |
|
4250 | 0 | case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY: |
4251 | 0 | if (aIsForeign) { |
4252 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, |
4253 | 0 | aCookieHeader, "third party cookies are blocked " |
4254 | 0 | "for this site"); |
4255 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; |
4256 | 0 | return STATUS_REJECTED; |
4257 | 0 |
|
4258 | 0 | } |
4259 | 0 | return STATUS_ACCEPTED; |
4260 | 0 |
|
4261 | 0 | case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY: |
4262 | 0 | if (!aIsForeign) |
4263 | 0 | return STATUS_ACCEPTED; |
4264 | 0 | if (aNumOfCookies == 0) { |
4265 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, |
4266 | 0 | aCookieHeader, "third party cookies are blocked " |
4267 | 0 | "for this site"); |
4268 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_BY_PERMISSION; |
4269 | 0 | return STATUS_REJECTED; |
4270 | 0 | } |
4271 | 0 | return STATUS_ACCEPTED; |
4272 | 0 | } |
4273 | 0 | } |
4274 | 0 | } |
4275 | 0 |
|
4276 | 0 | // No cookies allowed if this request comes from a tracker, in a 3rd party |
4277 | 0 | // context, when anti-tracking protection is enabled and when we don't have |
4278 | 0 | // access to the first-party cookie jar. |
4279 | 0 | if (aIsForeign && aIsTrackingResource && !aFirstPartyStorageAccessGranted && |
4280 | 0 | aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER) { |
4281 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled in trackers"); |
4282 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_TRACKER; |
4283 | 0 | return STATUS_REJECTED; |
4284 | 0 | } |
4285 | 0 |
|
4286 | 0 | // check default prefs |
4287 | 0 | if (aCookieBehavior == nsICookieService::BEHAVIOR_REJECT) { |
4288 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled"); |
4289 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_ALL; |
4290 | 0 | return STATUS_REJECTED; |
4291 | 0 | } |
4292 | 0 |
|
4293 | 0 | // check if cookie is foreign |
4294 | 0 | if (aIsForeign) { |
4295 | 0 | // Check aFirstPartyStorageAccessGranted when rejecting all third-party cookies, |
4296 | 0 | // so that we take things such as the content blocking allow list into account. |
4297 | 0 | if (aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN && |
4298 | 0 | !aFirstPartyStorageAccessGranted) { |
4299 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party"); |
4300 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; |
4301 | 0 | return STATUS_REJECTED; |
4302 | 0 | } |
4303 | 0 |
|
4304 | 0 | if (aCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN) { |
4305 | 0 | if (aNumOfCookies == 0) { |
4306 | 0 | COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party"); |
4307 | 0 | *aRejectedReason = nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN; |
4308 | 0 | return STATUS_REJECTED; |
4309 | 0 | } |
4310 | 0 | } |
4311 | 0 |
|
4312 | 0 | MOZ_ASSERT(aCookieBehavior == nsICookieService::BEHAVIOR_ACCEPT || |
4313 | 0 | aCookieBehavior == nsICookieService::BEHAVIOR_LIMIT_FOREIGN || |
4314 | 0 | // But with permission granted. |
4315 | 0 | aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_FOREIGN || |
4316 | 0 | aCookieBehavior == nsICookieService::BEHAVIOR_REJECT_TRACKER); |
4317 | 0 |
|
4318 | 0 | if (aThirdPartySession) |
4319 | 0 | return STATUS_ACCEPT_SESSION; |
4320 | 0 | |
4321 | 0 | if (aThirdPartyNonsecureSession) { |
4322 | 0 | bool isHTTPS = false; |
4323 | 0 | aHostURI->SchemeIs("https", &isHTTPS); |
4324 | 0 | if (!isHTTPS) |
4325 | 0 | return STATUS_ACCEPT_SESSION; |
4326 | 0 | } |
4327 | 0 | } |
4328 | 0 | |
4329 | 0 | // if nothing has complained, accept cookie |
4330 | 0 | return STATUS_ACCEPTED; |
4331 | 0 | } |
4332 | | |
4333 | | // processes domain attribute, and returns true if host has permission to set for this domain. |
4334 | | bool |
4335 | | nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes, |
4336 | | nsIURI *aHostURI, |
4337 | | const nsCString &aBaseDomain, |
4338 | | bool aRequireHostMatch) |
4339 | 0 | { |
4340 | 0 | // Note: The logic in this function is mirrored in |
4341 | 0 | // toolkit/components/extensions/ext-cookies.js:checkSetCookiePermissions(). |
4342 | 0 | // If it changes, please update that function, or file a bug for someone |
4343 | 0 | // else to do so. |
4344 | 0 |
|
4345 | 0 | // get host from aHostURI |
4346 | 0 | nsAutoCString hostFromURI; |
4347 | 0 | aHostURI->GetAsciiHost(hostFromURI); |
4348 | 0 |
|
4349 | 0 | // if a domain is given, check the host has permission |
4350 | 0 | if (!aCookieAttributes.host.IsEmpty()) { |
4351 | 0 | // Tolerate leading '.' characters, but not if it's otherwise an empty host. |
4352 | 0 | if (aCookieAttributes.host.Length() > 1 && |
4353 | 0 | aCookieAttributes.host.First() == '.') { |
4354 | 0 | aCookieAttributes.host.Cut(0, 1); |
4355 | 0 | } |
4356 | 0 |
|
4357 | 0 | // switch to lowercase now, to avoid case-insensitive compares everywhere |
4358 | 0 | ToLowerCase(aCookieAttributes.host); |
4359 | 0 |
|
4360 | 0 | // check whether the host is either an IP address, an alias such as |
4361 | 0 | // 'localhost', an eTLD such as 'co.uk', or the empty string. in these |
4362 | 0 | // cases, require an exact string match for the domain, and leave the cookie |
4363 | 0 | // as a non-domain one. bug 105917 originally noted the requirement to deal |
4364 | 0 | // with IP addresses. |
4365 | 0 | if (aRequireHostMatch) |
4366 | 0 | return hostFromURI.Equals(aCookieAttributes.host); |
4367 | 0 | |
4368 | 0 | // ensure the proposed domain is derived from the base domain; and also |
4369 | 0 | // that the host domain is derived from the proposed domain (per RFC2109). |
4370 | 0 | if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) && |
4371 | 0 | IsSubdomainOf(hostFromURI, aCookieAttributes.host)) { |
4372 | 0 | // prepend a dot to indicate a domain cookie |
4373 | 0 | aCookieAttributes.host.InsertLiteral(".", 0); |
4374 | 0 | return true; |
4375 | 0 | } |
4376 | 0 | |
4377 | 0 | /* |
4378 | 0 | * note: RFC2109 section 4.3.2 requires that we check the following: |
4379 | 0 | * that the portion of host not in domain does not contain a dot. |
4380 | 0 | * this prevents hosts of the form x.y.co.nz from setting cookies in the |
4381 | 0 | * entire .co.nz domain. however, it's only a only a partial solution and |
4382 | 0 | * it breaks sites (IE doesn't enforce it), so we don't perform this check. |
4383 | 0 | */ |
4384 | 0 | return false; |
4385 | 0 | } |
4386 | 0 | |
4387 | 0 | // no domain specified, use hostFromURI |
4388 | 0 | aCookieAttributes.host = hostFromURI; |
4389 | 0 | return true; |
4390 | 0 | } |
4391 | | |
4392 | | nsAutoCString |
4393 | | nsCookieService::GetPathFromURI(nsIURI* aHostURI) |
4394 | 0 | { |
4395 | 0 | // strip down everything after the last slash to get the path, |
4396 | 0 | // ignoring slashes in the query string part. |
4397 | 0 | // if we can QI to nsIURL, that'll take care of the query string portion. |
4398 | 0 | // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash. |
4399 | 0 | nsAutoCString path; |
4400 | 0 | nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI); |
4401 | 0 | if (hostURL) { |
4402 | 0 | hostURL->GetDirectory(path); |
4403 | 0 | } else { |
4404 | 0 | aHostURI->GetPathQueryRef(path); |
4405 | 0 | int32_t slash = path.RFindChar('/'); |
4406 | 0 | if (slash != kNotFound) { |
4407 | 0 | path.Truncate(slash + 1); |
4408 | 0 | } |
4409 | 0 | } |
4410 | 0 | return path; |
4411 | 0 | } |
4412 | | |
4413 | | bool |
4414 | | nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes, |
4415 | | nsIURI *aHostURI) |
4416 | 0 | { |
4417 | 0 | // if a path is given, check the host has permission |
4418 | 0 | if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') { |
4419 | 0 | aCookieAttributes.path = GetPathFromURI(aHostURI); |
4420 | 0 |
|
4421 | | #if 0 |
4422 | | } else { |
4423 | | /** |
4424 | | * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site |
4425 | | * cannot set a cookie for a path that it is not on. See bug 155083. However this patch |
4426 | | * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has |
4427 | | * been disabled, unless we can evangelize these sites. |
4428 | | */ |
4429 | | // get path from aHostURI |
4430 | | nsAutoCString pathFromURI; |
4431 | | if (NS_FAILED(aHostURI->GetPathQueryRef(pathFromURI)) || |
4432 | | !StringBeginsWith(pathFromURI, aCookieAttributes.path)) { |
4433 | | return false; |
4434 | | } |
4435 | | #endif |
4436 | | } |
4437 | 0 |
|
4438 | 0 | if (aCookieAttributes.path.Length() > kMaxBytesPerPath || |
4439 | 0 | aCookieAttributes.path.Contains('\t')) |
4440 | 0 | return false; |
4441 | 0 | |
4442 | 0 | return true; |
4443 | 0 | } |
4444 | | |
4445 | | // CheckPrefixes |
4446 | | // |
4447 | | // Reject cookies whose name starts with the magic prefixes from |
4448 | | // https://tools.ietf.org/html/draft-ietf-httpbis-cookie-prefixes-00 |
4449 | | // if they do not meet the criteria required by the prefix. |
4450 | | // |
4451 | | // Must not be called until after CheckDomain() and CheckPath() have |
4452 | | // regularized and validated the nsCookieAttributes values! |
4453 | | bool |
4454 | | nsCookieService::CheckPrefixes(nsCookieAttributes &aCookieAttributes, |
4455 | | bool aSecureRequest) |
4456 | 0 | { |
4457 | 0 | static const char kSecure[] = "__Secure-"; |
4458 | 0 | static const char kHost[] = "__Host-"; |
4459 | 0 | static const int kSecureLen = sizeof( kSecure ) - 1; |
4460 | 0 | static const int kHostLen = sizeof( kHost ) - 1; |
4461 | 0 |
|
4462 | 0 | bool isSecure = strncmp( aCookieAttributes.name.get(), kSecure, kSecureLen ) == 0; |
4463 | 0 | bool isHost = strncmp( aCookieAttributes.name.get(), kHost, kHostLen ) == 0; |
4464 | 0 |
|
4465 | 0 | if ( !isSecure && !isHost ) { |
4466 | 0 | // not one of the magic prefixes: carry on |
4467 | 0 | return true; |
4468 | 0 | } |
4469 | 0 | |
4470 | 0 | if ( !aSecureRequest || !aCookieAttributes.isSecure ) { |
4471 | 0 | // the magic prefixes may only be used from a secure request and |
4472 | 0 | // the secure attribute must be set on the cookie |
4473 | 0 | return false; |
4474 | 0 | } |
4475 | 0 | |
4476 | 0 | if ( isHost ) { |
4477 | 0 | // The host prefix requires that the path is "/" and that the cookie |
4478 | 0 | // had no domain attribute. CheckDomain() and CheckPath() MUST be run |
4479 | 0 | // first to make sure invalid attributes are rejected and to regularlize |
4480 | 0 | // them. In particular all explicit domain attributes result in a host |
4481 | 0 | // that starts with a dot, and if the host doesn't start with a dot it |
4482 | 0 | // correctly matches the true host. |
4483 | 0 | if ( aCookieAttributes.host[0] == '.' || |
4484 | 0 | !aCookieAttributes.path.EqualsLiteral( "/" )) { |
4485 | 0 | return false; |
4486 | 0 | } |
4487 | 0 | } |
4488 | 0 | |
4489 | 0 | return true; |
4490 | 0 | } |
4491 | | |
4492 | | bool |
4493 | | nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes, |
4494 | | int64_t aServerTime, |
4495 | | int64_t aCurrentTime) |
4496 | 0 | { |
4497 | 0 | /* Determine when the cookie should expire. This is done by taking the difference between |
4498 | 0 | * the server time and the time the server wants the cookie to expire, and adding that |
4499 | 0 | * difference to the client time. This localizes the client time regardless of whether or |
4500 | 0 | * not the TZ environment variable was set on the client. |
4501 | 0 | * |
4502 | 0 | * Note: We need to consider accounting for network lag here, per RFC. |
4503 | 0 | */ |
4504 | 0 | // check for max-age attribute first; this overrides expires attribute |
4505 | 0 | if (!aCookieAttributes.maxage.IsEmpty()) { |
4506 | 0 | // obtain numeric value of maxageAttribute |
4507 | 0 | int64_t maxage; |
4508 | 0 | int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage); |
4509 | 0 |
|
4510 | 0 | // default to session cookie if the conversion failed |
4511 | 0 | if (numInts != 1) { |
4512 | 0 | return true; |
4513 | 0 | } |
4514 | 0 | |
4515 | 0 | // if this addition overflows, expiryTime will be less than currentTime |
4516 | 0 | // and the cookie will be expired - that's okay. |
4517 | 0 | aCookieAttributes.expiryTime = aCurrentTime + maxage; |
4518 | 0 |
|
4519 | 0 | // check for expires attribute |
4520 | 0 | } else if (!aCookieAttributes.expires.IsEmpty()) { |
4521 | 0 | PRTime expires; |
4522 | 0 |
|
4523 | 0 | // parse expiry time |
4524 | 0 | if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) { |
4525 | 0 | return true; |
4526 | 0 | } |
4527 | 0 | |
4528 | 0 | // If set-cookie used absolute time to set expiration, and it can't use |
4529 | 0 | // client time to set expiration. |
4530 | 0 | // Because if current time be set in the future, but the cookie expire |
4531 | 0 | // time be set less than current time and more than server time. |
4532 | 0 | // The cookie item have to be used to the expired cookie. |
4533 | 0 | aCookieAttributes.expiryTime = expires / int64_t(PR_USEC_PER_SEC); |
4534 | 0 |
|
4535 | 0 | // default to session cookie if no attributes found |
4536 | 0 | } else { |
4537 | 0 | return true; |
4538 | 0 | } |
4539 | 0 | |
4540 | 0 | return false; |
4541 | 0 | } |
4542 | | |
4543 | | /****************************************************************************** |
4544 | | * nsCookieService impl: |
4545 | | * private cookielist management functions |
4546 | | ******************************************************************************/ |
4547 | | |
4548 | | void |
4549 | | nsCookieService::RemoveAllFromMemory() |
4550 | 0 | { |
4551 | 0 | // clearing the hashtable will call each nsCookieEntry's dtor, |
4552 | 0 | // which releases all their respective children. |
4553 | 0 | mDBState->hostTable.Clear(); |
4554 | 0 | mDBState->cookieCount = 0; |
4555 | 0 | mDBState->cookieOldestTime = INT64_MAX; |
4556 | 0 | } |
4557 | | |
4558 | | // comparator class for lastaccessed times of cookies. |
4559 | | class CompareCookiesByAge { |
4560 | | public: |
4561 | | bool Equals(const nsListIter &a, const nsListIter &b) const |
4562 | 0 | { |
4563 | 0 | return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() && |
4564 | 0 | a.Cookie()->CreationTime() == b.Cookie()->CreationTime(); |
4565 | 0 | } |
4566 | | |
4567 | | bool LessThan(const nsListIter &a, const nsListIter &b) const |
4568 | 0 | { |
4569 | 0 | // compare by lastAccessed time, and tiebreak by creationTime. |
4570 | 0 | int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed(); |
4571 | 0 | if (result != 0) |
4572 | 0 | return result < 0; |
4573 | 0 | |
4574 | 0 | return a.Cookie()->CreationTime() < b.Cookie()->CreationTime(); |
4575 | 0 | } |
4576 | | }; |
4577 | | |
4578 | | // purges expired and old cookies in a batch operation. |
4579 | | already_AddRefed<nsIArray> |
4580 | | nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec) |
4581 | 0 | { |
4582 | 0 | NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty"); |
4583 | 0 |
|
4584 | 0 | uint32_t initialCookieCount = mDBState->cookieCount; |
4585 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
4586 | 0 | ("PurgeCookies(): beginning purge with %" PRIu32 " cookies and %" PRId64 " oldest age", |
4587 | 0 | mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime)); |
4588 | 0 |
|
4589 | 0 | typedef nsTArray<nsListIter> PurgeList; |
4590 | 0 | PurgeList purgeList(kMaxNumberOfCookies); |
4591 | 0 |
|
4592 | 0 | nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID); |
4593 | 0 |
|
4594 | 0 | // Create a params array to batch the removals. This is OK here because |
4595 | 0 | // all the removals are in order, and there are no interleaved additions. |
4596 | 0 | mozIStorageAsyncStatement *stmt = mDBState->stmtDelete; |
4597 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
4598 | 0 | if (mDBState->dbConn) { |
4599 | 0 | stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
4600 | 0 | } |
4601 | 0 |
|
4602 | 0 | int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC; |
4603 | 0 | int64_t purgeTime = aCurrentTimeInUsec - mCookiePurgeAge; |
4604 | 0 | int64_t oldestTime = INT64_MAX; |
4605 | 0 |
|
4606 | 0 | for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) { |
4607 | 0 | nsCookieEntry* entry = iter.Get(); |
4608 | 0 |
|
4609 | 0 | const nsCookieEntry::ArrayType& cookies = entry->GetCookies(); |
4610 | 0 | auto length = cookies.Length(); |
4611 | 0 | for (nsCookieEntry::IndexType i = 0; i < length; ) { |
4612 | 0 | nsListIter iter(entry, i); |
4613 | 0 | nsCookie* cookie = cookies[i]; |
4614 | 0 |
|
4615 | 0 | // check if the cookie has expired |
4616 | 0 | if (cookie->Expiry() <= currentTime) { |
4617 | 0 | removedList->AppendElement(cookie); |
4618 | 0 | COOKIE_LOGEVICTED(cookie, "Cookie expired"); |
4619 | 0 |
|
4620 | 0 | // remove from list; do not increment our iterator, but stop if we're |
4621 | 0 | // done already. |
4622 | 0 | gCookieService->RemoveCookieFromList(iter, paramsArray); |
4623 | 0 | if (i == --length) { |
4624 | 0 | break; |
4625 | 0 | } |
4626 | 0 | } else { |
4627 | 0 | // check if the cookie is over the age limit |
4628 | 0 | if (cookie->LastAccessed() <= purgeTime) { |
4629 | 0 | purgeList.AppendElement(iter); |
4630 | 0 |
|
4631 | 0 | } else if (cookie->LastAccessed() < oldestTime) { |
4632 | 0 | // reset our indicator |
4633 | 0 | oldestTime = cookie->LastAccessed(); |
4634 | 0 | } |
4635 | 0 |
|
4636 | 0 | ++i; |
4637 | 0 | } |
4638 | 0 | MOZ_ASSERT(length == cookies.Length()); |
4639 | 0 | } |
4640 | 0 | } |
4641 | 0 |
|
4642 | 0 | uint32_t postExpiryCookieCount = mDBState->cookieCount; |
4643 | 0 |
|
4644 | 0 | // now we have a list of iterators for cookies over the age limit. |
4645 | 0 | // sort them by age, and then we'll see how many to remove... |
4646 | 0 | purgeList.Sort(CompareCookiesByAge()); |
4647 | 0 |
|
4648 | 0 | // only remove old cookies until we reach the max cookie limit, no more. |
4649 | 0 | uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ? |
4650 | 0 | mDBState->cookieCount - mMaxNumberOfCookies : 0; |
4651 | 0 | if (purgeList.Length() > excess) { |
4652 | 0 | // We're not purging everything in the list, so update our indicator. |
4653 | 0 | oldestTime = purgeList[excess].Cookie()->LastAccessed(); |
4654 | 0 |
|
4655 | 0 | purgeList.SetLength(excess); |
4656 | 0 | } |
4657 | 0 |
|
4658 | 0 | // sort the list again, this time grouping cookies with a common entryclass |
4659 | 0 | // together, and with ascending index. this allows us to iterate backwards |
4660 | 0 | // over the list removing cookies, without having to adjust indexes as we go. |
4661 | 0 | purgeList.Sort(CompareCookiesByIndex()); |
4662 | 0 | for (PurgeList::index_type i = purgeList.Length(); i--; ) { |
4663 | 0 | nsCookie *cookie = purgeList[i].Cookie(); |
4664 | 0 | removedList->AppendElement(cookie); |
4665 | 0 | COOKIE_LOGEVICTED(cookie, "Cookie too old"); |
4666 | 0 |
|
4667 | 0 | RemoveCookieFromList(purgeList[i], paramsArray); |
4668 | 0 | } |
4669 | 0 |
|
4670 | 0 | // Update the database if we have entries to purge. |
4671 | 0 | if (paramsArray) { |
4672 | 0 | uint32_t length; |
4673 | 0 | paramsArray->GetLength(&length); |
4674 | 0 | if (length) { |
4675 | 0 | DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
4676 | 0 | NS_ASSERT_SUCCESS(rv); |
4677 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
4678 | 0 | rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle)); |
4679 | 0 | NS_ASSERT_SUCCESS(rv); |
4680 | 0 | } |
4681 | 0 | } |
4682 | 0 |
|
4683 | 0 | // reset the oldest time indicator |
4684 | 0 | mDBState->cookieOldestTime = oldestTime; |
4685 | 0 |
|
4686 | 0 | COOKIE_LOGSTRING(LogLevel::Debug, |
4687 | 0 | ("PurgeCookies(): %" PRIu32 " expired; %" PRIu32 " purged; %" PRIu32 |
4688 | 0 | " remain; %" PRId64 " oldest age", |
4689 | 0 | initialCookieCount - postExpiryCookieCount, |
4690 | 0 | postExpiryCookieCount - mDBState->cookieCount, |
4691 | 0 | mDBState->cookieCount, |
4692 | 0 | aCurrentTimeInUsec - mDBState->cookieOldestTime)); |
4693 | 0 |
|
4694 | 0 | return removedList.forget(); |
4695 | 0 | } |
4696 | | |
4697 | | // find whether a given cookie has been previously set. this is provided by the |
4698 | | // nsICookieManager interface. |
4699 | | NS_IMETHODIMP |
4700 | | nsCookieService::CookieExists(const nsACString& aHost, |
4701 | | const nsACString& aPath, |
4702 | | const nsACString& aName, |
4703 | | JS::HandleValue aOriginAttributes, |
4704 | | JSContext* aCx, |
4705 | | bool* aFoundCookie) |
4706 | 0 | { |
4707 | 0 | NS_ENSURE_ARG_POINTER(aCx); |
4708 | 0 | NS_ENSURE_ARG_POINTER(aFoundCookie); |
4709 | 0 |
|
4710 | 0 | OriginAttributes attrs; |
4711 | 0 | if (!aOriginAttributes.isObject() || |
4712 | 0 | !attrs.Init(aCx, aOriginAttributes)) { |
4713 | 0 | return NS_ERROR_INVALID_ARG; |
4714 | 0 | } |
4715 | 0 | return CookieExistsNative(aHost, aPath, aName, &attrs, aFoundCookie); |
4716 | 0 | } |
4717 | | |
4718 | | NS_IMETHODIMP_(nsresult) |
4719 | | nsCookieService::CookieExistsNative(const nsACString& aHost, |
4720 | | const nsACString& aPath, |
4721 | | const nsACString& aName, |
4722 | | OriginAttributes* aOriginAttributes, |
4723 | | bool* aFoundCookie) |
4724 | 0 | { |
4725 | 0 | NS_ENSURE_ARG_POINTER(aOriginAttributes); |
4726 | 0 | NS_ENSURE_ARG_POINTER(aFoundCookie); |
4727 | 0 |
|
4728 | 0 | if (!mDBState) { |
4729 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
4730 | 0 | return NS_ERROR_NOT_AVAILABLE; |
4731 | 0 | } |
4732 | 0 |
|
4733 | 0 | EnsureReadComplete(true); |
4734 | 0 |
|
4735 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
4736 | 0 | mDBState = (aOriginAttributes->mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState; |
4737 | 0 |
|
4738 | 0 | nsAutoCString baseDomain; |
4739 | 0 | nsresult rv = GetBaseDomainFromHost(mTLDService, aHost, baseDomain); |
4740 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4741 | 0 |
|
4742 | 0 | nsListIter iter; |
4743 | 0 | *aFoundCookie = FindCookie(nsCookieKey(baseDomain, *aOriginAttributes), |
4744 | 0 | PromiseFlatCString(aHost), |
4745 | 0 | PromiseFlatCString(aName), |
4746 | 0 | PromiseFlatCString(aPath), iter); |
4747 | 0 | return NS_OK; |
4748 | 0 | } |
4749 | | |
4750 | | // Cookie comparator for the priority queue used in FindStaleCookies. |
4751 | | // Note that the expired cookie has the highest priority. |
4752 | | // Other non-expired cookies are sorted by their age. |
4753 | | class CookieIterComparator { |
4754 | | private: |
4755 | | CompareCookiesByAge mAgeComparator; |
4756 | | int64_t mCurrentTime; |
4757 | | |
4758 | | public: |
4759 | | explicit CookieIterComparator(int64_t aTime) |
4760 | 0 | : mCurrentTime(aTime) {} |
4761 | | |
4762 | | bool LessThan(const nsListIter& lhs, const nsListIter& rhs) |
4763 | 0 | { |
4764 | 0 | bool lExpired = lhs.Cookie()->Expiry() <= mCurrentTime; |
4765 | 0 | bool rExpired = rhs.Cookie()->Expiry() <= mCurrentTime; |
4766 | 0 | if (lExpired && !rExpired) { |
4767 | 0 | return true; |
4768 | 0 | } |
4769 | 0 | |
4770 | 0 | if (!lExpired && rExpired) { |
4771 | 0 | return false; |
4772 | 0 | } |
4773 | 0 | |
4774 | 0 | return mAgeComparator.LessThan(lhs, rhs); |
4775 | 0 | } |
4776 | | }; |
4777 | | |
4778 | | // Given the output iter array and the count limit, find cookies |
4779 | | // sort by expiry and lastAccessed time. |
4780 | | void |
4781 | | nsCookieService::FindStaleCookies(nsCookieEntry *aEntry, |
4782 | | int64_t aCurrentTime, |
4783 | | const mozilla::Maybe<bool> &aIsSecure, |
4784 | | nsTArray<nsListIter>& aOutput, |
4785 | | uint32_t aLimit) |
4786 | 0 | { |
4787 | 0 | MOZ_ASSERT(aLimit); |
4788 | 0 |
|
4789 | 0 | const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies(); |
4790 | 0 | aOutput.Clear(); |
4791 | 0 |
|
4792 | 0 | CookieIterComparator comp(aCurrentTime); |
4793 | 0 | nsTPriorityQueue<nsListIter, CookieIterComparator> queue(comp); |
4794 | 0 |
|
4795 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
4796 | 0 | nsCookie *cookie = cookies[i]; |
4797 | 0 |
|
4798 | 0 | if (cookie->Expiry() <= aCurrentTime) { |
4799 | 0 | queue.Push(nsListIter(aEntry, i)); |
4800 | 0 | continue; |
4801 | 0 | } |
4802 | 0 | |
4803 | 0 | if (aIsSecure.isSome() && !aIsSecure.value()) { |
4804 | 0 | // We want to look for the non-secure cookie first time through, |
4805 | 0 | // then find the secure cookie the second time this function is called. |
4806 | 0 | if (cookie->IsSecure()) { |
4807 | 0 | continue; |
4808 | 0 | } |
4809 | 0 | } |
4810 | 0 | |
4811 | 0 | queue.Push(nsListIter(aEntry, i)); |
4812 | 0 | } |
4813 | 0 |
|
4814 | 0 | uint32_t count = 0; |
4815 | 0 | while (!queue.IsEmpty() && count < aLimit) { |
4816 | 0 | aOutput.AppendElement(queue.Pop()); |
4817 | 0 | count++; |
4818 | 0 | } |
4819 | 0 | } |
4820 | | |
4821 | | void |
4822 | | nsCookieService::TelemetryForEvictingStaleCookie(nsCookie *aEvicted, |
4823 | | int64_t oldestCookieTime) |
4824 | 0 | { |
4825 | 0 | // We need to record the evicting cookie to telemetry. |
4826 | 0 | if (!aEvicted->IsSecure()) { |
4827 | 0 | if (aEvicted->LastAccessed() > oldestCookieTime) { |
4828 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
4829 | 0 | EVICTED_NEWER_INSECURE); |
4830 | 0 | } else { |
4831 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
4832 | 0 | EVICTED_OLDEST_COOKIE); |
4833 | 0 | } |
4834 | 0 | } else { |
4835 | 0 | Telemetry::Accumulate(Telemetry::COOKIE_LEAVE_SECURE_ALONE, |
4836 | 0 | EVICTED_PREFERRED_COOKIE); |
4837 | 0 | } |
4838 | 0 | } |
4839 | | |
4840 | | // count the number of cookies stored by a particular host. this is provided by the |
4841 | | // nsICookieManager interface. |
4842 | | NS_IMETHODIMP |
4843 | | nsCookieService::CountCookiesFromHost(const nsACString &aHost, |
4844 | | uint32_t *aCountFromHost) |
4845 | 0 | { |
4846 | 0 | if (!mDBState) { |
4847 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
4848 | 0 | return NS_ERROR_NOT_AVAILABLE; |
4849 | 0 | } |
4850 | 0 |
|
4851 | 0 | EnsureReadComplete(true); |
4852 | 0 |
|
4853 | 0 | // first, normalize the hostname, and fail if it contains illegal characters. |
4854 | 0 | nsAutoCString host(aHost); |
4855 | 0 | nsresult rv = NormalizeHost(host); |
4856 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4857 | 0 |
|
4858 | 0 | nsAutoCString baseDomain; |
4859 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
4860 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4861 | 0 |
|
4862 | 0 | nsCookieKey key = DEFAULT_APP_KEY(baseDomain); |
4863 | 0 |
|
4864 | 0 | // Return a count of all cookies, including expired. |
4865 | 0 | nsCookieEntry *entry = mDBState->hostTable.GetEntry(key); |
4866 | 0 | *aCountFromHost = entry ? entry->GetCookies().Length() : 0; |
4867 | 0 | return NS_OK; |
4868 | 0 | } |
4869 | | |
4870 | | // get an enumerator of cookies stored by a particular host. this is provided by the |
4871 | | // nsICookieManager interface. |
4872 | | NS_IMETHODIMP |
4873 | | nsCookieService::GetCookiesFromHost(const nsACString &aHost, |
4874 | | JS::HandleValue aOriginAttributes, |
4875 | | JSContext* aCx, |
4876 | | nsISimpleEnumerator **aEnumerator) |
4877 | 0 | { |
4878 | 0 | if (!mDBState) { |
4879 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
4880 | 0 | return NS_ERROR_NOT_AVAILABLE; |
4881 | 0 | } |
4882 | 0 |
|
4883 | 0 | EnsureReadComplete(true); |
4884 | 0 |
|
4885 | 0 | // first, normalize the hostname, and fail if it contains illegal characters. |
4886 | 0 | nsAutoCString host(aHost); |
4887 | 0 | nsresult rv = NormalizeHost(host); |
4888 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4889 | 0 |
|
4890 | 0 | nsAutoCString baseDomain; |
4891 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
4892 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4893 | 0 |
|
4894 | 0 | OriginAttributes attrs; |
4895 | 0 | if (!aOriginAttributes.isObject() || |
4896 | 0 | !attrs.Init(aCx, aOriginAttributes)) { |
4897 | 0 | return NS_ERROR_INVALID_ARG; |
4898 | 0 | } |
4899 | 0 | |
4900 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
4901 | 0 | mDBState = (attrs.mPrivateBrowsingId > 0) ? mPrivateDBState : mDefaultDBState; |
4902 | 0 |
|
4903 | 0 | nsCookieKey key = nsCookieKey(baseDomain, attrs); |
4904 | 0 |
|
4905 | 0 | nsCookieEntry *entry = mDBState->hostTable.GetEntry(key); |
4906 | 0 | if (!entry) |
4907 | 0 | return NS_NewEmptyEnumerator(aEnumerator); |
4908 | 0 | |
4909 | 0 | nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost); |
4910 | 0 | const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
4911 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
4912 | 0 | cookieList.AppendObject(cookies[i]); |
4913 | 0 | } |
4914 | 0 |
|
4915 | 0 | return NS_NewArrayEnumerator(aEnumerator, cookieList, NS_GET_IID(nsICookie2)); |
4916 | 0 | } |
4917 | | |
4918 | | NS_IMETHODIMP |
4919 | | nsCookieService::GetCookiesWithOriginAttributes(const nsAString& aPattern, |
4920 | | const nsACString& aHost, |
4921 | | nsISimpleEnumerator **aEnumerator) |
4922 | 0 | { |
4923 | 0 | mozilla::OriginAttributesPattern pattern; |
4924 | 0 | if (!pattern.Init(aPattern)) { |
4925 | 0 | return NS_ERROR_INVALID_ARG; |
4926 | 0 | } |
4927 | 0 | |
4928 | 0 | nsAutoCString host(aHost); |
4929 | 0 | nsresult rv = NormalizeHost(host); |
4930 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4931 | 0 |
|
4932 | 0 | nsAutoCString baseDomain; |
4933 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
4934 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4935 | 0 |
|
4936 | 0 | return GetCookiesWithOriginAttributes(pattern, baseDomain, aEnumerator); |
4937 | 0 | } |
4938 | | |
4939 | | nsresult |
4940 | | nsCookieService::GetCookiesWithOriginAttributes( |
4941 | | const mozilla::OriginAttributesPattern& aPattern, |
4942 | | const nsCString& aBaseDomain, |
4943 | | nsISimpleEnumerator **aEnumerator) |
4944 | 0 | { |
4945 | 0 | if (!mDBState) { |
4946 | 0 | NS_WARNING("No DBState! Profile already closed?"); |
4947 | 0 | return NS_ERROR_NOT_AVAILABLE; |
4948 | 0 | } |
4949 | 0 | EnsureReadComplete(true); |
4950 | 0 |
|
4951 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
4952 | 0 | mDBState = (aPattern.mPrivateBrowsingId.WasPassed() && |
4953 | 0 | aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState; |
4954 | 0 |
|
4955 | 0 | nsCOMArray<nsICookie> cookies; |
4956 | 0 | for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) { |
4957 | 0 | nsCookieEntry* entry = iter.Get(); |
4958 | 0 |
|
4959 | 0 | if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) { |
4960 | 0 | continue; |
4961 | 0 | } |
4962 | 0 | |
4963 | 0 | if (!aPattern.Matches(entry->mOriginAttributes)) { |
4964 | 0 | continue; |
4965 | 0 | } |
4966 | 0 | |
4967 | 0 | const nsCookieEntry::ArrayType& entryCookies = entry->GetCookies(); |
4968 | 0 |
|
4969 | 0 | for (nsCookieEntry::IndexType i = 0; i < entryCookies.Length(); ++i) { |
4970 | 0 | cookies.AppendObject(entryCookies[i]); |
4971 | 0 | } |
4972 | 0 | } |
4973 | 0 |
|
4974 | 0 | return NS_NewArrayEnumerator(aEnumerator, cookies, NS_GET_IID(nsICookie2)); |
4975 | 0 | } |
4976 | | |
4977 | | NS_IMETHODIMP |
4978 | | nsCookieService::RemoveCookiesWithOriginAttributes(const nsAString& aPattern, |
4979 | | const nsACString& aHost) |
4980 | 0 | { |
4981 | 0 | MOZ_ASSERT(XRE_IsParentProcess()); |
4982 | 0 |
|
4983 | 0 | mozilla::OriginAttributesPattern pattern; |
4984 | 0 | if (!pattern.Init(aPattern)) { |
4985 | 0 | return NS_ERROR_INVALID_ARG; |
4986 | 0 | } |
4987 | 0 | |
4988 | 0 | nsAutoCString host(aHost); |
4989 | 0 | nsresult rv = NormalizeHost(host); |
4990 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4991 | 0 |
|
4992 | 0 | nsAutoCString baseDomain; |
4993 | 0 | rv = GetBaseDomainFromHost(mTLDService, host, baseDomain); |
4994 | 0 | NS_ENSURE_SUCCESS(rv, rv); |
4995 | 0 |
|
4996 | 0 | return RemoveCookiesWithOriginAttributes(pattern, baseDomain); |
4997 | 0 | } |
4998 | | |
4999 | | nsresult |
5000 | | nsCookieService::RemoveCookiesWithOriginAttributes( |
5001 | | const mozilla::OriginAttributesPattern& aPattern, |
5002 | | const nsCString& aBaseDomain) |
5003 | 0 | { |
5004 | 0 | if (!mDBState) { |
5005 | 0 | NS_WARNING("No DBState! Profile already close?"); |
5006 | 0 | return NS_ERROR_NOT_AVAILABLE; |
5007 | 0 | } |
5008 | 0 |
|
5009 | 0 | EnsureReadComplete(true); |
5010 | 0 |
|
5011 | 0 | AutoRestore<DBState*> savePrevDBState(mDBState); |
5012 | 0 | mDBState = (aPattern.mPrivateBrowsingId.WasPassed() && |
5013 | 0 | aPattern.mPrivateBrowsingId.Value() > 0) ? mPrivateDBState : mDefaultDBState; |
5014 | 0 |
|
5015 | 0 | mozStorageTransaction transaction(mDBState->dbConn, false); |
5016 | 0 | // Iterate the hash table of nsCookieEntry. |
5017 | 0 | for (auto iter = mDBState->hostTable.Iter(); !iter.Done(); iter.Next()) { |
5018 | 0 | nsCookieEntry* entry = iter.Get(); |
5019 | 0 |
|
5020 | 0 | if (!aBaseDomain.IsEmpty() && !aBaseDomain.Equals(entry->mBaseDomain)) { |
5021 | 0 | continue; |
5022 | 0 | } |
5023 | 0 | |
5024 | 0 | if (!aPattern.Matches(entry->mOriginAttributes)) { |
5025 | 0 | continue; |
5026 | 0 | } |
5027 | 0 | |
5028 | 0 | // Pattern matches. Delete all cookies within this nsCookieEntry. |
5029 | 0 | uint32_t cookiesCount = entry->GetCookies().Length(); |
5030 | 0 |
|
5031 | 0 | for (nsCookieEntry::IndexType i = 0 ; i < cookiesCount; ++i) { |
5032 | 0 | // Remove the first cookie from the list. |
5033 | 0 | nsListIter iter(entry, 0); |
5034 | 0 | RefPtr<nsCookie> cookie = iter.Cookie(); |
5035 | 0 |
|
5036 | 0 | // Remove the cookie. |
5037 | 0 | RemoveCookieFromList(iter); |
5038 | 0 |
|
5039 | 0 | if (cookie) { |
5040 | 0 | NotifyChanged(cookie, u"deleted"); |
5041 | 0 | } |
5042 | 0 | } |
5043 | 0 | } |
5044 | 0 | DebugOnly<nsresult> rv = transaction.Commit(); |
5045 | 0 | MOZ_ASSERT(NS_SUCCEEDED(rv)); |
5046 | 0 |
|
5047 | 0 | return NS_OK; |
5048 | 0 | } |
5049 | | |
5050 | | // find an secure cookie specified by host and name |
5051 | | bool |
5052 | | nsCookieService::FindSecureCookie(const nsCookieKey &aKey, |
5053 | | nsCookie *aCookie) |
5054 | 0 | { |
5055 | 0 | nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey); |
5056 | 0 | if (!entry) |
5057 | 0 | return false; |
5058 | 0 | |
5059 | 0 | const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
5060 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
5061 | 0 | nsCookie *cookie = cookies[i]; |
5062 | 0 | // isn't a match if insecure or a different name |
5063 | 0 | if (!cookie->IsSecure() || !aCookie->Name().Equals(cookie->Name())) |
5064 | 0 | continue; |
5065 | 0 | |
5066 | 0 | // The host must "domain-match" an existing cookie or vice-versa |
5067 | 0 | if (DomainMatches(cookie, aCookie->Host()) || |
5068 | 0 | DomainMatches(aCookie, cookie->Host())) { |
5069 | 0 | // If the path of new cookie and the path of existing cookie |
5070 | 0 | // aren't "/", then this situation needs to compare paths to |
5071 | 0 | // ensure only that a newly-created non-secure cookie does not |
5072 | 0 | // overlay an existing secure cookie. |
5073 | 0 | if (PathMatches(cookie, aCookie->Path())) { |
5074 | 0 | return true; |
5075 | 0 | } |
5076 | 0 | } |
5077 | 0 | } |
5078 | 0 |
|
5079 | 0 | return false; |
5080 | 0 | } |
5081 | | |
5082 | | // find an exact cookie specified by host, name, and path that hasn't expired. |
5083 | | bool |
5084 | | nsCookieService::FindCookie(const nsCookieKey &aKey, |
5085 | | const nsCString& aHost, |
5086 | | const nsCString& aName, |
5087 | | const nsCString& aPath, |
5088 | | nsListIter &aIter) |
5089 | 0 | { |
5090 | 0 | // Should |EnsureReadComplete| before. |
5091 | 0 | MOZ_ASSERT(mInitializedDBStates); |
5092 | 0 | MOZ_ASSERT(mInitializedDBConn); |
5093 | 0 |
|
5094 | 0 | nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey); |
5095 | 0 | if (!entry) |
5096 | 0 | return false; |
5097 | 0 | |
5098 | 0 | const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
5099 | 0 | for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
5100 | 0 | nsCookie *cookie = cookies[i]; |
5101 | 0 |
|
5102 | 0 | if (aHost.Equals(cookie->Host()) && |
5103 | 0 | aPath.Equals(cookie->Path()) && |
5104 | 0 | aName.Equals(cookie->Name())) { |
5105 | 0 | aIter = nsListIter(entry, i); |
5106 | 0 | return true; |
5107 | 0 | } |
5108 | 0 | } |
5109 | 0 |
|
5110 | 0 | return false; |
5111 | 0 | } |
5112 | | |
5113 | | // remove a cookie from the hashtable, and update the iterator state. |
5114 | | void |
5115 | | nsCookieService::RemoveCookieFromList(const nsListIter &aIter, |
5116 | | mozIStorageBindingParamsArray *aParamsArray) |
5117 | 0 | { |
5118 | 0 | // if it's a non-session cookie, remove it from the db |
5119 | 0 | if (!aIter.Cookie()->IsSession() && mDBState->dbConn) { |
5120 | 0 | // Use the asynchronous binding methods to ensure that we do not acquire |
5121 | 0 | // the database lock. |
5122 | 0 | mozIStorageAsyncStatement *stmt = mDBState->stmtDelete; |
5123 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray); |
5124 | 0 | if (!paramsArray) { |
5125 | 0 | stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
5126 | 0 | } |
5127 | 0 |
|
5128 | 0 | nsCOMPtr<mozIStorageBindingParams> params; |
5129 | 0 | paramsArray->NewBindingParams(getter_AddRefs(params)); |
5130 | 0 |
|
5131 | 0 | DebugOnly<nsresult> rv = |
5132 | 0 | params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), |
5133 | 0 | aIter.Cookie()->Name()); |
5134 | 0 | NS_ASSERT_SUCCESS(rv); |
5135 | 0 |
|
5136 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), |
5137 | 0 | aIter.Cookie()->Host()); |
5138 | 0 | NS_ASSERT_SUCCESS(rv); |
5139 | 0 |
|
5140 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), |
5141 | 0 | aIter.Cookie()->Path()); |
5142 | 0 | NS_ASSERT_SUCCESS(rv); |
5143 | 0 |
|
5144 | 0 | nsAutoCString suffix; |
5145 | 0 | aIter.Cookie()->OriginAttributesRef().CreateSuffix(suffix); |
5146 | 0 | rv = params->BindUTF8StringByName( |
5147 | 0 | NS_LITERAL_CSTRING("originAttributes"), suffix); |
5148 | 0 | NS_ASSERT_SUCCESS(rv); |
5149 | 0 |
|
5150 | 0 | rv = paramsArray->AddParams(params); |
5151 | 0 | NS_ASSERT_SUCCESS(rv); |
5152 | 0 |
|
5153 | 0 | // If we weren't given a params array, we'll need to remove it ourselves. |
5154 | 0 | if (!aParamsArray) { |
5155 | 0 | rv = stmt->BindParameters(paramsArray); |
5156 | 0 | NS_ASSERT_SUCCESS(rv); |
5157 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
5158 | 0 | rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle)); |
5159 | 0 | NS_ASSERT_SUCCESS(rv); |
5160 | 0 | } |
5161 | 0 | } |
5162 | 0 |
|
5163 | 0 | if (aIter.entry->GetCookies().Length() == 1) { |
5164 | 0 | // we're removing the last element in the array - so just remove the entry |
5165 | 0 | // from the hash. note that the entryclass' dtor will take care of |
5166 | 0 | // releasing this last element for us! |
5167 | 0 | mDBState->hostTable.RawRemoveEntry(aIter.entry); |
5168 | 0 |
|
5169 | 0 | } else { |
5170 | 0 | // just remove the element from the list |
5171 | 0 | aIter.entry->GetCookies().RemoveElementAt(aIter.index); |
5172 | 0 | } |
5173 | 0 |
|
5174 | 0 | --mDBState->cookieCount; |
5175 | 0 | } |
5176 | | |
5177 | | void |
5178 | | bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray, |
5179 | | const nsCookieKey &aKey, |
5180 | | const nsCookie *aCookie) |
5181 | 0 | { |
5182 | 0 | NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!"); |
5183 | 0 | NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!"); |
5184 | 0 |
|
5185 | 0 | // Use the asynchronous binding methods to ensure that we do not acquire the |
5186 | 0 | // database lock. |
5187 | 0 | nsCOMPtr<mozIStorageBindingParams> params; |
5188 | 0 | DebugOnly<nsresult> rv = |
5189 | 0 | aParamsArray->NewBindingParams(getter_AddRefs(params)); |
5190 | 0 | NS_ASSERT_SUCCESS(rv); |
5191 | 0 |
|
5192 | 0 | // Bind our values to params |
5193 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"), |
5194 | 0 | aKey.mBaseDomain); |
5195 | 0 | NS_ASSERT_SUCCESS(rv); |
5196 | 0 |
|
5197 | 0 | nsAutoCString suffix; |
5198 | 0 | aKey.mOriginAttributes.CreateSuffix(suffix); |
5199 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"), |
5200 | 0 | suffix); |
5201 | 0 | NS_ASSERT_SUCCESS(rv); |
5202 | 0 |
|
5203 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), |
5204 | 0 | aCookie->Name()); |
5205 | 0 | NS_ASSERT_SUCCESS(rv); |
5206 | 0 |
|
5207 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), |
5208 | 0 | aCookie->Value()); |
5209 | 0 | NS_ASSERT_SUCCESS(rv); |
5210 | 0 |
|
5211 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), |
5212 | 0 | aCookie->Host()); |
5213 | 0 | NS_ASSERT_SUCCESS(rv); |
5214 | 0 |
|
5215 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), |
5216 | 0 | aCookie->Path()); |
5217 | 0 | NS_ASSERT_SUCCESS(rv); |
5218 | 0 |
|
5219 | 0 | rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"), |
5220 | 0 | aCookie->Expiry()); |
5221 | 0 | NS_ASSERT_SUCCESS(rv); |
5222 | 0 |
|
5223 | 0 | rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"), |
5224 | 0 | aCookie->LastAccessed()); |
5225 | 0 | NS_ASSERT_SUCCESS(rv); |
5226 | 0 |
|
5227 | 0 | rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"), |
5228 | 0 | aCookie->CreationTime()); |
5229 | 0 | NS_ASSERT_SUCCESS(rv); |
5230 | 0 |
|
5231 | 0 | rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"), |
5232 | 0 | aCookie->IsSecure()); |
5233 | 0 | NS_ASSERT_SUCCESS(rv); |
5234 | 0 |
|
5235 | 0 | rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"), |
5236 | 0 | aCookie->IsHttpOnly()); |
5237 | 0 | NS_ASSERT_SUCCESS(rv); |
5238 | 0 |
|
5239 | 0 | rv = params->BindInt32ByName(NS_LITERAL_CSTRING("sameSite"), |
5240 | 0 | aCookie->SameSite()); |
5241 | 0 | NS_ASSERT_SUCCESS(rv); |
5242 | 0 |
|
5243 | 0 | // Bind the params to the array. |
5244 | 0 | rv = aParamsArray->AddParams(params); |
5245 | 0 | NS_ASSERT_SUCCESS(rv); |
5246 | 0 | } |
5247 | | |
5248 | | void |
5249 | | nsCookieService::UpdateCookieOldestTime(DBState* aDBState, |
5250 | | nsCookie* aCookie) |
5251 | 0 | { |
5252 | 0 | if (aCookie->LastAccessed() < aDBState->cookieOldestTime) { |
5253 | 0 | aDBState->cookieOldestTime = aCookie->LastAccessed(); |
5254 | 0 | } |
5255 | 0 | } |
5256 | | |
5257 | | void |
5258 | | nsCookieService::AddCookieToList(const nsCookieKey &aKey, |
5259 | | nsCookie *aCookie, |
5260 | | DBState *aDBState, |
5261 | | mozIStorageBindingParamsArray *aParamsArray, |
5262 | | bool aWriteToDB) |
5263 | 0 | { |
5264 | 0 | NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray), |
5265 | 0 | "Not writing to the DB but have a params array?"); |
5266 | 0 | NS_ASSERTION(!(!aDBState->dbConn && aParamsArray), |
5267 | 0 | "Do not have a DB connection but have a params array?"); |
5268 | 0 |
|
5269 | 0 | if (!aCookie) { |
5270 | 0 | NS_WARNING("Attempting to AddCookieToList with null cookie"); |
5271 | 0 | return; |
5272 | 0 | } |
5273 | 0 |
|
5274 | 0 | nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey); |
5275 | 0 | NS_ASSERTION(entry, "can't insert element into a null entry!"); |
5276 | 0 |
|
5277 | 0 | entry->GetCookies().AppendElement(aCookie); |
5278 | 0 | ++aDBState->cookieCount; |
5279 | 0 |
|
5280 | 0 | // keep track of the oldest cookie, for when it comes time to purge |
5281 | 0 | UpdateCookieOldestTime(aDBState, aCookie); |
5282 | 0 |
|
5283 | 0 | // if it's a non-session cookie and hasn't just been read from the db, write it out. |
5284 | 0 | if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) { |
5285 | 0 | mozIStorageAsyncStatement *stmt = aDBState->stmtInsert; |
5286 | 0 | nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray); |
5287 | 0 | if (!paramsArray) { |
5288 | 0 | stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
5289 | 0 | } |
5290 | 0 | bindCookieParameters(paramsArray, aKey, aCookie); |
5291 | 0 |
|
5292 | 0 | // If we were supplied an array to store parameters, we shouldn't call |
5293 | 0 | // executeAsync - someone up the stack will do this for us. |
5294 | 0 | if (!aParamsArray) { |
5295 | 0 | DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
5296 | 0 | NS_ASSERT_SUCCESS(rv); |
5297 | 0 | nsCOMPtr<mozIStoragePendingStatement> handle; |
5298 | 0 | rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle)); |
5299 | 0 | NS_ASSERT_SUCCESS(rv); |
5300 | 0 | } |
5301 | 0 | } |
5302 | 0 | } |
5303 | | |
5304 | | void |
5305 | | nsCookieService::UpdateCookieInList(nsCookie *aCookie, |
5306 | | int64_t aLastAccessed, |
5307 | | mozIStorageBindingParamsArray *aParamsArray) |
5308 | 0 | { |
5309 | 0 | NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!"); |
5310 | 0 |
|
5311 | 0 | // udpate the lastAccessed timestamp |
5312 | 0 | aCookie->SetLastAccessed(aLastAccessed); |
5313 | 0 |
|
5314 | 0 | // if it's a non-session cookie, update it in the db too |
5315 | 0 | if (!aCookie->IsSession() && aParamsArray) { |
5316 | 0 | // Create our params holder. |
5317 | 0 | nsCOMPtr<mozIStorageBindingParams> params; |
5318 | 0 | aParamsArray->NewBindingParams(getter_AddRefs(params)); |
5319 | 0 |
|
5320 | 0 | // Bind our parameters. |
5321 | 0 | DebugOnly<nsresult> rv = |
5322 | 0 | params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"), |
5323 | 0 | aLastAccessed); |
5324 | 0 | NS_ASSERT_SUCCESS(rv); |
5325 | 0 |
|
5326 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), |
5327 | 0 | aCookie->Name()); |
5328 | 0 | NS_ASSERT_SUCCESS(rv); |
5329 | 0 |
|
5330 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), |
5331 | 0 | aCookie->Host()); |
5332 | 0 | NS_ASSERT_SUCCESS(rv); |
5333 | 0 |
|
5334 | 0 | rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), |
5335 | 0 | aCookie->Path()); |
5336 | 0 | NS_ASSERT_SUCCESS(rv); |
5337 | 0 |
|
5338 | 0 | nsAutoCString suffix; |
5339 | 0 | aCookie->OriginAttributesRef().CreateSuffix(suffix); |
5340 | 0 | rv = params->BindUTF8StringByName( |
5341 | 0 | NS_LITERAL_CSTRING("originAttributes"), suffix); |
5342 | 0 | NS_ASSERT_SUCCESS(rv); |
5343 | 0 |
|
5344 | 0 | // Add our bound parameters to the array. |
5345 | 0 | rv = aParamsArray->AddParams(params); |
5346 | 0 | NS_ASSERT_SUCCESS(rv); |
5347 | 0 | } |
5348 | 0 | } |
5349 | | |
5350 | | size_t |
5351 | | nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
5352 | 0 | { |
5353 | 0 | size_t n = aMallocSizeOf(this); |
5354 | 0 |
|
5355 | 0 | if (mDefaultDBState) { |
5356 | 0 | n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf); |
5357 | 0 | } |
5358 | 0 | if (mPrivateDBState) { |
5359 | 0 | n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf); |
5360 | 0 | } |
5361 | 0 |
|
5362 | 0 | return n; |
5363 | 0 | } |
5364 | | |
5365 | | MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf) |
5366 | | |
5367 | | NS_IMETHODIMP |
5368 | | nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport, |
5369 | | nsISupports* aData, bool aAnonymize) |
5370 | 0 | { |
5371 | 0 | MOZ_COLLECT_REPORT( |
5372 | 0 | "explicit/cookie-service", KIND_HEAP, UNITS_BYTES, |
5373 | 0 | SizeOfIncludingThis(CookieServiceMallocSizeOf), |
5374 | 0 | "Memory used by the cookie service."); |
5375 | 0 |
|
5376 | 0 | return NS_OK; |
5377 | 0 | } |