|
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/Attributes.h" |
|
8 #include "mozilla/DebugOnly.h" |
|
9 #include "mozilla/Likely.h" |
|
10 |
|
11 #ifdef MOZ_LOGGING |
|
12 // this next define has to appear before the include of prlog.h |
|
13 #define FORCE_PR_LOG // Allow logging in the release build |
|
14 #endif |
|
15 |
|
16 #include "mozilla/net/CookieServiceChild.h" |
|
17 #include "mozilla/net/NeckoCommon.h" |
|
18 |
|
19 #include "nsCookieService.h" |
|
20 #include "nsIServiceManager.h" |
|
21 |
|
22 #include "nsIIOService.h" |
|
23 #include "nsIPrefBranch.h" |
|
24 #include "nsIPrefService.h" |
|
25 #include "nsICookiePermission.h" |
|
26 #include "nsIURI.h" |
|
27 #include "nsIURL.h" |
|
28 #include "nsIChannel.h" |
|
29 #include "nsIFile.h" |
|
30 #include "nsIObserverService.h" |
|
31 #include "nsILineInputStream.h" |
|
32 #include "nsIEffectiveTLDService.h" |
|
33 #include "nsIIDNService.h" |
|
34 #include "mozIThirdPartyUtil.h" |
|
35 |
|
36 #include "nsTArray.h" |
|
37 #include "nsCOMArray.h" |
|
38 #include "nsIMutableArray.h" |
|
39 #include "nsArrayEnumerator.h" |
|
40 #include "nsEnumeratorUtils.h" |
|
41 #include "nsAutoPtr.h" |
|
42 #include "nsReadableUtils.h" |
|
43 #include "nsCRT.h" |
|
44 #include "prprf.h" |
|
45 #include "nsNetUtil.h" |
|
46 #include "nsNetCID.h" |
|
47 #include "nsAppDirectoryServiceDefs.h" |
|
48 #include "nsNetCID.h" |
|
49 #include "mozilla/storage.h" |
|
50 #include "mozilla/AutoRestore.h" |
|
51 #include "mozilla/FileUtils.h" |
|
52 #include "mozilla/Telemetry.h" |
|
53 #include "nsIAppsService.h" |
|
54 #include "mozIApplication.h" |
|
55 #include "nsIConsoleService.h" |
|
56 |
|
57 using namespace mozilla; |
|
58 using namespace mozilla::net; |
|
59 |
|
60 // Create key from baseDomain that will access the default cookie namespace. |
|
61 // TODO: When we figure out what the API will look like for nsICookieManager{2} |
|
62 // on content processes (see bug 777620), change to use the appropriate app |
|
63 // namespace. For now those IDLs aren't supported on child processes. |
|
64 #define DEFAULT_APP_KEY(baseDomain) \ |
|
65 nsCookieKey(baseDomain, NECKO_NO_APP_ID, false) |
|
66 |
|
67 /****************************************************************************** |
|
68 * nsCookieService impl: |
|
69 * useful types & constants |
|
70 ******************************************************************************/ |
|
71 |
|
72 static nsCookieService *gCookieService; |
|
73 |
|
74 // XXX_hack. See bug 178993. |
|
75 // This is a hack to hide HttpOnly cookies from older browsers |
|
76 static const char kHttpOnlyPrefix[] = "#HttpOnly_"; |
|
77 |
|
78 #define COOKIES_FILE "cookies.sqlite" |
|
79 #define COOKIES_SCHEMA_VERSION 5 |
|
80 |
|
81 // parameter indexes; see EnsureReadDomain, EnsureReadComplete and |
|
82 // ReadCookieDBListener::HandleResult |
|
83 #define IDX_NAME 0 |
|
84 #define IDX_VALUE 1 |
|
85 #define IDX_HOST 2 |
|
86 #define IDX_PATH 3 |
|
87 #define IDX_EXPIRY 4 |
|
88 #define IDX_LAST_ACCESSED 5 |
|
89 #define IDX_CREATION_TIME 6 |
|
90 #define IDX_SECURE 7 |
|
91 #define IDX_HTTPONLY 8 |
|
92 #define IDX_BASE_DOMAIN 9 |
|
93 #define IDX_APP_ID 10 |
|
94 #define IDX_BROWSER_ELEM 11 |
|
95 |
|
96 static const int64_t kCookieStaleThreshold = 60 * PR_USEC_PER_SEC; // 1 minute in microseconds |
|
97 static const int64_t kCookiePurgeAge = |
|
98 int64_t(30 * 24 * 60 * 60) * PR_USEC_PER_SEC; // 30 days in microseconds |
|
99 |
|
100 static const char kOldCookieFileName[] = "cookies.txt"; |
|
101 |
|
102 #undef LIMIT |
|
103 #define LIMIT(x, low, high, default) ((x) >= (low) && (x) <= (high) ? (x) : (default)) |
|
104 |
|
105 #undef ADD_TEN_PERCENT |
|
106 #define ADD_TEN_PERCENT(i) static_cast<uint32_t>((i) + (i)/10) |
|
107 |
|
108 // default limits for the cookie list. these can be tuned by the |
|
109 // network.cookie.maxNumber and network.cookie.maxPerHost prefs respectively. |
|
110 static const uint32_t kMaxNumberOfCookies = 3000; |
|
111 static const uint32_t kMaxCookiesPerHost = 150; |
|
112 static const uint32_t kMaxBytesPerCookie = 4096; |
|
113 static const uint32_t kMaxBytesPerPath = 1024; |
|
114 |
|
115 // behavior pref constants |
|
116 static const uint32_t BEHAVIOR_ACCEPT = 0; // allow all cookies |
|
117 static const uint32_t BEHAVIOR_REJECTFOREIGN = 1; // reject all third-party cookies |
|
118 static const uint32_t BEHAVIOR_REJECT = 2; // reject all cookies |
|
119 static const uint32_t BEHAVIOR_LIMITFOREIGN = 3; // reject third-party cookies unless the |
|
120 // eTLD already has at least one cookie |
|
121 |
|
122 // pref string constants |
|
123 static const char kPrefCookieBehavior[] = "network.cookie.cookieBehavior"; |
|
124 static const char kPrefMaxNumberOfCookies[] = "network.cookie.maxNumber"; |
|
125 static const char kPrefMaxCookiesPerHost[] = "network.cookie.maxPerHost"; |
|
126 static const char kPrefCookiePurgeAge[] = "network.cookie.purgeAge"; |
|
127 static const char kPrefThirdPartySession[] = "network.cookie.thirdparty.sessionOnly"; |
|
128 |
|
129 static void |
|
130 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray, |
|
131 const nsCookieKey &aKey, |
|
132 const nsCookie *aCookie); |
|
133 |
|
134 // struct for temporarily storing cookie attributes during header parsing |
|
135 struct nsCookieAttributes |
|
136 { |
|
137 nsAutoCString name; |
|
138 nsAutoCString value; |
|
139 nsAutoCString host; |
|
140 nsAutoCString path; |
|
141 nsAutoCString expires; |
|
142 nsAutoCString maxage; |
|
143 int64_t expiryTime; |
|
144 bool isSession; |
|
145 bool isSecure; |
|
146 bool isHttpOnly; |
|
147 }; |
|
148 |
|
149 // stores the nsCookieEntry entryclass and an index into the cookie array |
|
150 // within that entryclass, for purposes of storing an iteration state that |
|
151 // points to a certain cookie. |
|
152 struct nsListIter |
|
153 { |
|
154 // default (non-initializing) constructor. |
|
155 nsListIter() |
|
156 { |
|
157 } |
|
158 |
|
159 // explicit constructor to a given iterator state with entryclass 'aEntry' |
|
160 // and index 'aIndex'. |
|
161 explicit |
|
162 nsListIter(nsCookieEntry *aEntry, nsCookieEntry::IndexType aIndex) |
|
163 : entry(aEntry) |
|
164 , index(aIndex) |
|
165 { |
|
166 } |
|
167 |
|
168 // get the nsCookie * the iterator currently points to. |
|
169 nsCookie * Cookie() const |
|
170 { |
|
171 return entry->GetCookies()[index]; |
|
172 } |
|
173 |
|
174 nsCookieEntry *entry; |
|
175 nsCookieEntry::IndexType index; |
|
176 }; |
|
177 |
|
178 /****************************************************************************** |
|
179 * Cookie logging handlers |
|
180 * used for logging in nsCookieService |
|
181 ******************************************************************************/ |
|
182 |
|
183 // logging handlers |
|
184 #ifdef MOZ_LOGGING |
|
185 // in order to do logging, the following environment variables need to be set: |
|
186 // |
|
187 // set NSPR_LOG_MODULES=cookie:3 -- shows rejected cookies |
|
188 // set NSPR_LOG_MODULES=cookie:4 -- shows accepted and rejected cookies |
|
189 // set NSPR_LOG_FILE=cookie.log |
|
190 // |
|
191 #include "prlog.h" |
|
192 #endif |
|
193 |
|
194 // define logging macros for convenience |
|
195 #define SET_COOKIE true |
|
196 #define GET_COOKIE false |
|
197 |
|
198 #ifdef PR_LOGGING |
|
199 static PRLogModuleInfo * |
|
200 GetCookieLog() |
|
201 { |
|
202 static PRLogModuleInfo *sCookieLog; |
|
203 if (!sCookieLog) |
|
204 sCookieLog = PR_NewLogModule("cookie"); |
|
205 return sCookieLog; |
|
206 } |
|
207 |
|
208 #define COOKIE_LOGFAILURE(a, b, c, d) LogFailure(a, b, c, d) |
|
209 #define COOKIE_LOGSUCCESS(a, b, c, d, e) LogSuccess(a, b, c, d, e) |
|
210 |
|
211 #define COOKIE_LOGEVICTED(a, details) \ |
|
212 PR_BEGIN_MACRO \ |
|
213 if (PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG)) \ |
|
214 LogEvicted(a, details); \ |
|
215 PR_END_MACRO |
|
216 |
|
217 #define COOKIE_LOGSTRING(lvl, fmt) \ |
|
218 PR_BEGIN_MACRO \ |
|
219 PR_LOG(GetCookieLog(), lvl, fmt); \ |
|
220 PR_LOG(GetCookieLog(), lvl, ("\n")); \ |
|
221 PR_END_MACRO |
|
222 |
|
223 static void |
|
224 LogFailure(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, const char *aReason) |
|
225 { |
|
226 // if logging isn't enabled, return now to save cycles |
|
227 if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_WARNING)) |
|
228 return; |
|
229 |
|
230 nsAutoCString spec; |
|
231 if (aHostURI) |
|
232 aHostURI->GetAsciiSpec(spec); |
|
233 |
|
234 PR_LOG(GetCookieLog(), PR_LOG_WARNING, |
|
235 ("===== %s =====\n", aSetCookie ? "COOKIE NOT ACCEPTED" : "COOKIE NOT SENT")); |
|
236 PR_LOG(GetCookieLog(), PR_LOG_WARNING,("request URL: %s\n", spec.get())); |
|
237 if (aSetCookie) |
|
238 PR_LOG(GetCookieLog(), PR_LOG_WARNING,("cookie string: %s\n", aCookieString)); |
|
239 |
|
240 PRExplodedTime explodedTime; |
|
241 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime); |
|
242 char timeString[40]; |
|
243 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
|
244 |
|
245 PR_LOG(GetCookieLog(), PR_LOG_WARNING,("current time: %s", timeString)); |
|
246 PR_LOG(GetCookieLog(), PR_LOG_WARNING,("rejected because %s\n", aReason)); |
|
247 PR_LOG(GetCookieLog(), PR_LOG_WARNING,("\n")); |
|
248 } |
|
249 |
|
250 static void |
|
251 LogCookie(nsCookie *aCookie) |
|
252 { |
|
253 PRExplodedTime explodedTime; |
|
254 PR_ExplodeTime(PR_Now(), PR_GMTParameters, &explodedTime); |
|
255 char timeString[40]; |
|
256 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
|
257 |
|
258 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("current time: %s", timeString)); |
|
259 |
|
260 if (aCookie) { |
|
261 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("----------------\n")); |
|
262 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("name: %s\n", aCookie->Name().get())); |
|
263 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("value: %s\n", aCookie->Value().get())); |
|
264 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s: %s\n", aCookie->IsDomain() ? "domain" : "host", aCookie->Host().get())); |
|
265 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("path: %s\n", aCookie->Path().get())); |
|
266 |
|
267 PR_ExplodeTime(aCookie->Expiry() * int64_t(PR_USEC_PER_SEC), |
|
268 PR_GMTParameters, &explodedTime); |
|
269 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
|
270 PR_LOG(GetCookieLog(), PR_LOG_DEBUG, |
|
271 ("expires: %s%s", timeString, aCookie->IsSession() ? " (at end of session)" : "")); |
|
272 |
|
273 PR_ExplodeTime(aCookie->CreationTime(), PR_GMTParameters, &explodedTime); |
|
274 PR_FormatTimeUSEnglish(timeString, 40, "%c GMT", &explodedTime); |
|
275 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("created: %s", timeString)); |
|
276 |
|
277 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is secure: %s\n", aCookie->IsSecure() ? "true" : "false")); |
|
278 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("is httpOnly: %s\n", aCookie->IsHttpOnly() ? "true" : "false")); |
|
279 } |
|
280 } |
|
281 |
|
282 static void |
|
283 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const char *aCookieString, nsCookie *aCookie, bool aReplacing) |
|
284 { |
|
285 // if logging isn't enabled, return now to save cycles |
|
286 if (!PR_LOG_TEST(GetCookieLog(), PR_LOG_DEBUG)) { |
|
287 return; |
|
288 } |
|
289 |
|
290 nsAutoCString spec; |
|
291 if (aHostURI) |
|
292 aHostURI->GetAsciiSpec(spec); |
|
293 |
|
294 PR_LOG(GetCookieLog(), PR_LOG_DEBUG, |
|
295 ("===== %s =====\n", aSetCookie ? "COOKIE ACCEPTED" : "COOKIE SENT")); |
|
296 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("request URL: %s\n", spec.get())); |
|
297 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("cookie string: %s\n", aCookieString)); |
|
298 if (aSetCookie) |
|
299 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("replaces existing cookie: %s\n", aReplacing ? "true" : "false")); |
|
300 |
|
301 LogCookie(aCookie); |
|
302 |
|
303 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n")); |
|
304 } |
|
305 |
|
306 static void |
|
307 LogEvicted(nsCookie *aCookie, const char* details) |
|
308 { |
|
309 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("===== COOKIE EVICTED =====\n")); |
|
310 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("%s\n", details)); |
|
311 |
|
312 LogCookie(aCookie); |
|
313 |
|
314 PR_LOG(GetCookieLog(), PR_LOG_DEBUG,("\n")); |
|
315 } |
|
316 |
|
317 // inline wrappers to make passing in nsAFlatCStrings easier |
|
318 static inline void |
|
319 LogFailure(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, const char *aReason) |
|
320 { |
|
321 LogFailure(aSetCookie, aHostURI, aCookieString.get(), aReason); |
|
322 } |
|
323 |
|
324 static inline void |
|
325 LogSuccess(bool aSetCookie, nsIURI *aHostURI, const nsAFlatCString &aCookieString, nsCookie *aCookie, bool aReplacing) |
|
326 { |
|
327 LogSuccess(aSetCookie, aHostURI, aCookieString.get(), aCookie, aReplacing); |
|
328 } |
|
329 |
|
330 #else |
|
331 #define COOKIE_LOGFAILURE(a, b, c, d) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO |
|
332 #define COOKIE_LOGSUCCESS(a, b, c, d, e) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO |
|
333 #define COOKIE_LOGEVICTED(a, b) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO |
|
334 #define COOKIE_LOGSTRING(a, b) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO |
|
335 #endif |
|
336 |
|
337 #ifdef DEBUG |
|
338 #define NS_ASSERT_SUCCESS(res) \ |
|
339 PR_BEGIN_MACRO \ |
|
340 nsresult __rv = res; /* Do not evaluate |res| more than once! */ \ |
|
341 if (NS_FAILED(__rv)) { \ |
|
342 char *msg = PR_smprintf("NS_ASSERT_SUCCESS(%s) failed with result 0x%X", \ |
|
343 #res, __rv); \ |
|
344 NS_ASSERTION(NS_SUCCEEDED(__rv), msg); \ |
|
345 PR_smprintf_free(msg); \ |
|
346 } \ |
|
347 PR_END_MACRO |
|
348 #else |
|
349 #define NS_ASSERT_SUCCESS(res) PR_BEGIN_MACRO /* nothing */ PR_END_MACRO |
|
350 #endif |
|
351 |
|
352 /****************************************************************************** |
|
353 * DBListenerErrorHandler impl: |
|
354 * Parent class for our async storage listeners that handles the logging of |
|
355 * errors. |
|
356 ******************************************************************************/ |
|
357 class DBListenerErrorHandler : public mozIStorageStatementCallback |
|
358 { |
|
359 protected: |
|
360 DBListenerErrorHandler(DBState* dbState) : mDBState(dbState) { } |
|
361 nsRefPtr<DBState> mDBState; |
|
362 virtual const char *GetOpType() = 0; |
|
363 |
|
364 public: |
|
365 NS_IMETHOD HandleError(mozIStorageError* aError) |
|
366 { |
|
367 int32_t result = -1; |
|
368 aError->GetResult(&result); |
|
369 |
|
370 #ifdef PR_LOGGING |
|
371 nsAutoCString message; |
|
372 aError->GetMessage(message); |
|
373 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
374 ("DBListenerErrorHandler::HandleError(): Error %d occurred while " |
|
375 "performing operation '%s' with message '%s'; rebuilding database.", |
|
376 result, GetOpType(), message.get())); |
|
377 #endif |
|
378 |
|
379 // Rebuild the database. |
|
380 gCookieService->HandleCorruptDB(mDBState); |
|
381 |
|
382 return NS_OK; |
|
383 } |
|
384 }; |
|
385 |
|
386 /****************************************************************************** |
|
387 * InsertCookieDBListener impl: |
|
388 * mozIStorageStatementCallback used to track asynchronous insertion operations. |
|
389 ******************************************************************************/ |
|
390 class InsertCookieDBListener MOZ_FINAL : public DBListenerErrorHandler |
|
391 { |
|
392 protected: |
|
393 virtual const char *GetOpType() { return "INSERT"; } |
|
394 |
|
395 public: |
|
396 NS_DECL_ISUPPORTS |
|
397 |
|
398 InsertCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { } |
|
399 NS_IMETHOD HandleResult(mozIStorageResultSet*) |
|
400 { |
|
401 NS_NOTREACHED("Unexpected call to InsertCookieDBListener::HandleResult"); |
|
402 return NS_OK; |
|
403 } |
|
404 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
405 { |
|
406 // If we were rebuilding the db and we succeeded, make our corruptFlag say |
|
407 // so. |
|
408 if (mDBState->corruptFlag == DBState::REBUILDING && |
|
409 aReason == mozIStorageStatementCallback::REASON_FINISHED) { |
|
410 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
411 ("InsertCookieDBListener::HandleCompletion(): rebuild complete")); |
|
412 mDBState->corruptFlag = DBState::OK; |
|
413 } |
|
414 return NS_OK; |
|
415 } |
|
416 }; |
|
417 |
|
418 NS_IMPL_ISUPPORTS(InsertCookieDBListener, mozIStorageStatementCallback) |
|
419 |
|
420 /****************************************************************************** |
|
421 * UpdateCookieDBListener impl: |
|
422 * mozIStorageStatementCallback used to track asynchronous update operations. |
|
423 ******************************************************************************/ |
|
424 class UpdateCookieDBListener MOZ_FINAL : public DBListenerErrorHandler |
|
425 { |
|
426 protected: |
|
427 virtual const char *GetOpType() { return "UPDATE"; } |
|
428 |
|
429 public: |
|
430 NS_DECL_ISUPPORTS |
|
431 |
|
432 UpdateCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { } |
|
433 NS_IMETHOD HandleResult(mozIStorageResultSet*) |
|
434 { |
|
435 NS_NOTREACHED("Unexpected call to UpdateCookieDBListener::HandleResult"); |
|
436 return NS_OK; |
|
437 } |
|
438 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
439 { |
|
440 return NS_OK; |
|
441 } |
|
442 }; |
|
443 |
|
444 NS_IMPL_ISUPPORTS(UpdateCookieDBListener, mozIStorageStatementCallback) |
|
445 |
|
446 /****************************************************************************** |
|
447 * RemoveCookieDBListener impl: |
|
448 * mozIStorageStatementCallback used to track asynchronous removal operations. |
|
449 ******************************************************************************/ |
|
450 class RemoveCookieDBListener MOZ_FINAL : public DBListenerErrorHandler |
|
451 { |
|
452 protected: |
|
453 virtual const char *GetOpType() { return "REMOVE"; } |
|
454 |
|
455 public: |
|
456 NS_DECL_ISUPPORTS |
|
457 |
|
458 RemoveCookieDBListener(DBState* dbState) : DBListenerErrorHandler(dbState) { } |
|
459 NS_IMETHOD HandleResult(mozIStorageResultSet*) |
|
460 { |
|
461 NS_NOTREACHED("Unexpected call to RemoveCookieDBListener::HandleResult"); |
|
462 return NS_OK; |
|
463 } |
|
464 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
465 { |
|
466 return NS_OK; |
|
467 } |
|
468 }; |
|
469 |
|
470 NS_IMPL_ISUPPORTS(RemoveCookieDBListener, mozIStorageStatementCallback) |
|
471 |
|
472 /****************************************************************************** |
|
473 * ReadCookieDBListener impl: |
|
474 * mozIStorageStatementCallback used to track asynchronous removal operations. |
|
475 ******************************************************************************/ |
|
476 class ReadCookieDBListener MOZ_FINAL : public DBListenerErrorHandler |
|
477 { |
|
478 protected: |
|
479 virtual const char *GetOpType() { return "READ"; } |
|
480 bool mCanceled; |
|
481 |
|
482 public: |
|
483 NS_DECL_ISUPPORTS |
|
484 |
|
485 ReadCookieDBListener(DBState* dbState) |
|
486 : DBListenerErrorHandler(dbState) |
|
487 , mCanceled(false) |
|
488 { |
|
489 } |
|
490 |
|
491 void Cancel() { mCanceled = true; } |
|
492 |
|
493 NS_IMETHOD HandleResult(mozIStorageResultSet *aResult) |
|
494 { |
|
495 nsCOMPtr<mozIStorageRow> row; |
|
496 |
|
497 while (1) { |
|
498 DebugOnly<nsresult> rv = aResult->GetNextRow(getter_AddRefs(row)); |
|
499 NS_ASSERT_SUCCESS(rv); |
|
500 |
|
501 if (!row) |
|
502 break; |
|
503 |
|
504 CookieDomainTuple *tuple = mDBState->hostArray.AppendElement(); |
|
505 row->GetUTF8String(IDX_BASE_DOMAIN, tuple->key.mBaseDomain); |
|
506 tuple->key.mAppId = static_cast<uint32_t>(row->AsInt32(IDX_APP_ID)); |
|
507 tuple->key.mInBrowserElement = static_cast<bool>(row->AsInt32(IDX_BROWSER_ELEM)); |
|
508 tuple->cookie = gCookieService->GetCookieFromRow(row); |
|
509 } |
|
510 |
|
511 return NS_OK; |
|
512 } |
|
513 NS_IMETHOD HandleCompletion(uint16_t aReason) |
|
514 { |
|
515 // Process the completion of the read operation. If we have been canceled, |
|
516 // we cannot assume that the cookieservice still has an open connection |
|
517 // or that it even refers to the same database, so we must return early. |
|
518 // Conversely, the cookieservice guarantees that if we have not been |
|
519 // canceled, the database connection is still alive and we can safely |
|
520 // operate on it. |
|
521 |
|
522 if (mCanceled) { |
|
523 // We may receive a REASON_FINISHED after being canceled; |
|
524 // tweak the reason accordingly. |
|
525 aReason = mozIStorageStatementCallback::REASON_CANCELED; |
|
526 } |
|
527 |
|
528 switch (aReason) { |
|
529 case mozIStorageStatementCallback::REASON_FINISHED: |
|
530 gCookieService->AsyncReadComplete(); |
|
531 break; |
|
532 case mozIStorageStatementCallback::REASON_CANCELED: |
|
533 // Nothing more to do here. The partially read data has already been |
|
534 // thrown away. |
|
535 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read canceled")); |
|
536 break; |
|
537 case mozIStorageStatementCallback::REASON_ERROR: |
|
538 // Nothing more to do here. DBListenerErrorHandler::HandleError() |
|
539 // can handle it. |
|
540 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read error")); |
|
541 break; |
|
542 default: |
|
543 NS_NOTREACHED("invalid reason"); |
|
544 } |
|
545 return NS_OK; |
|
546 } |
|
547 }; |
|
548 |
|
549 NS_IMPL_ISUPPORTS(ReadCookieDBListener, mozIStorageStatementCallback) |
|
550 |
|
551 /****************************************************************************** |
|
552 * CloseCookieDBListener imp: |
|
553 * Static mozIStorageCompletionCallback used to notify when the database is |
|
554 * successfully closed. |
|
555 ******************************************************************************/ |
|
556 class CloseCookieDBListener MOZ_FINAL : public mozIStorageCompletionCallback |
|
557 { |
|
558 public: |
|
559 CloseCookieDBListener(DBState* dbState) : mDBState(dbState) { } |
|
560 nsRefPtr<DBState> mDBState; |
|
561 NS_DECL_ISUPPORTS |
|
562 |
|
563 NS_IMETHOD Complete(nsresult, nsISupports*) |
|
564 { |
|
565 gCookieService->HandleDBClosed(mDBState); |
|
566 return NS_OK; |
|
567 } |
|
568 }; |
|
569 |
|
570 NS_IMPL_ISUPPORTS(CloseCookieDBListener, mozIStorageCompletionCallback) |
|
571 |
|
572 namespace { |
|
573 |
|
574 class AppClearDataObserver MOZ_FINAL : public nsIObserver { |
|
575 public: |
|
576 NS_DECL_ISUPPORTS |
|
577 |
|
578 // nsIObserver implementation. |
|
579 NS_IMETHODIMP |
|
580 Observe(nsISupports *aSubject, const char *aTopic, const char16_t *data) |
|
581 { |
|
582 MOZ_ASSERT(!nsCRT::strcmp(aTopic, TOPIC_WEB_APP_CLEAR_DATA)); |
|
583 |
|
584 uint32_t appId = NECKO_UNKNOWN_APP_ID; |
|
585 bool browserOnly = false; |
|
586 nsresult rv = NS_GetAppInfoFromClearDataNotification(aSubject, &appId, |
|
587 &browserOnly); |
|
588 NS_ENSURE_SUCCESS(rv, rv); |
|
589 |
|
590 nsCOMPtr<nsICookieManager2> cookieManager |
|
591 = do_GetService(NS_COOKIEMANAGER_CONTRACTID); |
|
592 MOZ_ASSERT(cookieManager); |
|
593 return cookieManager->RemoveCookiesForApp(appId, browserOnly); |
|
594 } |
|
595 }; |
|
596 |
|
597 NS_IMPL_ISUPPORTS(AppClearDataObserver, nsIObserver) |
|
598 |
|
599 } // anonymous namespace |
|
600 |
|
601 size_t |
|
602 nsCookieKey::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
603 { |
|
604 return mBaseDomain.SizeOfExcludingThisIfUnshared(aMallocSizeOf); |
|
605 } |
|
606 |
|
607 size_t |
|
608 nsCookieEntry::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
609 { |
|
610 size_t amount = nsCookieKey::SizeOfExcludingThis(aMallocSizeOf); |
|
611 |
|
612 amount += mCookies.SizeOfExcludingThis(aMallocSizeOf); |
|
613 for (uint32_t i = 0; i < mCookies.Length(); ++i) { |
|
614 amount += mCookies[i]->SizeOfIncludingThis(aMallocSizeOf); |
|
615 } |
|
616 |
|
617 return amount; |
|
618 } |
|
619 |
|
620 static size_t |
|
621 HostTableEntrySizeOfExcludingThis(nsCookieEntry *aEntry, |
|
622 MallocSizeOf aMallocSizeOf, |
|
623 void *arg) |
|
624 { |
|
625 return aEntry->SizeOfExcludingThis(aMallocSizeOf); |
|
626 } |
|
627 |
|
628 size_t |
|
629 CookieDomainTuple::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const |
|
630 { |
|
631 size_t amount = 0; |
|
632 |
|
633 amount += key.SizeOfExcludingThis(aMallocSizeOf); |
|
634 amount += cookie->SizeOfIncludingThis(aMallocSizeOf); |
|
635 |
|
636 return amount; |
|
637 } |
|
638 |
|
639 static size_t |
|
640 ReadSetEntrySizeOfExcludingThis(nsCookieKey *aEntry, |
|
641 MallocSizeOf aMallocSizeOf, |
|
642 void *) |
|
643 { |
|
644 return aEntry->SizeOfExcludingThis(aMallocSizeOf); |
|
645 } |
|
646 |
|
647 size_t |
|
648 DBState::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const |
|
649 { |
|
650 size_t amount = 0; |
|
651 |
|
652 amount += aMallocSizeOf(this); |
|
653 amount += hostTable.SizeOfExcludingThis(HostTableEntrySizeOfExcludingThis, |
|
654 aMallocSizeOf); |
|
655 amount += hostArray.SizeOfExcludingThis(aMallocSizeOf); |
|
656 for (uint32_t i = 0; i < hostArray.Length(); ++i) { |
|
657 amount += hostArray[i].SizeOfExcludingThis(aMallocSizeOf); |
|
658 } |
|
659 amount += readSet.SizeOfExcludingThis(ReadSetEntrySizeOfExcludingThis, |
|
660 aMallocSizeOf); |
|
661 |
|
662 return amount; |
|
663 } |
|
664 |
|
665 /****************************************************************************** |
|
666 * nsCookieService impl: |
|
667 * singleton instance ctor/dtor methods |
|
668 ******************************************************************************/ |
|
669 |
|
670 nsICookieService* |
|
671 nsCookieService::GetXPCOMSingleton() |
|
672 { |
|
673 if (IsNeckoChild()) |
|
674 return CookieServiceChild::GetSingleton(); |
|
675 |
|
676 return GetSingleton(); |
|
677 } |
|
678 |
|
679 nsCookieService* |
|
680 nsCookieService::GetSingleton() |
|
681 { |
|
682 NS_ASSERTION(!IsNeckoChild(), "not a parent process"); |
|
683 |
|
684 if (gCookieService) { |
|
685 NS_ADDREF(gCookieService); |
|
686 return gCookieService; |
|
687 } |
|
688 |
|
689 // Create a new singleton nsCookieService. |
|
690 // We AddRef only once since XPCOM has rules about the ordering of module |
|
691 // teardowns - by the time our module destructor is called, it's too late to |
|
692 // Release our members (e.g. nsIObserverService and nsIPrefBranch), since GC |
|
693 // cycles have already been completed and would result in serious leaks. |
|
694 // See bug 209571. |
|
695 gCookieService = new nsCookieService(); |
|
696 if (gCookieService) { |
|
697 NS_ADDREF(gCookieService); |
|
698 if (NS_FAILED(gCookieService->Init())) { |
|
699 NS_RELEASE(gCookieService); |
|
700 } |
|
701 } |
|
702 |
|
703 return gCookieService; |
|
704 } |
|
705 |
|
706 /* static */ void |
|
707 nsCookieService::AppClearDataObserverInit() |
|
708 { |
|
709 nsCOMPtr<nsIObserverService> observerService = do_GetService("@mozilla.org/observer-service;1"); |
|
710 nsCOMPtr<nsIObserver> obs = new AppClearDataObserver(); |
|
711 observerService->AddObserver(obs, TOPIC_WEB_APP_CLEAR_DATA, |
|
712 /* holdsWeak= */ false); |
|
713 } |
|
714 |
|
715 /****************************************************************************** |
|
716 * nsCookieService impl: |
|
717 * public methods |
|
718 ******************************************************************************/ |
|
719 |
|
720 NS_IMPL_ISUPPORTS(nsCookieService, |
|
721 nsICookieService, |
|
722 nsICookieManager, |
|
723 nsICookieManager2, |
|
724 nsIObserver, |
|
725 nsISupportsWeakReference, |
|
726 nsIMemoryReporter) |
|
727 |
|
728 nsCookieService::nsCookieService() |
|
729 : mDBState(nullptr) |
|
730 , mCookieBehavior(BEHAVIOR_ACCEPT) |
|
731 , mThirdPartySession(false) |
|
732 , mMaxNumberOfCookies(kMaxNumberOfCookies) |
|
733 , mMaxCookiesPerHost(kMaxCookiesPerHost) |
|
734 , mCookiePurgeAge(kCookiePurgeAge) |
|
735 { |
|
736 } |
|
737 |
|
738 nsresult |
|
739 nsCookieService::Init() |
|
740 { |
|
741 nsresult rv; |
|
742 mTLDService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID, &rv); |
|
743 NS_ENSURE_SUCCESS(rv, rv); |
|
744 |
|
745 mIDNService = do_GetService(NS_IDNSERVICE_CONTRACTID, &rv); |
|
746 NS_ENSURE_SUCCESS(rv, rv); |
|
747 |
|
748 mThirdPartyUtil = do_GetService(THIRDPARTYUTIL_CONTRACTID); |
|
749 NS_ENSURE_SUCCESS(rv, rv); |
|
750 |
|
751 // init our pref and observer |
|
752 nsCOMPtr<nsIPrefBranch> prefBranch = do_GetService(NS_PREFSERVICE_CONTRACTID); |
|
753 if (prefBranch) { |
|
754 prefBranch->AddObserver(kPrefCookieBehavior, this, true); |
|
755 prefBranch->AddObserver(kPrefMaxNumberOfCookies, this, true); |
|
756 prefBranch->AddObserver(kPrefMaxCookiesPerHost, this, true); |
|
757 prefBranch->AddObserver(kPrefCookiePurgeAge, this, true); |
|
758 prefBranch->AddObserver(kPrefThirdPartySession, this, true); |
|
759 PrefChanged(prefBranch); |
|
760 } |
|
761 |
|
762 mStorageService = do_GetService("@mozilla.org/storage/service;1", &rv); |
|
763 NS_ENSURE_SUCCESS(rv, rv); |
|
764 |
|
765 // Init our default, and possibly private DBStates. |
|
766 InitDBStates(); |
|
767 |
|
768 RegisterWeakMemoryReporter(this); |
|
769 |
|
770 mObserverService = mozilla::services::GetObserverService(); |
|
771 NS_ENSURE_STATE(mObserverService); |
|
772 mObserverService->AddObserver(this, "profile-before-change", true); |
|
773 mObserverService->AddObserver(this, "profile-do-change", true); |
|
774 mObserverService->AddObserver(this, "last-pb-context-exited", true); |
|
775 |
|
776 mPermissionService = do_GetService(NS_COOKIEPERMISSION_CONTRACTID); |
|
777 if (!mPermissionService) { |
|
778 NS_WARNING("nsICookiePermission implementation not available - some features won't work!"); |
|
779 COOKIE_LOGSTRING(PR_LOG_WARNING, ("Init(): nsICookiePermission implementation not available")); |
|
780 } |
|
781 |
|
782 return NS_OK; |
|
783 } |
|
784 |
|
785 void |
|
786 nsCookieService::InitDBStates() |
|
787 { |
|
788 NS_ASSERTION(!mDBState, "already have a DBState"); |
|
789 NS_ASSERTION(!mDefaultDBState, "already have a default DBState"); |
|
790 NS_ASSERTION(!mPrivateDBState, "already have a private DBState"); |
|
791 |
|
792 // Create a new default DBState and set our current one. |
|
793 mDefaultDBState = new DBState(); |
|
794 mDBState = mDefaultDBState; |
|
795 |
|
796 mPrivateDBState = new DBState(); |
|
797 |
|
798 // Get our cookie file. |
|
799 nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, |
|
800 getter_AddRefs(mDefaultDBState->cookieFile)); |
|
801 if (NS_FAILED(rv)) { |
|
802 // We've already set up our DBStates appropriately; nothing more to do. |
|
803 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
804 ("InitDBStates(): couldn't get cookie file")); |
|
805 return; |
|
806 } |
|
807 mDefaultDBState->cookieFile->AppendNative(NS_LITERAL_CSTRING(COOKIES_FILE)); |
|
808 |
|
809 // Attempt to open and read the database. If TryInitDB() returns RESULT_RETRY, |
|
810 // do so. |
|
811 OpenDBResult result = TryInitDB(false); |
|
812 if (result == RESULT_RETRY) { |
|
813 // Database may be corrupt. Synchronously close the connection, clean up the |
|
814 // default DBState, and try again. |
|
815 COOKIE_LOGSTRING(PR_LOG_WARNING, ("InitDBStates(): retrying TryInitDB()")); |
|
816 CleanupCachedStatements(); |
|
817 CleanupDefaultDBConnection(); |
|
818 result = TryInitDB(true); |
|
819 if (result == RESULT_RETRY) { |
|
820 // We're done. Change the code to failure so we clean up below. |
|
821 result = RESULT_FAILURE; |
|
822 } |
|
823 } |
|
824 |
|
825 if (result == RESULT_FAILURE) { |
|
826 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
827 ("InitDBStates(): TryInitDB() failed, closing connection")); |
|
828 |
|
829 // Connection failure is unrecoverable. Clean up our connection. We can run |
|
830 // fine without persistent storage -- e.g. if there's no profile. |
|
831 CleanupCachedStatements(); |
|
832 CleanupDefaultDBConnection(); |
|
833 } |
|
834 } |
|
835 |
|
836 /* Attempt to open and read the database. If 'aRecreateDB' is true, try to |
|
837 * move the existing database file out of the way and create a new one. |
|
838 * |
|
839 * @returns RESULT_OK if opening or creating the database succeeded; |
|
840 * RESULT_RETRY if the database cannot be opened, is corrupt, or some |
|
841 * other failure occurred that might be resolved by recreating the |
|
842 * database; or RESULT_FAILED if there was an unrecoverable error and |
|
843 * we must run without a database. |
|
844 * |
|
845 * If RESULT_RETRY or RESULT_FAILED is returned, the caller should perform |
|
846 * cleanup of the default DBState. |
|
847 */ |
|
848 OpenDBResult |
|
849 nsCookieService::TryInitDB(bool aRecreateDB) |
|
850 { |
|
851 NS_ASSERTION(!mDefaultDBState->dbConn, "nonnull dbConn"); |
|
852 NS_ASSERTION(!mDefaultDBState->stmtInsert, "nonnull stmtInsert"); |
|
853 NS_ASSERTION(!mDefaultDBState->insertListener, "nonnull insertListener"); |
|
854 NS_ASSERTION(!mDefaultDBState->syncConn, "nonnull syncConn"); |
|
855 |
|
856 // Ditch an existing db, if we've been told to (i.e. it's corrupt). We don't |
|
857 // want to delete it outright, since it may be useful for debugging purposes, |
|
858 // so we move it out of the way. |
|
859 nsresult rv; |
|
860 if (aRecreateDB) { |
|
861 nsCOMPtr<nsIFile> backupFile; |
|
862 mDefaultDBState->cookieFile->Clone(getter_AddRefs(backupFile)); |
|
863 rv = backupFile->MoveToNative(nullptr, |
|
864 NS_LITERAL_CSTRING(COOKIES_FILE ".bak")); |
|
865 NS_ENSURE_SUCCESS(rv, RESULT_FAILURE); |
|
866 } |
|
867 |
|
868 // This block provides scope for the Telemetry AutoTimer |
|
869 { |
|
870 Telemetry::AutoTimer<Telemetry::MOZ_SQLITE_COOKIES_OPEN_READAHEAD_MS> |
|
871 telemetry; |
|
872 ReadAheadFile(mDefaultDBState->cookieFile); |
|
873 |
|
874 // open a connection to the cookie database, and only cache our connection |
|
875 // and statements upon success. The connection is opened unshared to eliminate |
|
876 // cache contention between the main and background threads. |
|
877 rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile, |
|
878 getter_AddRefs(mDefaultDBState->dbConn)); |
|
879 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
880 } |
|
881 |
|
882 // Set up our listeners. |
|
883 mDefaultDBState->insertListener = new InsertCookieDBListener(mDefaultDBState); |
|
884 mDefaultDBState->updateListener = new UpdateCookieDBListener(mDefaultDBState); |
|
885 mDefaultDBState->removeListener = new RemoveCookieDBListener(mDefaultDBState); |
|
886 mDefaultDBState->closeListener = new CloseCookieDBListener(mDefaultDBState); |
|
887 |
|
888 // Grow cookie db in 512KB increments |
|
889 mDefaultDBState->dbConn->SetGrowthIncrement(512 * 1024, EmptyCString()); |
|
890 |
|
891 bool tableExists = false; |
|
892 mDefaultDBState->dbConn->TableExists(NS_LITERAL_CSTRING("moz_cookies"), |
|
893 &tableExists); |
|
894 if (!tableExists) { |
|
895 rv = CreateTable(); |
|
896 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
897 |
|
898 } else { |
|
899 // table already exists; check the schema version before reading |
|
900 int32_t dbSchemaVersion; |
|
901 rv = mDefaultDBState->dbConn->GetSchemaVersion(&dbSchemaVersion); |
|
902 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
903 |
|
904 // Start a transaction for the whole migration block. |
|
905 mozStorageTransaction transaction(mDefaultDBState->dbConn, true); |
|
906 |
|
907 switch (dbSchemaVersion) { |
|
908 // Upgrading. |
|
909 // Every time you increment the database schema, you need to implement |
|
910 // the upgrading code from the previous version to the new one. If migration |
|
911 // fails for any reason, it's a bug -- so we return RESULT_RETRY such that |
|
912 // the original database will be saved, in the hopes that we might one day |
|
913 // see it and fix it. |
|
914 case 1: |
|
915 { |
|
916 // Add the lastAccessed column to the table. |
|
917 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
918 "ALTER TABLE moz_cookies ADD lastAccessed INTEGER")); |
|
919 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
920 } |
|
921 // Fall through to the next upgrade. |
|
922 |
|
923 case 2: |
|
924 { |
|
925 // Add the baseDomain column and index to the table. |
|
926 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
927 "ALTER TABLE moz_cookies ADD baseDomain TEXT")); |
|
928 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
929 |
|
930 // Compute the baseDomains for the table. This must be done eagerly |
|
931 // otherwise we won't be able to synchronously read in individual |
|
932 // domains on demand. |
|
933 const int64_t SCHEMA2_IDX_ID = 0; |
|
934 const int64_t SCHEMA2_IDX_HOST = 1; |
|
935 nsCOMPtr<mozIStorageStatement> select; |
|
936 rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
937 "SELECT id, host FROM moz_cookies"), getter_AddRefs(select)); |
|
938 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
939 |
|
940 nsCOMPtr<mozIStorageStatement> update; |
|
941 rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
942 "UPDATE moz_cookies SET baseDomain = :baseDomain WHERE id = :id"), |
|
943 getter_AddRefs(update)); |
|
944 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
945 |
|
946 nsCString baseDomain, host; |
|
947 bool hasResult; |
|
948 while (1) { |
|
949 rv = select->ExecuteStep(&hasResult); |
|
950 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
951 |
|
952 if (!hasResult) |
|
953 break; |
|
954 |
|
955 int64_t id = select->AsInt64(SCHEMA2_IDX_ID); |
|
956 select->GetUTF8String(SCHEMA2_IDX_HOST, host); |
|
957 |
|
958 rv = GetBaseDomainFromHost(host, baseDomain); |
|
959 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
960 |
|
961 mozStorageStatementScoper scoper(update); |
|
962 |
|
963 rv = update->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"), |
|
964 baseDomain); |
|
965 NS_ASSERT_SUCCESS(rv); |
|
966 rv = update->BindInt64ByName(NS_LITERAL_CSTRING("id"), |
|
967 id); |
|
968 NS_ASSERT_SUCCESS(rv); |
|
969 |
|
970 rv = update->ExecuteStep(&hasResult); |
|
971 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
972 } |
|
973 |
|
974 // Create an index on baseDomain. |
|
975 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
976 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)")); |
|
977 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
978 } |
|
979 // Fall through to the next upgrade. |
|
980 |
|
981 case 3: |
|
982 { |
|
983 // Add the creationTime column to the table, and create a unique index |
|
984 // on (name, host, path). Before we do this, we have to purge the table |
|
985 // of expired cookies such that we know that the (name, host, path) |
|
986 // index is truly unique -- otherwise we can't create the index. Note |
|
987 // that we can't just execute a statement to delete all rows where the |
|
988 // expiry column is in the past -- doing so would rely on the clock |
|
989 // (both now and when previous cookies were set) being monotonic. |
|
990 |
|
991 // Select the whole table, and order by the fields we're interested in. |
|
992 // This means we can simply do a linear traversal of the results and |
|
993 // check for duplicates as we go. |
|
994 const int64_t SCHEMA3_IDX_ID = 0; |
|
995 const int64_t SCHEMA3_IDX_NAME = 1; |
|
996 const int64_t SCHEMA3_IDX_HOST = 2; |
|
997 const int64_t SCHEMA3_IDX_PATH = 3; |
|
998 nsCOMPtr<mozIStorageStatement> select; |
|
999 rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
1000 "SELECT id, name, host, path FROM moz_cookies " |
|
1001 "ORDER BY name ASC, host ASC, path ASC, expiry ASC"), |
|
1002 getter_AddRefs(select)); |
|
1003 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1004 |
|
1005 nsCOMPtr<mozIStorageStatement> deleteExpired; |
|
1006 rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
1007 "DELETE FROM moz_cookies WHERE id = :id"), |
|
1008 getter_AddRefs(deleteExpired)); |
|
1009 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1010 |
|
1011 // Read the first row. |
|
1012 bool hasResult; |
|
1013 rv = select->ExecuteStep(&hasResult); |
|
1014 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1015 |
|
1016 if (hasResult) { |
|
1017 nsCString name1, host1, path1; |
|
1018 int64_t id1 = select->AsInt64(SCHEMA3_IDX_ID); |
|
1019 select->GetUTF8String(SCHEMA3_IDX_NAME, name1); |
|
1020 select->GetUTF8String(SCHEMA3_IDX_HOST, host1); |
|
1021 select->GetUTF8String(SCHEMA3_IDX_PATH, path1); |
|
1022 |
|
1023 nsCString name2, host2, path2; |
|
1024 while (1) { |
|
1025 // Read the second row. |
|
1026 rv = select->ExecuteStep(&hasResult); |
|
1027 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1028 |
|
1029 if (!hasResult) |
|
1030 break; |
|
1031 |
|
1032 int64_t id2 = select->AsInt64(SCHEMA3_IDX_ID); |
|
1033 select->GetUTF8String(SCHEMA3_IDX_NAME, name2); |
|
1034 select->GetUTF8String(SCHEMA3_IDX_HOST, host2); |
|
1035 select->GetUTF8String(SCHEMA3_IDX_PATH, path2); |
|
1036 |
|
1037 // If the two rows match in (name, host, path), we know the earlier |
|
1038 // row has an earlier expiry time. Delete it. |
|
1039 if (name1 == name2 && host1 == host2 && path1 == path2) { |
|
1040 mozStorageStatementScoper scoper(deleteExpired); |
|
1041 |
|
1042 rv = deleteExpired->BindInt64ByName(NS_LITERAL_CSTRING("id"), |
|
1043 id1); |
|
1044 NS_ASSERT_SUCCESS(rv); |
|
1045 |
|
1046 rv = deleteExpired->ExecuteStep(&hasResult); |
|
1047 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1048 } |
|
1049 |
|
1050 // Make the second row the first for the next iteration. |
|
1051 name1 = name2; |
|
1052 host1 = host2; |
|
1053 path1 = path2; |
|
1054 id1 = id2; |
|
1055 } |
|
1056 } |
|
1057 |
|
1058 // Add the creationTime column to the table. |
|
1059 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1060 "ALTER TABLE moz_cookies ADD creationTime INTEGER")); |
|
1061 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1062 |
|
1063 // Copy the id of each row into the new creationTime column. |
|
1064 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1065 "UPDATE moz_cookies SET creationTime = " |
|
1066 "(SELECT id WHERE id = moz_cookies.id)")); |
|
1067 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1068 |
|
1069 // Create a unique index on (name, host, path) to allow fast lookup. |
|
1070 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1071 "CREATE UNIQUE INDEX moz_uniqueid " |
|
1072 "ON moz_cookies (name, host, path)")); |
|
1073 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1074 } |
|
1075 // Fall through to the next upgrade. |
|
1076 |
|
1077 case 4: |
|
1078 { |
|
1079 // We need to add appId/inBrowserElement, plus change a constraint on |
|
1080 // the table (unique entries now include appId/inBrowserElement): |
|
1081 // this requires creating a new table and copying the data to it. We |
|
1082 // then rename the new table to the old name. |
|
1083 // |
|
1084 // Why we made this change: appId/inBrowserElement allow "cookie jars" |
|
1085 // for Firefox OS. We create a separate cookie namespace per {appId, |
|
1086 // inBrowserElement}. When upgrading, we convert existing cookies |
|
1087 // (which imply we're on desktop/mobile) to use {0, false}, as that is |
|
1088 // the only namespace used by a non-Firefox-OS implementation. |
|
1089 |
|
1090 // Rename existing table |
|
1091 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1092 "ALTER TABLE moz_cookies RENAME TO moz_cookies_old")); |
|
1093 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1094 |
|
1095 // Drop existing index (CreateTable will create new one for new table) |
|
1096 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1097 "DROP INDEX moz_basedomain")); |
|
1098 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1099 |
|
1100 // Create new table (with new fields and new unique constraint) |
|
1101 rv = CreateTable(); |
|
1102 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1103 |
|
1104 // Copy data from old table, using appId/inBrowser=0 for existing rows |
|
1105 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1106 "INSERT INTO moz_cookies " |
|
1107 "(baseDomain, appId, inBrowserElement, name, value, host, path, expiry," |
|
1108 " lastAccessed, creationTime, isSecure, isHttpOnly) " |
|
1109 "SELECT baseDomain, 0, 0, name, value, host, path, expiry," |
|
1110 " lastAccessed, creationTime, isSecure, isHttpOnly " |
|
1111 "FROM moz_cookies_old")); |
|
1112 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1113 |
|
1114 // Drop old table |
|
1115 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1116 "DROP TABLE moz_cookies_old")); |
|
1117 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1118 |
|
1119 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
1120 ("Upgraded database to schema version 5")); |
|
1121 } |
|
1122 |
|
1123 // No more upgrades. Update the schema version. |
|
1124 rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); |
|
1125 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1126 |
|
1127 case COOKIES_SCHEMA_VERSION: |
|
1128 break; |
|
1129 |
|
1130 case 0: |
|
1131 { |
|
1132 NS_WARNING("couldn't get schema version!"); |
|
1133 |
|
1134 // the table may be usable; someone might've just clobbered the schema |
|
1135 // version. we can treat this case like a downgrade using the codepath |
|
1136 // below, by verifying the columns we care about are all there. for now, |
|
1137 // re-set the schema version in the db, in case the checks succeed (if |
|
1138 // they don't, we're dropping the table anyway). |
|
1139 rv = mDefaultDBState->dbConn->SetSchemaVersion(COOKIES_SCHEMA_VERSION); |
|
1140 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1141 } |
|
1142 // fall through to downgrade check |
|
1143 |
|
1144 // downgrading. |
|
1145 // if columns have been added to the table, we can still use the ones we |
|
1146 // understand safely. if columns have been deleted or altered, just |
|
1147 // blow away the table and start from scratch! if you change the way |
|
1148 // a column is interpreted, make sure you also change its name so this |
|
1149 // check will catch it. |
|
1150 default: |
|
1151 { |
|
1152 // check if all the expected columns exist |
|
1153 nsCOMPtr<mozIStorageStatement> stmt; |
|
1154 rv = mDefaultDBState->dbConn->CreateStatement(NS_LITERAL_CSTRING( |
|
1155 "SELECT " |
|
1156 "id, " |
|
1157 "baseDomain, " |
|
1158 "appId, " |
|
1159 "inBrowserElement, " |
|
1160 "name, " |
|
1161 "value, " |
|
1162 "host, " |
|
1163 "path, " |
|
1164 "expiry, " |
|
1165 "lastAccessed, " |
|
1166 "creationTime, " |
|
1167 "isSecure, " |
|
1168 "isHttpOnly " |
|
1169 "FROM moz_cookies"), getter_AddRefs(stmt)); |
|
1170 if (NS_SUCCEEDED(rv)) |
|
1171 break; |
|
1172 |
|
1173 // our columns aren't there - drop the table! |
|
1174 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1175 "DROP TABLE moz_cookies")); |
|
1176 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1177 |
|
1178 rv = CreateTable(); |
|
1179 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1180 } |
|
1181 break; |
|
1182 } |
|
1183 } |
|
1184 |
|
1185 // make operations on the table asynchronous, for performance |
|
1186 mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1187 "PRAGMA synchronous = OFF")); |
|
1188 |
|
1189 // Use write-ahead-logging for performance. We cap the autocheckpoint limit at |
|
1190 // 16 pages (around 500KB). |
|
1191 mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1192 MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = WAL")); |
|
1193 mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1194 "PRAGMA wal_autocheckpoint = 16")); |
|
1195 |
|
1196 // cache frequently used statements (for insertion, deletion, and updating) |
|
1197 rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
|
1198 "INSERT INTO moz_cookies (" |
|
1199 "baseDomain, " |
|
1200 "appId, " |
|
1201 "inBrowserElement, " |
|
1202 "name, " |
|
1203 "value, " |
|
1204 "host, " |
|
1205 "path, " |
|
1206 "expiry, " |
|
1207 "lastAccessed, " |
|
1208 "creationTime, " |
|
1209 "isSecure, " |
|
1210 "isHttpOnly" |
|
1211 ") VALUES (" |
|
1212 ":baseDomain, " |
|
1213 ":appId, " |
|
1214 ":inBrowserElement, " |
|
1215 ":name, " |
|
1216 ":value, " |
|
1217 ":host, " |
|
1218 ":path, " |
|
1219 ":expiry, " |
|
1220 ":lastAccessed, " |
|
1221 ":creationTime, " |
|
1222 ":isSecure, " |
|
1223 ":isHttpOnly" |
|
1224 ")"), |
|
1225 getter_AddRefs(mDefaultDBState->stmtInsert)); |
|
1226 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1227 |
|
1228 rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
|
1229 "DELETE FROM moz_cookies " |
|
1230 "WHERE name = :name AND host = :host AND path = :path"), |
|
1231 getter_AddRefs(mDefaultDBState->stmtDelete)); |
|
1232 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1233 |
|
1234 rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
|
1235 "UPDATE moz_cookies SET lastAccessed = :lastAccessed " |
|
1236 "WHERE name = :name AND host = :host AND path = :path"), |
|
1237 getter_AddRefs(mDefaultDBState->stmtUpdate)); |
|
1238 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
1239 |
|
1240 // if we deleted a corrupt db, don't attempt to import - return now |
|
1241 if (aRecreateDB) |
|
1242 return RESULT_OK; |
|
1243 |
|
1244 // check whether to import or just read in the db |
|
1245 if (tableExists) |
|
1246 return Read(); |
|
1247 |
|
1248 nsCOMPtr<nsIFile> oldCookieFile; |
|
1249 rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, |
|
1250 getter_AddRefs(oldCookieFile)); |
|
1251 if (NS_FAILED(rv)) return RESULT_OK; |
|
1252 |
|
1253 // Import cookies, and clean up the old file regardless of success or failure. |
|
1254 // Note that we have to switch out our DBState temporarily, in case we're in |
|
1255 // private browsing mode; otherwise ImportCookies() won't be happy. |
|
1256 DBState* initialState = mDBState; |
|
1257 mDBState = mDefaultDBState; |
|
1258 oldCookieFile->AppendNative(NS_LITERAL_CSTRING(kOldCookieFileName)); |
|
1259 ImportCookies(oldCookieFile); |
|
1260 oldCookieFile->Remove(false); |
|
1261 mDBState = initialState; |
|
1262 |
|
1263 return RESULT_OK; |
|
1264 } |
|
1265 |
|
1266 // Sets the schema version and creates the moz_cookies table. |
|
1267 nsresult |
|
1268 nsCookieService::CreateTable() |
|
1269 { |
|
1270 // Set the schema version, before creating the table. |
|
1271 nsresult rv = mDefaultDBState->dbConn->SetSchemaVersion( |
|
1272 COOKIES_SCHEMA_VERSION); |
|
1273 if (NS_FAILED(rv)) return rv; |
|
1274 |
|
1275 // Create the table. We default appId/inBrowserElement to 0: this is so if |
|
1276 // users revert to an older Firefox version that doesn't know about these |
|
1277 // fields, any cookies set will still work once they upgrade back. |
|
1278 rv = mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1279 "CREATE TABLE moz_cookies (" |
|
1280 "id INTEGER PRIMARY KEY, " |
|
1281 "baseDomain TEXT, " |
|
1282 "appId INTEGER DEFAULT 0, " |
|
1283 "inBrowserElement INTEGER DEFAULT 0, " |
|
1284 "name TEXT, " |
|
1285 "value TEXT, " |
|
1286 "host TEXT, " |
|
1287 "path TEXT, " |
|
1288 "expiry INTEGER, " |
|
1289 "lastAccessed INTEGER, " |
|
1290 "creationTime INTEGER, " |
|
1291 "isSecure INTEGER, " |
|
1292 "isHttpOnly INTEGER, " |
|
1293 "CONSTRAINT moz_uniqueid UNIQUE (name, host, path, appId, inBrowserElement)" |
|
1294 ")")); |
|
1295 if (NS_FAILED(rv)) return rv; |
|
1296 |
|
1297 // Create an index on baseDomain. |
|
1298 return mDefaultDBState->dbConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING( |
|
1299 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain, " |
|
1300 "appId, " |
|
1301 "inBrowserElement)")); |
|
1302 } |
|
1303 |
|
1304 void |
|
1305 nsCookieService::CloseDBStates() |
|
1306 { |
|
1307 // Null out our private and pointer DBStates regardless. |
|
1308 mPrivateDBState = nullptr; |
|
1309 mDBState = nullptr; |
|
1310 |
|
1311 // If we don't have a default DBState, we're done. |
|
1312 if (!mDefaultDBState) |
|
1313 return; |
|
1314 |
|
1315 // Cleanup cached statements before we can close anything. |
|
1316 CleanupCachedStatements(); |
|
1317 |
|
1318 if (mDefaultDBState->dbConn) { |
|
1319 // Cancel any pending read. No further results will be received by our |
|
1320 // read listener. |
|
1321 if (mDefaultDBState->pendingRead) { |
|
1322 CancelAsyncRead(true); |
|
1323 } |
|
1324 |
|
1325 // Asynchronously close the connection. We will null it below. |
|
1326 mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener); |
|
1327 } |
|
1328 |
|
1329 CleanupDefaultDBConnection(); |
|
1330 |
|
1331 mDefaultDBState = nullptr; |
|
1332 } |
|
1333 |
|
1334 // Null out the statements. |
|
1335 // This must be done before closing the connection. |
|
1336 void |
|
1337 nsCookieService::CleanupCachedStatements() |
|
1338 { |
|
1339 mDefaultDBState->stmtInsert = nullptr; |
|
1340 mDefaultDBState->stmtDelete = nullptr; |
|
1341 mDefaultDBState->stmtUpdate = nullptr; |
|
1342 } |
|
1343 |
|
1344 // Null out the listeners, and the database connection itself. This |
|
1345 // will not null out the statements, cancel a pending read or |
|
1346 // asynchronously close the connection -- these must be done |
|
1347 // beforehand if necessary. |
|
1348 void |
|
1349 nsCookieService::CleanupDefaultDBConnection() |
|
1350 { |
|
1351 MOZ_ASSERT(!mDefaultDBState->stmtInsert, "stmtInsert has been cleaned up"); |
|
1352 MOZ_ASSERT(!mDefaultDBState->stmtDelete, "stmtDelete has been cleaned up"); |
|
1353 MOZ_ASSERT(!mDefaultDBState->stmtUpdate, "stmtUpdate has been cleaned up"); |
|
1354 |
|
1355 // Null out the database connections. If 'dbConn' has not been used for any |
|
1356 // asynchronous operations yet, this will synchronously close it; otherwise, |
|
1357 // it's expected that the caller has performed an AsyncClose prior. |
|
1358 mDefaultDBState->dbConn = nullptr; |
|
1359 mDefaultDBState->syncConn = nullptr; |
|
1360 |
|
1361 // Manually null out our listeners. This is necessary because they hold a |
|
1362 // strong ref to the DBState itself. They'll stay alive until whatever |
|
1363 // statements are still executing complete. |
|
1364 mDefaultDBState->readListener = nullptr; |
|
1365 mDefaultDBState->insertListener = nullptr; |
|
1366 mDefaultDBState->updateListener = nullptr; |
|
1367 mDefaultDBState->removeListener = nullptr; |
|
1368 mDefaultDBState->closeListener = nullptr; |
|
1369 } |
|
1370 |
|
1371 void |
|
1372 nsCookieService::HandleDBClosed(DBState* aDBState) |
|
1373 { |
|
1374 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
1375 ("HandleDBClosed(): DBState %x closed", aDBState)); |
|
1376 |
|
1377 switch (aDBState->corruptFlag) { |
|
1378 case DBState::OK: { |
|
1379 // Database is healthy. Notify of closure. |
|
1380 mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
|
1381 break; |
|
1382 } |
|
1383 case DBState::CLOSING_FOR_REBUILD: { |
|
1384 // Our close finished. Start the rebuild, and notify of db closure later. |
|
1385 RebuildCorruptDB(aDBState); |
|
1386 break; |
|
1387 } |
|
1388 case DBState::REBUILDING: { |
|
1389 // We encountered an error during rebuild, closed the database, and now |
|
1390 // here we are. We already have a 'cookies.sqlite.bak' from the original |
|
1391 // dead database; we don't want to overwrite it, so let's move this one to |
|
1392 // 'cookies.sqlite.bak-rebuild'. |
|
1393 nsCOMPtr<nsIFile> backupFile; |
|
1394 aDBState->cookieFile->Clone(getter_AddRefs(backupFile)); |
|
1395 nsresult rv = backupFile->MoveToNative(nullptr, |
|
1396 NS_LITERAL_CSTRING(COOKIES_FILE ".bak-rebuild")); |
|
1397 |
|
1398 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
1399 ("HandleDBClosed(): DBState %x encountered error rebuilding db; move to " |
|
1400 "'cookies.sqlite.bak-rebuild' gave rv 0x%x", aDBState, rv)); |
|
1401 mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
|
1402 break; |
|
1403 } |
|
1404 } |
|
1405 } |
|
1406 |
|
1407 void |
|
1408 nsCookieService::HandleCorruptDB(DBState* aDBState) |
|
1409 { |
|
1410 if (mDefaultDBState != aDBState) { |
|
1411 // We've either closed the state or we've switched profiles. It's getting |
|
1412 // a bit late to rebuild -- bail instead. |
|
1413 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
1414 ("HandleCorruptDB(): DBState %x is already closed, aborting", aDBState)); |
|
1415 return; |
|
1416 } |
|
1417 |
|
1418 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
1419 ("HandleCorruptDB(): DBState %x has corruptFlag %u", aDBState, |
|
1420 aDBState->corruptFlag)); |
|
1421 |
|
1422 // Mark the database corrupt, so the close listener can begin reconstructing |
|
1423 // it. |
|
1424 switch (mDefaultDBState->corruptFlag) { |
|
1425 case DBState::OK: { |
|
1426 // Move to 'closing' state. |
|
1427 mDefaultDBState->corruptFlag = DBState::CLOSING_FOR_REBUILD; |
|
1428 |
|
1429 // Cancel any pending read and close the database. If we do have an |
|
1430 // in-flight read we want to throw away all the results so far -- we have no |
|
1431 // idea how consistent the database is. Note that we may have already |
|
1432 // canceled the read but not emptied our readSet; do so now. |
|
1433 mDefaultDBState->readSet.Clear(); |
|
1434 if (mDefaultDBState->pendingRead) { |
|
1435 CancelAsyncRead(true); |
|
1436 mDefaultDBState->syncConn = nullptr; |
|
1437 } |
|
1438 |
|
1439 CleanupCachedStatements(); |
|
1440 mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener); |
|
1441 CleanupDefaultDBConnection(); |
|
1442 break; |
|
1443 } |
|
1444 case DBState::CLOSING_FOR_REBUILD: { |
|
1445 // We had an error while waiting for close completion. That's OK, just |
|
1446 // ignore it -- we're rebuilding anyway. |
|
1447 return; |
|
1448 } |
|
1449 case DBState::REBUILDING: { |
|
1450 // We had an error while rebuilding the DB. Game over. Close the database |
|
1451 // and let the close handler do nothing; then we'll move it out of the way. |
|
1452 CleanupCachedStatements(); |
|
1453 if (mDefaultDBState->dbConn) { |
|
1454 mDefaultDBState->dbConn->AsyncClose(mDefaultDBState->closeListener); |
|
1455 } |
|
1456 CleanupDefaultDBConnection(); |
|
1457 break; |
|
1458 } |
|
1459 } |
|
1460 } |
|
1461 |
|
1462 static PLDHashOperator |
|
1463 RebuildDBCallback(nsCookieEntry *aEntry, |
|
1464 void *aArg) |
|
1465 { |
|
1466 mozIStorageBindingParamsArray* paramsArray = |
|
1467 static_cast<mozIStorageBindingParamsArray*>(aArg); |
|
1468 |
|
1469 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies(); |
|
1470 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
1471 nsCookie* cookie = cookies[i]; |
|
1472 |
|
1473 if (!cookie->IsSession()) { |
|
1474 bindCookieParameters(paramsArray, aEntry, cookie); |
|
1475 } |
|
1476 } |
|
1477 |
|
1478 return PL_DHASH_NEXT; |
|
1479 } |
|
1480 |
|
1481 void |
|
1482 nsCookieService::RebuildCorruptDB(DBState* aDBState) |
|
1483 { |
|
1484 NS_ASSERTION(!aDBState->dbConn, "shouldn't have an open db connection"); |
|
1485 NS_ASSERTION(aDBState->corruptFlag == DBState::CLOSING_FOR_REBUILD, |
|
1486 "should be in CLOSING_FOR_REBUILD state"); |
|
1487 |
|
1488 aDBState->corruptFlag = DBState::REBUILDING; |
|
1489 |
|
1490 if (mDefaultDBState != aDBState) { |
|
1491 // We've either closed the state or we've switched profiles. It's getting |
|
1492 // a bit late to rebuild -- bail instead. In any case, we were waiting |
|
1493 // on rebuild completion to notify of the db closure, which won't happen -- |
|
1494 // do so now. |
|
1495 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
1496 ("RebuildCorruptDB(): DBState %x is stale, aborting", aDBState)); |
|
1497 mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
|
1498 return; |
|
1499 } |
|
1500 |
|
1501 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
1502 ("RebuildCorruptDB(): creating new database")); |
|
1503 |
|
1504 // The database has been closed, and we're ready to rebuild. Open a |
|
1505 // connection. |
|
1506 OpenDBResult result = TryInitDB(true); |
|
1507 if (result != RESULT_OK) { |
|
1508 // We're done. Reset our DB connection and statements, and notify of |
|
1509 // closure. |
|
1510 COOKIE_LOGSTRING(PR_LOG_WARNING, |
|
1511 ("RebuildCorruptDB(): TryInitDB() failed with result %u", result)); |
|
1512 CleanupCachedStatements(); |
|
1513 CleanupDefaultDBConnection(); |
|
1514 mDefaultDBState->corruptFlag = DBState::OK; |
|
1515 mObserverService->NotifyObservers(nullptr, "cookie-db-closed", nullptr); |
|
1516 return; |
|
1517 } |
|
1518 |
|
1519 // Notify observers that we're beginning the rebuild. |
|
1520 mObserverService->NotifyObservers(nullptr, "cookie-db-rebuilding", nullptr); |
|
1521 |
|
1522 // Enumerate the hash, and add cookies to the params array. |
|
1523 mozIStorageAsyncStatement* stmt = aDBState->stmtInsert; |
|
1524 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
|
1525 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
|
1526 aDBState->hostTable.EnumerateEntries(RebuildDBCallback, paramsArray.get()); |
|
1527 |
|
1528 // Make sure we've got something to write. If we don't, we're done. |
|
1529 uint32_t length; |
|
1530 paramsArray->GetLength(&length); |
|
1531 if (length == 0) { |
|
1532 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
1533 ("RebuildCorruptDB(): nothing to write, rebuild complete")); |
|
1534 mDefaultDBState->corruptFlag = DBState::OK; |
|
1535 return; |
|
1536 } |
|
1537 |
|
1538 // Execute the statement. If any errors crop up, we won't try again. |
|
1539 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
|
1540 NS_ASSERT_SUCCESS(rv); |
|
1541 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
1542 rv = stmt->ExecuteAsync(aDBState->insertListener, getter_AddRefs(handle)); |
|
1543 NS_ASSERT_SUCCESS(rv); |
|
1544 } |
|
1545 |
|
1546 nsCookieService::~nsCookieService() |
|
1547 { |
|
1548 CloseDBStates(); |
|
1549 |
|
1550 UnregisterWeakMemoryReporter(this); |
|
1551 |
|
1552 gCookieService = nullptr; |
|
1553 } |
|
1554 |
|
1555 NS_IMETHODIMP |
|
1556 nsCookieService::Observe(nsISupports *aSubject, |
|
1557 const char *aTopic, |
|
1558 const char16_t *aData) |
|
1559 { |
|
1560 // check the topic |
|
1561 if (!strcmp(aTopic, "profile-before-change")) { |
|
1562 // The profile is about to change, |
|
1563 // or is going away because the application is shutting down. |
|
1564 if (mDBState && mDBState->dbConn && |
|
1565 !nsCRT::strcmp(aData, MOZ_UTF16("shutdown-cleanse"))) { |
|
1566 // Clear the cookie db if we're in the default DBState. |
|
1567 RemoveAll(); |
|
1568 } |
|
1569 |
|
1570 // Close the default DB connection and null out our DBStates before |
|
1571 // changing. |
|
1572 CloseDBStates(); |
|
1573 |
|
1574 } else if (!strcmp(aTopic, "profile-do-change")) { |
|
1575 NS_ASSERTION(!mDefaultDBState, "shouldn't have a default DBState"); |
|
1576 NS_ASSERTION(!mPrivateDBState, "shouldn't have a private DBState"); |
|
1577 |
|
1578 // the profile has already changed; init the db from the new location. |
|
1579 // if we are in the private browsing state, however, we do not want to read |
|
1580 // data into it - we should instead put it into the default state, so it's |
|
1581 // ready for us if and when we switch back to it. |
|
1582 InitDBStates(); |
|
1583 |
|
1584 } else if (!strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { |
|
1585 nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(aSubject); |
|
1586 if (prefBranch) |
|
1587 PrefChanged(prefBranch); |
|
1588 |
|
1589 } else if (!strcmp(aTopic, "last-pb-context-exited")) { |
|
1590 // Flush all the cookies stored by private browsing contexts |
|
1591 mPrivateDBState = new DBState(); |
|
1592 } |
|
1593 |
|
1594 |
|
1595 return NS_OK; |
|
1596 } |
|
1597 |
|
1598 NS_IMETHODIMP |
|
1599 nsCookieService::GetCookieString(nsIURI *aHostURI, |
|
1600 nsIChannel *aChannel, |
|
1601 char **aCookie) |
|
1602 { |
|
1603 return GetCookieStringCommon(aHostURI, aChannel, false, aCookie); |
|
1604 } |
|
1605 |
|
1606 NS_IMETHODIMP |
|
1607 nsCookieService::GetCookieStringFromHttp(nsIURI *aHostURI, |
|
1608 nsIURI *aFirstURI, |
|
1609 nsIChannel *aChannel, |
|
1610 char **aCookie) |
|
1611 { |
|
1612 return GetCookieStringCommon(aHostURI, aChannel, true, aCookie); |
|
1613 } |
|
1614 |
|
1615 nsresult |
|
1616 nsCookieService::GetCookieStringCommon(nsIURI *aHostURI, |
|
1617 nsIChannel *aChannel, |
|
1618 bool aHttpBound, |
|
1619 char** aCookie) |
|
1620 { |
|
1621 NS_ENSURE_ARG(aHostURI); |
|
1622 NS_ENSURE_ARG(aCookie); |
|
1623 |
|
1624 // Determine whether the request is foreign. Failure is acceptable. |
|
1625 bool isForeign = true; |
|
1626 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); |
|
1627 |
|
1628 // Get app info, if channel is present. Else assume default namespace. |
|
1629 uint32_t appId = NECKO_NO_APP_ID; |
|
1630 bool inBrowserElement = false; |
|
1631 if (aChannel) { |
|
1632 NS_GetAppInfo(aChannel, &appId, &inBrowserElement); |
|
1633 } |
|
1634 |
|
1635 bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel); |
|
1636 |
|
1637 nsAutoCString result; |
|
1638 GetCookieStringInternal(aHostURI, isForeign, aHttpBound, appId, |
|
1639 inBrowserElement, isPrivate, result); |
|
1640 *aCookie = result.IsEmpty() ? nullptr : ToNewCString(result); |
|
1641 return NS_OK; |
|
1642 } |
|
1643 |
|
1644 NS_IMETHODIMP |
|
1645 nsCookieService::SetCookieString(nsIURI *aHostURI, |
|
1646 nsIPrompt *aPrompt, |
|
1647 const char *aCookieHeader, |
|
1648 nsIChannel *aChannel) |
|
1649 { |
|
1650 // The aPrompt argument is deprecated and unused. Avoid introducing new |
|
1651 // code that uses this argument by warning if the value is non-null. |
|
1652 MOZ_ASSERT(!aPrompt); |
|
1653 if (aPrompt) { |
|
1654 nsCOMPtr<nsIConsoleService> aConsoleService = |
|
1655 do_GetService("@mozilla.org/consoleservice;1"); |
|
1656 if (aConsoleService) { |
|
1657 aConsoleService->LogStringMessage( |
|
1658 MOZ_UTF16("Non-null prompt ignored by nsCookieService.")); |
|
1659 } |
|
1660 } |
|
1661 return SetCookieStringCommon(aHostURI, aCookieHeader, nullptr, aChannel, |
|
1662 false); |
|
1663 } |
|
1664 |
|
1665 NS_IMETHODIMP |
|
1666 nsCookieService::SetCookieStringFromHttp(nsIURI *aHostURI, |
|
1667 nsIURI *aFirstURI, |
|
1668 nsIPrompt *aPrompt, |
|
1669 const char *aCookieHeader, |
|
1670 const char *aServerTime, |
|
1671 nsIChannel *aChannel) |
|
1672 { |
|
1673 // The aPrompt argument is deprecated and unused. Avoid introducing new |
|
1674 // code that uses this argument by warning if the value is non-null. |
|
1675 MOZ_ASSERT(!aPrompt); |
|
1676 if (aPrompt) { |
|
1677 nsCOMPtr<nsIConsoleService> aConsoleService = |
|
1678 do_GetService("@mozilla.org/consoleservice;1"); |
|
1679 if (aConsoleService) { |
|
1680 aConsoleService->LogStringMessage( |
|
1681 MOZ_UTF16("Non-null prompt ignored by nsCookieService.")); |
|
1682 } |
|
1683 } |
|
1684 return SetCookieStringCommon(aHostURI, aCookieHeader, aServerTime, aChannel, |
|
1685 true); |
|
1686 } |
|
1687 |
|
1688 nsresult |
|
1689 nsCookieService::SetCookieStringCommon(nsIURI *aHostURI, |
|
1690 const char *aCookieHeader, |
|
1691 const char *aServerTime, |
|
1692 nsIChannel *aChannel, |
|
1693 bool aFromHttp) |
|
1694 { |
|
1695 NS_ENSURE_ARG(aHostURI); |
|
1696 NS_ENSURE_ARG(aCookieHeader); |
|
1697 |
|
1698 // Determine whether the request is foreign. Failure is acceptable. |
|
1699 bool isForeign = true; |
|
1700 mThirdPartyUtil->IsThirdPartyChannel(aChannel, aHostURI, &isForeign); |
|
1701 |
|
1702 // Get app info, if channel is present. Else assume default namespace. |
|
1703 uint32_t appId = NECKO_NO_APP_ID; |
|
1704 bool inBrowserElement = false; |
|
1705 if (aChannel) { |
|
1706 NS_GetAppInfo(aChannel, &appId, &inBrowserElement); |
|
1707 } |
|
1708 |
|
1709 bool isPrivate = aChannel && NS_UsePrivateBrowsing(aChannel); |
|
1710 |
|
1711 nsDependentCString cookieString(aCookieHeader); |
|
1712 nsDependentCString serverTime(aServerTime ? aServerTime : ""); |
|
1713 SetCookieStringInternal(aHostURI, isForeign, cookieString, |
|
1714 serverTime, aFromHttp, appId, inBrowserElement, |
|
1715 isPrivate, aChannel); |
|
1716 return NS_OK; |
|
1717 } |
|
1718 |
|
1719 void |
|
1720 nsCookieService::SetCookieStringInternal(nsIURI *aHostURI, |
|
1721 bool aIsForeign, |
|
1722 nsDependentCString &aCookieHeader, |
|
1723 const nsCString &aServerTime, |
|
1724 bool aFromHttp, |
|
1725 uint32_t aAppId, |
|
1726 bool aInBrowserElement, |
|
1727 bool aIsPrivate, |
|
1728 nsIChannel *aChannel) |
|
1729 { |
|
1730 NS_ASSERTION(aHostURI, "null host!"); |
|
1731 |
|
1732 if (!mDBState) { |
|
1733 NS_WARNING("No DBState! Profile already closed?"); |
|
1734 return; |
|
1735 } |
|
1736 |
|
1737 AutoRestore<DBState*> savePrevDBState(mDBState); |
|
1738 mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState; |
|
1739 |
|
1740 // get the base domain for the host URI. |
|
1741 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". |
|
1742 // file:// URI's (i.e. with an empty host) are allowed, but any other |
|
1743 // scheme must have a non-empty host. A trailing dot in the host |
|
1744 // is acceptable. |
|
1745 bool requireHostMatch; |
|
1746 nsAutoCString baseDomain; |
|
1747 nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch); |
|
1748 if (NS_FAILED(rv)) { |
|
1749 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
1750 "couldn't get base domain from URI"); |
|
1751 return; |
|
1752 } |
|
1753 |
|
1754 nsCookieKey key(baseDomain, aAppId, aInBrowserElement); |
|
1755 |
|
1756 // check default prefs |
|
1757 CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch, |
|
1758 aCookieHeader.get()); |
|
1759 // fire a notification if third party or if cookie was rejected |
|
1760 // (but not if there was an error) |
|
1761 switch (cookieStatus) { |
|
1762 case STATUS_REJECTED: |
|
1763 NotifyRejected(aHostURI); |
|
1764 if (aIsForeign) { |
|
1765 NotifyThirdParty(aHostURI, false, aChannel); |
|
1766 } |
|
1767 return; // Stop here |
|
1768 case STATUS_REJECTED_WITH_ERROR: |
|
1769 return; |
|
1770 case STATUS_ACCEPTED: // Fallthrough |
|
1771 case STATUS_ACCEPT_SESSION: |
|
1772 if (aIsForeign) { |
|
1773 NotifyThirdParty(aHostURI, true, aChannel); |
|
1774 } |
|
1775 break; |
|
1776 default: |
|
1777 break; |
|
1778 } |
|
1779 |
|
1780 // parse server local time. this is not just done here for efficiency |
|
1781 // reasons - if there's an error parsing it, and we need to default it |
|
1782 // to the current time, we must do it here since the current time in |
|
1783 // SetCookieInternal() will change for each cookie processed (e.g. if the |
|
1784 // user is prompted). |
|
1785 PRTime tempServerTime; |
|
1786 int64_t serverTime; |
|
1787 PRStatus result = PR_ParseTimeString(aServerTime.get(), true, |
|
1788 &tempServerTime); |
|
1789 if (result == PR_SUCCESS) { |
|
1790 serverTime = tempServerTime / int64_t(PR_USEC_PER_SEC); |
|
1791 } else { |
|
1792 serverTime = PR_Now() / PR_USEC_PER_SEC; |
|
1793 } |
|
1794 |
|
1795 // process each cookie in the header |
|
1796 while (SetCookieInternal(aHostURI, key, requireHostMatch, cookieStatus, |
|
1797 aCookieHeader, serverTime, aFromHttp, aChannel)) { |
|
1798 // document.cookie can only set one cookie at a time |
|
1799 if (!aFromHttp) |
|
1800 break; |
|
1801 } |
|
1802 } |
|
1803 |
|
1804 // notify observers that a cookie was rejected due to the users' prefs. |
|
1805 void |
|
1806 nsCookieService::NotifyRejected(nsIURI *aHostURI) |
|
1807 { |
|
1808 if (mObserverService) { |
|
1809 mObserverService->NotifyObservers(aHostURI, "cookie-rejected", nullptr); |
|
1810 } |
|
1811 } |
|
1812 |
|
1813 // notify observers that a third-party cookie was accepted/rejected |
|
1814 // if the cookie issuer is unknown, it defaults to "?" |
|
1815 void |
|
1816 nsCookieService::NotifyThirdParty(nsIURI *aHostURI, bool aIsAccepted, nsIChannel *aChannel) |
|
1817 { |
|
1818 if (!mObserverService) { |
|
1819 return; |
|
1820 } |
|
1821 |
|
1822 const char* topic; |
|
1823 |
|
1824 if (mDBState != mPrivateDBState) { |
|
1825 // Regular (non-private) browsing |
|
1826 if (aIsAccepted) { |
|
1827 topic = "third-party-cookie-accepted"; |
|
1828 } else { |
|
1829 topic = "third-party-cookie-rejected"; |
|
1830 } |
|
1831 } else { |
|
1832 // Private browsing |
|
1833 if (aIsAccepted) { |
|
1834 topic = "private-third-party-cookie-accepted"; |
|
1835 } else { |
|
1836 topic = "private-third-party-cookie-rejected"; |
|
1837 } |
|
1838 } |
|
1839 |
|
1840 do { |
|
1841 // Attempt to find the host of aChannel. |
|
1842 if (!aChannel) { |
|
1843 break; |
|
1844 } |
|
1845 nsCOMPtr<nsIURI> channelURI; |
|
1846 nsresult rv = aChannel->GetURI(getter_AddRefs(channelURI)); |
|
1847 if (NS_FAILED(rv)) { |
|
1848 break; |
|
1849 } |
|
1850 |
|
1851 nsAutoCString referringHost; |
|
1852 rv = channelURI->GetHost(referringHost); |
|
1853 if (NS_FAILED(rv)) { |
|
1854 break; |
|
1855 } |
|
1856 |
|
1857 nsAutoString referringHostUTF16 = NS_ConvertUTF8toUTF16(referringHost); |
|
1858 mObserverService->NotifyObservers(aHostURI, |
|
1859 topic, |
|
1860 referringHostUTF16.get()); |
|
1861 return; |
|
1862 } while (0); |
|
1863 |
|
1864 // This can fail for a number of reasons, in which kind we fallback to "?" |
|
1865 mObserverService->NotifyObservers(aHostURI, |
|
1866 topic, |
|
1867 MOZ_UTF16("?")); |
|
1868 } |
|
1869 |
|
1870 // notify observers that the cookie list changed. there are five possible |
|
1871 // values for aData: |
|
1872 // "deleted" means a cookie was deleted. aSubject is the deleted cookie. |
|
1873 // "added" means a cookie was added. aSubject is the added cookie. |
|
1874 // "changed" means a cookie was altered. aSubject is the new cookie. |
|
1875 // "cleared" means the entire cookie list was cleared. aSubject is null. |
|
1876 // "batch-deleted" means a set of cookies was purged. aSubject is the list of |
|
1877 // cookies. |
|
1878 void |
|
1879 nsCookieService::NotifyChanged(nsISupports *aSubject, |
|
1880 const char16_t *aData) |
|
1881 { |
|
1882 const char* topic = mDBState == mPrivateDBState ? |
|
1883 "private-cookie-changed" : "cookie-changed"; |
|
1884 if (mObserverService) |
|
1885 mObserverService->NotifyObservers(aSubject, topic, aData); |
|
1886 } |
|
1887 |
|
1888 already_AddRefed<nsIArray> |
|
1889 nsCookieService::CreatePurgeList(nsICookie2* aCookie) |
|
1890 { |
|
1891 nsCOMPtr<nsIMutableArray> removedList = |
|
1892 do_CreateInstance(NS_ARRAY_CONTRACTID); |
|
1893 removedList->AppendElement(aCookie, false); |
|
1894 return removedList.forget(); |
|
1895 } |
|
1896 |
|
1897 /****************************************************************************** |
|
1898 * nsCookieService: |
|
1899 * pref observer impl |
|
1900 ******************************************************************************/ |
|
1901 |
|
1902 void |
|
1903 nsCookieService::PrefChanged(nsIPrefBranch *aPrefBranch) |
|
1904 { |
|
1905 int32_t val; |
|
1906 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookieBehavior, &val))) |
|
1907 mCookieBehavior = (uint8_t) LIMIT(val, 0, 3, 0); |
|
1908 |
|
1909 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxNumberOfCookies, &val))) |
|
1910 mMaxNumberOfCookies = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxNumberOfCookies); |
|
1911 |
|
1912 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefMaxCookiesPerHost, &val))) |
|
1913 mMaxCookiesPerHost = (uint16_t) LIMIT(val, 1, 0xFFFF, kMaxCookiesPerHost); |
|
1914 |
|
1915 if (NS_SUCCEEDED(aPrefBranch->GetIntPref(kPrefCookiePurgeAge, &val))) { |
|
1916 mCookiePurgeAge = |
|
1917 int64_t(LIMIT(val, 0, INT32_MAX, INT32_MAX)) * PR_USEC_PER_SEC; |
|
1918 } |
|
1919 |
|
1920 bool boolval; |
|
1921 if (NS_SUCCEEDED(aPrefBranch->GetBoolPref(kPrefThirdPartySession, &boolval))) |
|
1922 mThirdPartySession = boolval; |
|
1923 } |
|
1924 |
|
1925 /****************************************************************************** |
|
1926 * nsICookieManager impl: |
|
1927 * nsICookieManager |
|
1928 ******************************************************************************/ |
|
1929 |
|
1930 NS_IMETHODIMP |
|
1931 nsCookieService::RemoveAll() |
|
1932 { |
|
1933 if (!mDBState) { |
|
1934 NS_WARNING("No DBState! Profile already closed?"); |
|
1935 return NS_ERROR_NOT_AVAILABLE; |
|
1936 } |
|
1937 |
|
1938 RemoveAllFromMemory(); |
|
1939 |
|
1940 // clear the cookie file |
|
1941 if (mDBState->dbConn) { |
|
1942 NS_ASSERTION(mDBState == mDefaultDBState, "not in default DB state"); |
|
1943 |
|
1944 // Cancel any pending read. No further results will be received by our |
|
1945 // read listener. |
|
1946 if (mDefaultDBState->pendingRead) { |
|
1947 CancelAsyncRead(true); |
|
1948 } |
|
1949 |
|
1950 nsCOMPtr<mozIStorageAsyncStatement> stmt; |
|
1951 nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
|
1952 "DELETE FROM moz_cookies"), getter_AddRefs(stmt)); |
|
1953 if (NS_SUCCEEDED(rv)) { |
|
1954 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
1955 rv = stmt->ExecuteAsync(mDefaultDBState->removeListener, |
|
1956 getter_AddRefs(handle)); |
|
1957 NS_ASSERT_SUCCESS(rv); |
|
1958 } else { |
|
1959 // Recreate the database. |
|
1960 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
1961 ("RemoveAll(): corruption detected with rv 0x%x", rv)); |
|
1962 HandleCorruptDB(mDefaultDBState); |
|
1963 } |
|
1964 } |
|
1965 |
|
1966 NotifyChanged(nullptr, MOZ_UTF16("cleared")); |
|
1967 return NS_OK; |
|
1968 } |
|
1969 |
|
1970 static PLDHashOperator |
|
1971 COMArrayCallback(nsCookieEntry *aEntry, |
|
1972 void *aArg) |
|
1973 { |
|
1974 nsCOMArray<nsICookie> *data = static_cast<nsCOMArray<nsICookie> *>(aArg); |
|
1975 |
|
1976 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies(); |
|
1977 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
1978 data->AppendObject(cookies[i]); |
|
1979 } |
|
1980 |
|
1981 return PL_DHASH_NEXT; |
|
1982 } |
|
1983 |
|
1984 NS_IMETHODIMP |
|
1985 nsCookieService::GetEnumerator(nsISimpleEnumerator **aEnumerator) |
|
1986 { |
|
1987 if (!mDBState) { |
|
1988 NS_WARNING("No DBState! Profile already closed?"); |
|
1989 return NS_ERROR_NOT_AVAILABLE; |
|
1990 } |
|
1991 |
|
1992 EnsureReadComplete(); |
|
1993 |
|
1994 nsCOMArray<nsICookie> cookieList(mDBState->cookieCount); |
|
1995 mDBState->hostTable.EnumerateEntries(COMArrayCallback, &cookieList); |
|
1996 |
|
1997 return NS_NewArrayEnumerator(aEnumerator, cookieList); |
|
1998 } |
|
1999 |
|
2000 NS_IMETHODIMP |
|
2001 nsCookieService::Add(const nsACString &aHost, |
|
2002 const nsACString &aPath, |
|
2003 const nsACString &aName, |
|
2004 const nsACString &aValue, |
|
2005 bool aIsSecure, |
|
2006 bool aIsHttpOnly, |
|
2007 bool aIsSession, |
|
2008 int64_t aExpiry) |
|
2009 { |
|
2010 if (!mDBState) { |
|
2011 NS_WARNING("No DBState! Profile already closed?"); |
|
2012 return NS_ERROR_NOT_AVAILABLE; |
|
2013 } |
|
2014 |
|
2015 // first, normalize the hostname, and fail if it contains illegal characters. |
|
2016 nsAutoCString host(aHost); |
|
2017 nsresult rv = NormalizeHost(host); |
|
2018 NS_ENSURE_SUCCESS(rv, rv); |
|
2019 |
|
2020 // get the base domain for the host URI. |
|
2021 // e.g. for "www.bbc.co.uk", this would be "bbc.co.uk". |
|
2022 nsAutoCString baseDomain; |
|
2023 rv = GetBaseDomainFromHost(host, baseDomain); |
|
2024 NS_ENSURE_SUCCESS(rv, rv); |
|
2025 |
|
2026 int64_t currentTimeInUsec = PR_Now(); |
|
2027 |
|
2028 nsRefPtr<nsCookie> cookie = |
|
2029 nsCookie::Create(aName, aValue, host, aPath, |
|
2030 aExpiry, |
|
2031 currentTimeInUsec, |
|
2032 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), |
|
2033 aIsSession, |
|
2034 aIsSecure, |
|
2035 aIsHttpOnly); |
|
2036 if (!cookie) { |
|
2037 return NS_ERROR_OUT_OF_MEMORY; |
|
2038 } |
|
2039 |
|
2040 AddInternal(DEFAULT_APP_KEY(baseDomain), cookie, currentTimeInUsec, nullptr, nullptr, true); |
|
2041 return NS_OK; |
|
2042 } |
|
2043 |
|
2044 |
|
2045 nsresult |
|
2046 nsCookieService::Remove(const nsACString& aHost, uint32_t aAppId, |
|
2047 bool aInBrowserElement, const nsACString& aName, |
|
2048 const nsACString& aPath, bool aBlocked) |
|
2049 { |
|
2050 if (!mDBState) { |
|
2051 NS_WARNING("No DBState! Profile already closed?"); |
|
2052 return NS_ERROR_NOT_AVAILABLE; |
|
2053 } |
|
2054 |
|
2055 // first, normalize the hostname, and fail if it contains illegal characters. |
|
2056 nsAutoCString host(aHost); |
|
2057 nsresult rv = NormalizeHost(host); |
|
2058 NS_ENSURE_SUCCESS(rv, rv); |
|
2059 |
|
2060 nsAutoCString baseDomain; |
|
2061 rv = GetBaseDomainFromHost(host, baseDomain); |
|
2062 NS_ENSURE_SUCCESS(rv, rv); |
|
2063 |
|
2064 nsListIter matchIter; |
|
2065 nsRefPtr<nsCookie> cookie; |
|
2066 if (FindCookie(nsCookieKey(baseDomain, aAppId, aInBrowserElement), |
|
2067 host, |
|
2068 PromiseFlatCString(aName), |
|
2069 PromiseFlatCString(aPath), |
|
2070 matchIter)) { |
|
2071 cookie = matchIter.Cookie(); |
|
2072 RemoveCookieFromList(matchIter); |
|
2073 } |
|
2074 |
|
2075 // check if we need to add the host to the permissions blacklist. |
|
2076 if (aBlocked && mPermissionService) { |
|
2077 // strip off the domain dot, if necessary |
|
2078 if (!host.IsEmpty() && host.First() == '.') |
|
2079 host.Cut(0, 1); |
|
2080 |
|
2081 host.Insert(NS_LITERAL_CSTRING("http://"), 0); |
|
2082 |
|
2083 nsCOMPtr<nsIURI> uri; |
|
2084 NS_NewURI(getter_AddRefs(uri), host); |
|
2085 |
|
2086 if (uri) |
|
2087 mPermissionService->SetAccess(uri, nsICookiePermission::ACCESS_DENY); |
|
2088 } |
|
2089 |
|
2090 if (cookie) { |
|
2091 // Everything's done. Notify observers. |
|
2092 NotifyChanged(cookie, MOZ_UTF16("deleted")); |
|
2093 } |
|
2094 |
|
2095 return NS_OK; |
|
2096 } |
|
2097 |
|
2098 NS_IMETHODIMP |
|
2099 nsCookieService::Remove(const nsACString &aHost, |
|
2100 const nsACString &aName, |
|
2101 const nsACString &aPath, |
|
2102 bool aBlocked) |
|
2103 { |
|
2104 return Remove(aHost, NECKO_NO_APP_ID, false, aName, aPath, aBlocked); |
|
2105 } |
|
2106 |
|
2107 /****************************************************************************** |
|
2108 * nsCookieService impl: |
|
2109 * private file I/O functions |
|
2110 ******************************************************************************/ |
|
2111 |
|
2112 // Begin an asynchronous read from the database. |
|
2113 OpenDBResult |
|
2114 nsCookieService::Read() |
|
2115 { |
|
2116 // Set up a statement for the read. Note that our query specifies that |
|
2117 // 'baseDomain' not be nullptr -- see below for why. |
|
2118 nsCOMPtr<mozIStorageAsyncStatement> stmtRead; |
|
2119 nsresult rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
|
2120 "SELECT " |
|
2121 "name, " |
|
2122 "value, " |
|
2123 "host, " |
|
2124 "path, " |
|
2125 "expiry, " |
|
2126 "lastAccessed, " |
|
2127 "creationTime, " |
|
2128 "isSecure, " |
|
2129 "isHttpOnly, " |
|
2130 "baseDomain, " |
|
2131 "appId, " |
|
2132 "inBrowserElement " |
|
2133 "FROM moz_cookies " |
|
2134 "WHERE baseDomain NOTNULL"), getter_AddRefs(stmtRead)); |
|
2135 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
2136 |
|
2137 // Set up a statement to delete any rows with a nullptr 'baseDomain' |
|
2138 // column. This takes care of any cookies set by browsers that don't |
|
2139 // understand the 'baseDomain' column, where the database schema version |
|
2140 // is from one that does. (This would occur when downgrading.) |
|
2141 nsCOMPtr<mozIStorageAsyncStatement> stmtDeleteNull; |
|
2142 rv = mDefaultDBState->dbConn->CreateAsyncStatement(NS_LITERAL_CSTRING( |
|
2143 "DELETE FROM moz_cookies WHERE baseDomain ISNULL"), |
|
2144 getter_AddRefs(stmtDeleteNull)); |
|
2145 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
2146 |
|
2147 // Start a new connection for sync reads, to reduce contention with the |
|
2148 // background thread. We need to do this before we kick off write statements, |
|
2149 // since they can lock the database and prevent connections from being opened. |
|
2150 rv = mStorageService->OpenUnsharedDatabase(mDefaultDBState->cookieFile, |
|
2151 getter_AddRefs(mDefaultDBState->syncConn)); |
|
2152 NS_ENSURE_SUCCESS(rv, RESULT_RETRY); |
|
2153 |
|
2154 // Init our readSet hash and execute the statements. Note that, after this |
|
2155 // point, we cannot fail without altering the cleanup code in InitDBStates() |
|
2156 // to handle closing of the now-asynchronous connection. |
|
2157 mDefaultDBState->hostArray.SetCapacity(kMaxNumberOfCookies); |
|
2158 |
|
2159 mDefaultDBState->readListener = new ReadCookieDBListener(mDefaultDBState); |
|
2160 rv = stmtRead->ExecuteAsync(mDefaultDBState->readListener, |
|
2161 getter_AddRefs(mDefaultDBState->pendingRead)); |
|
2162 NS_ASSERT_SUCCESS(rv); |
|
2163 |
|
2164 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
2165 rv = stmtDeleteNull->ExecuteAsync(mDefaultDBState->removeListener, |
|
2166 getter_AddRefs(handle)); |
|
2167 NS_ASSERT_SUCCESS(rv); |
|
2168 |
|
2169 return RESULT_OK; |
|
2170 } |
|
2171 |
|
2172 // Extract data from a single result row and create an nsCookie. |
|
2173 // This is templated since 'T' is different for sync vs async results. |
|
2174 template<class T> nsCookie* |
|
2175 nsCookieService::GetCookieFromRow(T &aRow) |
|
2176 { |
|
2177 // Skip reading 'baseDomain' -- up to the caller. |
|
2178 nsCString name, value, host, path; |
|
2179 DebugOnly<nsresult> rv = aRow->GetUTF8String(IDX_NAME, name); |
|
2180 NS_ASSERT_SUCCESS(rv); |
|
2181 rv = aRow->GetUTF8String(IDX_VALUE, value); |
|
2182 NS_ASSERT_SUCCESS(rv); |
|
2183 rv = aRow->GetUTF8String(IDX_HOST, host); |
|
2184 NS_ASSERT_SUCCESS(rv); |
|
2185 rv = aRow->GetUTF8String(IDX_PATH, path); |
|
2186 NS_ASSERT_SUCCESS(rv); |
|
2187 |
|
2188 int64_t expiry = aRow->AsInt64(IDX_EXPIRY); |
|
2189 int64_t lastAccessed = aRow->AsInt64(IDX_LAST_ACCESSED); |
|
2190 int64_t creationTime = aRow->AsInt64(IDX_CREATION_TIME); |
|
2191 bool isSecure = 0 != aRow->AsInt32(IDX_SECURE); |
|
2192 bool isHttpOnly = 0 != aRow->AsInt32(IDX_HTTPONLY); |
|
2193 |
|
2194 // Create a new nsCookie and assign the data. |
|
2195 return nsCookie::Create(name, value, host, path, |
|
2196 expiry, |
|
2197 lastAccessed, |
|
2198 creationTime, |
|
2199 false, |
|
2200 isSecure, |
|
2201 isHttpOnly); |
|
2202 } |
|
2203 |
|
2204 void |
|
2205 nsCookieService::AsyncReadComplete() |
|
2206 { |
|
2207 // We may be in the private browsing DB state, with a pending read on the |
|
2208 // default DB state. (This would occur if we started up in private browsing |
|
2209 // mode.) As long as we do all our operations on the default state, we're OK. |
|
2210 NS_ASSERTION(mDefaultDBState, "no default DBState"); |
|
2211 NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read"); |
|
2212 NS_ASSERTION(mDefaultDBState->readListener, "no read listener"); |
|
2213 |
|
2214 // Merge the data read on the background thread with the data synchronously |
|
2215 // read on the main thread. Note that transactions on the cookie table may |
|
2216 // have occurred on the main thread since, making the background data stale. |
|
2217 for (uint32_t i = 0; i < mDefaultDBState->hostArray.Length(); ++i) { |
|
2218 const CookieDomainTuple &tuple = mDefaultDBState->hostArray[i]; |
|
2219 |
|
2220 // Tiebreak: if the given base domain has already been read in, ignore |
|
2221 // the background data. Note that readSet may contain domains that were |
|
2222 // queried but found not to be in the db -- that's harmless. |
|
2223 if (mDefaultDBState->readSet.GetEntry(tuple.key)) |
|
2224 continue; |
|
2225 |
|
2226 AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, false); |
|
2227 } |
|
2228 |
|
2229 mDefaultDBState->stmtReadDomain = nullptr; |
|
2230 mDefaultDBState->pendingRead = nullptr; |
|
2231 mDefaultDBState->readListener = nullptr; |
|
2232 mDefaultDBState->syncConn = nullptr; |
|
2233 mDefaultDBState->hostArray.Clear(); |
|
2234 mDefaultDBState->readSet.Clear(); |
|
2235 |
|
2236 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("Read(): %ld cookies read", |
|
2237 mDefaultDBState->cookieCount)); |
|
2238 |
|
2239 mObserverService->NotifyObservers(nullptr, "cookie-db-read", nullptr); |
|
2240 } |
|
2241 |
|
2242 void |
|
2243 nsCookieService::CancelAsyncRead(bool aPurgeReadSet) |
|
2244 { |
|
2245 // We may be in the private browsing DB state, with a pending read on the |
|
2246 // default DB state. (This would occur if we started up in private browsing |
|
2247 // mode.) As long as we do all our operations on the default state, we're OK. |
|
2248 NS_ASSERTION(mDefaultDBState, "no default DBState"); |
|
2249 NS_ASSERTION(mDefaultDBState->pendingRead, "no pending read"); |
|
2250 NS_ASSERTION(mDefaultDBState->readListener, "no read listener"); |
|
2251 |
|
2252 // Cancel the pending read, kill the read listener, and empty the array |
|
2253 // of data already read in on the background thread. |
|
2254 mDefaultDBState->readListener->Cancel(); |
|
2255 DebugOnly<nsresult> rv = mDefaultDBState->pendingRead->Cancel(); |
|
2256 NS_ASSERT_SUCCESS(rv); |
|
2257 |
|
2258 mDefaultDBState->stmtReadDomain = nullptr; |
|
2259 mDefaultDBState->pendingRead = nullptr; |
|
2260 mDefaultDBState->readListener = nullptr; |
|
2261 mDefaultDBState->hostArray.Clear(); |
|
2262 |
|
2263 // Only clear the 'readSet' table if we no longer need to know what set of |
|
2264 // data is already accounted for. |
|
2265 if (aPurgeReadSet) |
|
2266 mDefaultDBState->readSet.Clear(); |
|
2267 } |
|
2268 |
|
2269 void |
|
2270 nsCookieService::EnsureReadDomain(const nsCookieKey &aKey) |
|
2271 { |
|
2272 NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState, |
|
2273 "not in default db state"); |
|
2274 |
|
2275 // Fast path 1: nothing to read, or we've already finished reading. |
|
2276 if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead)) |
|
2277 return; |
|
2278 |
|
2279 // Fast path 2: already read in this particular domain. |
|
2280 if (MOZ_LIKELY(mDefaultDBState->readSet.GetEntry(aKey))) |
|
2281 return; |
|
2282 |
|
2283 // Read in the data synchronously. |
|
2284 // see IDX_NAME, etc. for parameter indexes |
|
2285 nsresult rv; |
|
2286 if (!mDefaultDBState->stmtReadDomain) { |
|
2287 // Cache the statement, since it's likely to be used again. |
|
2288 rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
|
2289 "SELECT " |
|
2290 "name, " |
|
2291 "value, " |
|
2292 "host, " |
|
2293 "path, " |
|
2294 "expiry, " |
|
2295 "lastAccessed, " |
|
2296 "creationTime, " |
|
2297 "isSecure, " |
|
2298 "isHttpOnly " |
|
2299 "FROM moz_cookies " |
|
2300 "WHERE baseDomain = :baseDomain " |
|
2301 " AND appId = :appId " |
|
2302 " AND inBrowserElement = :inBrowserElement"), |
|
2303 getter_AddRefs(mDefaultDBState->stmtReadDomain)); |
|
2304 |
|
2305 if (NS_FAILED(rv)) { |
|
2306 // Recreate the database. |
|
2307 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
2308 ("EnsureReadDomain(): corruption detected when creating statement " |
|
2309 "with rv 0x%x", rv)); |
|
2310 HandleCorruptDB(mDefaultDBState); |
|
2311 return; |
|
2312 } |
|
2313 } |
|
2314 |
|
2315 NS_ASSERTION(mDefaultDBState->syncConn, "should have a sync db connection"); |
|
2316 |
|
2317 mozStorageStatementScoper scoper(mDefaultDBState->stmtReadDomain); |
|
2318 |
|
2319 rv = mDefaultDBState->stmtReadDomain->BindUTF8StringByName( |
|
2320 NS_LITERAL_CSTRING("baseDomain"), aKey.mBaseDomain); |
|
2321 NS_ASSERT_SUCCESS(rv); |
|
2322 rv = mDefaultDBState->stmtReadDomain->BindInt32ByName( |
|
2323 NS_LITERAL_CSTRING("appId"), aKey.mAppId); |
|
2324 NS_ASSERT_SUCCESS(rv); |
|
2325 rv = mDefaultDBState->stmtReadDomain->BindInt32ByName( |
|
2326 NS_LITERAL_CSTRING("inBrowserElement"), aKey.mInBrowserElement ? 1 : 0); |
|
2327 NS_ASSERT_SUCCESS(rv); |
|
2328 |
|
2329 |
|
2330 bool hasResult; |
|
2331 nsCString name, value, host, path; |
|
2332 nsAutoTArray<nsRefPtr<nsCookie>, kMaxCookiesPerHost> array; |
|
2333 while (1) { |
|
2334 rv = mDefaultDBState->stmtReadDomain->ExecuteStep(&hasResult); |
|
2335 if (NS_FAILED(rv)) { |
|
2336 // Recreate the database. |
|
2337 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
2338 ("EnsureReadDomain(): corruption detected when reading result " |
|
2339 "with rv 0x%x", rv)); |
|
2340 HandleCorruptDB(mDefaultDBState); |
|
2341 return; |
|
2342 } |
|
2343 |
|
2344 if (!hasResult) |
|
2345 break; |
|
2346 |
|
2347 array.AppendElement(GetCookieFromRow(mDefaultDBState->stmtReadDomain)); |
|
2348 } |
|
2349 |
|
2350 // Add the cookies to the table in a single operation. This makes sure that |
|
2351 // either all the cookies get added, or in the case of corruption, none. |
|
2352 for (uint32_t i = 0; i < array.Length(); ++i) { |
|
2353 AddCookieToList(aKey, array[i], mDefaultDBState, nullptr, false); |
|
2354 } |
|
2355 |
|
2356 // Add it to the hashset of read entries, so we don't read it again. |
|
2357 mDefaultDBState->readSet.PutEntry(aKey); |
|
2358 |
|
2359 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
2360 ("EnsureReadDomain(): %ld cookies read for base domain %s, " |
|
2361 " appId=%u, inBrowser=%d", array.Length(), aKey.mBaseDomain.get(), |
|
2362 (unsigned)aKey.mAppId, (int)aKey.mInBrowserElement)); |
|
2363 } |
|
2364 |
|
2365 void |
|
2366 nsCookieService::EnsureReadComplete() |
|
2367 { |
|
2368 NS_ASSERTION(!mDBState->dbConn || mDBState == mDefaultDBState, |
|
2369 "not in default db state"); |
|
2370 |
|
2371 // Fast path 1: nothing to read, or we've already finished reading. |
|
2372 if (MOZ_LIKELY(!mDBState->dbConn || !mDefaultDBState->pendingRead)) |
|
2373 return; |
|
2374 |
|
2375 // Cancel the pending read, so we don't get any more results. |
|
2376 CancelAsyncRead(false); |
|
2377 |
|
2378 // Read in the data synchronously. |
|
2379 // see IDX_NAME, etc. for parameter indexes |
|
2380 nsCOMPtr<mozIStorageStatement> stmt; |
|
2381 nsresult rv = mDefaultDBState->syncConn->CreateStatement(NS_LITERAL_CSTRING( |
|
2382 "SELECT " |
|
2383 "name, " |
|
2384 "value, " |
|
2385 "host, " |
|
2386 "path, " |
|
2387 "expiry, " |
|
2388 "lastAccessed, " |
|
2389 "creationTime, " |
|
2390 "isSecure, " |
|
2391 "isHttpOnly, " |
|
2392 "baseDomain, " |
|
2393 "appId, " |
|
2394 "inBrowserElement " |
|
2395 "FROM moz_cookies " |
|
2396 "WHERE baseDomain NOTNULL"), getter_AddRefs(stmt)); |
|
2397 |
|
2398 if (NS_FAILED(rv)) { |
|
2399 // Recreate the database. |
|
2400 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
2401 ("EnsureReadComplete(): corruption detected when creating statement " |
|
2402 "with rv 0x%x", rv)); |
|
2403 HandleCorruptDB(mDefaultDBState); |
|
2404 return; |
|
2405 } |
|
2406 |
|
2407 nsCString baseDomain, name, value, host, path; |
|
2408 uint32_t appId; |
|
2409 bool inBrowserElement, hasResult; |
|
2410 nsAutoTArray<CookieDomainTuple, kMaxNumberOfCookies> array; |
|
2411 while (1) { |
|
2412 rv = stmt->ExecuteStep(&hasResult); |
|
2413 if (NS_FAILED(rv)) { |
|
2414 // Recreate the database. |
|
2415 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
2416 ("EnsureReadComplete(): corruption detected when reading result " |
|
2417 "with rv 0x%x", rv)); |
|
2418 HandleCorruptDB(mDefaultDBState); |
|
2419 return; |
|
2420 } |
|
2421 |
|
2422 if (!hasResult) |
|
2423 break; |
|
2424 |
|
2425 // Make sure we haven't already read the data. |
|
2426 stmt->GetUTF8String(IDX_BASE_DOMAIN, baseDomain); |
|
2427 appId = static_cast<uint32_t>(stmt->AsInt32(IDX_APP_ID)); |
|
2428 inBrowserElement = static_cast<bool>(stmt->AsInt32(IDX_BROWSER_ELEM)); |
|
2429 nsCookieKey key(baseDomain, appId, inBrowserElement); |
|
2430 if (mDefaultDBState->readSet.GetEntry(key)) |
|
2431 continue; |
|
2432 |
|
2433 CookieDomainTuple* tuple = array.AppendElement(); |
|
2434 tuple->key = key; |
|
2435 tuple->cookie = GetCookieFromRow(stmt); |
|
2436 } |
|
2437 |
|
2438 // Add the cookies to the table in a single operation. This makes sure that |
|
2439 // either all the cookies get added, or in the case of corruption, none. |
|
2440 for (uint32_t i = 0; i < array.Length(); ++i) { |
|
2441 CookieDomainTuple& tuple = array[i]; |
|
2442 AddCookieToList(tuple.key, tuple.cookie, mDefaultDBState, nullptr, |
|
2443 false); |
|
2444 } |
|
2445 |
|
2446 mDefaultDBState->syncConn = nullptr; |
|
2447 mDefaultDBState->readSet.Clear(); |
|
2448 |
|
2449 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
2450 ("EnsureReadComplete(): %ld cookies read", array.Length())); |
|
2451 } |
|
2452 |
|
2453 NS_IMETHODIMP |
|
2454 nsCookieService::ImportCookies(nsIFile *aCookieFile) |
|
2455 { |
|
2456 if (!mDBState) { |
|
2457 NS_WARNING("No DBState! Profile already closed?"); |
|
2458 return NS_ERROR_NOT_AVAILABLE; |
|
2459 } |
|
2460 |
|
2461 // Make sure we're in the default DB state. We don't want people importing |
|
2462 // cookies into a private browsing session! |
|
2463 if (mDBState != mDefaultDBState) { |
|
2464 NS_WARNING("Trying to import cookies in a private browsing session!"); |
|
2465 return NS_ERROR_NOT_AVAILABLE; |
|
2466 } |
|
2467 |
|
2468 nsresult rv; |
|
2469 nsCOMPtr<nsIInputStream> fileInputStream; |
|
2470 rv = NS_NewLocalFileInputStream(getter_AddRefs(fileInputStream), aCookieFile); |
|
2471 if (NS_FAILED(rv)) return rv; |
|
2472 |
|
2473 nsCOMPtr<nsILineInputStream> lineInputStream = do_QueryInterface(fileInputStream, &rv); |
|
2474 if (NS_FAILED(rv)) return rv; |
|
2475 |
|
2476 // First, ensure we've read in everything from the database, if we have one. |
|
2477 EnsureReadComplete(); |
|
2478 |
|
2479 static const char kTrue[] = "TRUE"; |
|
2480 |
|
2481 nsAutoCString buffer, baseDomain; |
|
2482 bool isMore = true; |
|
2483 int32_t hostIndex, isDomainIndex, pathIndex, secureIndex, expiresIndex, nameIndex, cookieIndex; |
|
2484 nsASingleFragmentCString::char_iterator iter; |
|
2485 int32_t numInts; |
|
2486 int64_t expires; |
|
2487 bool isDomain, isHttpOnly = false; |
|
2488 uint32_t originalCookieCount = mDefaultDBState->cookieCount; |
|
2489 |
|
2490 int64_t currentTimeInUsec = PR_Now(); |
|
2491 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; |
|
2492 // we use lastAccessedCounter to keep cookies in recently-used order, |
|
2493 // so we start by initializing to currentTime (somewhat arbitrary) |
|
2494 int64_t lastAccessedCounter = currentTimeInUsec; |
|
2495 |
|
2496 /* file format is: |
|
2497 * |
|
2498 * host \t isDomain \t path \t secure \t expires \t name \t cookie |
|
2499 * |
|
2500 * if this format isn't respected we move onto the next line in the file. |
|
2501 * isDomain is "TRUE" or "FALSE" (default to "FALSE") |
|
2502 * isSecure is "TRUE" or "FALSE" (default to "TRUE") |
|
2503 * expires is a int64_t integer |
|
2504 * note 1: cookie can contain tabs. |
|
2505 * note 2: cookies will be stored in order of lastAccessed time: |
|
2506 * most-recently used come first; least-recently-used come last. |
|
2507 */ |
|
2508 |
|
2509 /* |
|
2510 * ...but due to bug 178933, we hide HttpOnly cookies from older code |
|
2511 * in a comment, so they don't expose HttpOnly cookies to JS. |
|
2512 * |
|
2513 * The format for HttpOnly cookies is |
|
2514 * |
|
2515 * #HttpOnly_host \t isDomain \t path \t secure \t expires \t name \t cookie |
|
2516 * |
|
2517 */ |
|
2518 |
|
2519 // We will likely be adding a bunch of cookies to the DB, so we use async |
|
2520 // batching with storage to make this super fast. |
|
2521 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
|
2522 if (originalCookieCount == 0 && mDefaultDBState->dbConn) { |
|
2523 mDefaultDBState->stmtInsert->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
|
2524 } |
|
2525 |
|
2526 while (isMore && NS_SUCCEEDED(lineInputStream->ReadLine(buffer, &isMore))) { |
|
2527 if (StringBeginsWith(buffer, NS_LITERAL_CSTRING(kHttpOnlyPrefix))) { |
|
2528 isHttpOnly = true; |
|
2529 hostIndex = sizeof(kHttpOnlyPrefix) - 1; |
|
2530 } else if (buffer.IsEmpty() || buffer.First() == '#') { |
|
2531 continue; |
|
2532 } else { |
|
2533 isHttpOnly = false; |
|
2534 hostIndex = 0; |
|
2535 } |
|
2536 |
|
2537 // this is a cheap, cheesy way of parsing a tab-delimited line into |
|
2538 // string indexes, which can be lopped off into substrings. just for |
|
2539 // purposes of obfuscation, it also checks that each token was found. |
|
2540 // todo: use iterators? |
|
2541 if ((isDomainIndex = buffer.FindChar('\t', hostIndex) + 1) == 0 || |
|
2542 (pathIndex = buffer.FindChar('\t', isDomainIndex) + 1) == 0 || |
|
2543 (secureIndex = buffer.FindChar('\t', pathIndex) + 1) == 0 || |
|
2544 (expiresIndex = buffer.FindChar('\t', secureIndex) + 1) == 0 || |
|
2545 (nameIndex = buffer.FindChar('\t', expiresIndex) + 1) == 0 || |
|
2546 (cookieIndex = buffer.FindChar('\t', nameIndex) + 1) == 0) { |
|
2547 continue; |
|
2548 } |
|
2549 |
|
2550 // check the expirytime first - if it's expired, ignore |
|
2551 // nullstomp the trailing tab, to avoid copying the string |
|
2552 buffer.BeginWriting(iter); |
|
2553 *(iter += nameIndex - 1) = char(0); |
|
2554 numInts = PR_sscanf(buffer.get() + expiresIndex, "%lld", &expires); |
|
2555 if (numInts != 1 || expires < currentTime) { |
|
2556 continue; |
|
2557 } |
|
2558 |
|
2559 isDomain = Substring(buffer, isDomainIndex, pathIndex - isDomainIndex - 1).EqualsLiteral(kTrue); |
|
2560 const nsASingleFragmentCString &host = Substring(buffer, hostIndex, isDomainIndex - hostIndex - 1); |
|
2561 // check for bad legacy cookies (domain not starting with a dot, or containing a port), |
|
2562 // and discard |
|
2563 if ((isDomain && !host.IsEmpty() && host.First() != '.') || |
|
2564 host.FindChar(':') != kNotFound) { |
|
2565 continue; |
|
2566 } |
|
2567 |
|
2568 // compute the baseDomain from the host |
|
2569 rv = GetBaseDomainFromHost(host, baseDomain); |
|
2570 if (NS_FAILED(rv)) |
|
2571 continue; |
|
2572 |
|
2573 // pre-existing cookies have appId=0, inBrowser=false |
|
2574 nsCookieKey key = DEFAULT_APP_KEY(baseDomain); |
|
2575 |
|
2576 // Create a new nsCookie and assign the data. We don't know the cookie |
|
2577 // creation time, so just use the current time to generate a unique one. |
|
2578 nsRefPtr<nsCookie> newCookie = |
|
2579 nsCookie::Create(Substring(buffer, nameIndex, cookieIndex - nameIndex - 1), |
|
2580 Substring(buffer, cookieIndex, buffer.Length() - cookieIndex), |
|
2581 host, |
|
2582 Substring(buffer, pathIndex, secureIndex - pathIndex - 1), |
|
2583 expires, |
|
2584 lastAccessedCounter, |
|
2585 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), |
|
2586 false, |
|
2587 Substring(buffer, secureIndex, expiresIndex - secureIndex - 1).EqualsLiteral(kTrue), |
|
2588 isHttpOnly); |
|
2589 if (!newCookie) { |
|
2590 return NS_ERROR_OUT_OF_MEMORY; |
|
2591 } |
|
2592 |
|
2593 // trick: preserve the most-recently-used cookie ordering, |
|
2594 // by successively decrementing the lastAccessed time |
|
2595 lastAccessedCounter--; |
|
2596 |
|
2597 if (originalCookieCount == 0) { |
|
2598 AddCookieToList(key, newCookie, mDefaultDBState, paramsArray); |
|
2599 } |
|
2600 else { |
|
2601 AddInternal(key, newCookie, currentTimeInUsec, |
|
2602 nullptr, nullptr, true); |
|
2603 } |
|
2604 } |
|
2605 |
|
2606 // If we need to write to disk, do so now. |
|
2607 if (paramsArray) { |
|
2608 uint32_t length; |
|
2609 paramsArray->GetLength(&length); |
|
2610 if (length) { |
|
2611 rv = mDefaultDBState->stmtInsert->BindParameters(paramsArray); |
|
2612 NS_ASSERT_SUCCESS(rv); |
|
2613 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
2614 rv = mDefaultDBState->stmtInsert->ExecuteAsync( |
|
2615 mDefaultDBState->insertListener, getter_AddRefs(handle)); |
|
2616 NS_ASSERT_SUCCESS(rv); |
|
2617 } |
|
2618 } |
|
2619 |
|
2620 |
|
2621 COOKIE_LOGSTRING(PR_LOG_DEBUG, ("ImportCookies(): %ld cookies imported", |
|
2622 mDefaultDBState->cookieCount)); |
|
2623 |
|
2624 return NS_OK; |
|
2625 } |
|
2626 |
|
2627 /****************************************************************************** |
|
2628 * nsCookieService impl: |
|
2629 * private GetCookie/SetCookie helpers |
|
2630 ******************************************************************************/ |
|
2631 |
|
2632 // helper function for GetCookieList |
|
2633 static inline bool ispathdelimiter(char c) { return c == '/' || c == '?' || c == '#' || c == ';'; } |
|
2634 |
|
2635 // Comparator class for sorting cookies before sending to a server. |
|
2636 class CompareCookiesForSending |
|
2637 { |
|
2638 public: |
|
2639 bool Equals(const nsCookie* aCookie1, const nsCookie* aCookie2) const |
|
2640 { |
|
2641 return aCookie1->CreationTime() == aCookie2->CreationTime() && |
|
2642 aCookie2->Path().Length() == aCookie1->Path().Length(); |
|
2643 } |
|
2644 |
|
2645 bool LessThan(const nsCookie* aCookie1, const nsCookie* aCookie2) const |
|
2646 { |
|
2647 // compare by cookie path length in accordance with RFC2109 |
|
2648 int32_t result = aCookie2->Path().Length() - aCookie1->Path().Length(); |
|
2649 if (result != 0) |
|
2650 return result < 0; |
|
2651 |
|
2652 // when path lengths match, older cookies should be listed first. this is |
|
2653 // required for backwards compatibility since some websites erroneously |
|
2654 // depend on receiving cookies in the order in which they were sent to the |
|
2655 // browser! see bug 236772. |
|
2656 return aCookie1->CreationTime() < aCookie2->CreationTime(); |
|
2657 } |
|
2658 }; |
|
2659 |
|
2660 void |
|
2661 nsCookieService::GetCookieStringInternal(nsIURI *aHostURI, |
|
2662 bool aIsForeign, |
|
2663 bool aHttpBound, |
|
2664 uint32_t aAppId, |
|
2665 bool aInBrowserElement, |
|
2666 bool aIsPrivate, |
|
2667 nsCString &aCookieString) |
|
2668 { |
|
2669 NS_ASSERTION(aHostURI, "null host!"); |
|
2670 |
|
2671 if (!mDBState) { |
|
2672 NS_WARNING("No DBState! Profile already closed?"); |
|
2673 return; |
|
2674 } |
|
2675 |
|
2676 AutoRestore<DBState*> savePrevDBState(mDBState); |
|
2677 mDBState = aIsPrivate ? mPrivateDBState : mDefaultDBState; |
|
2678 |
|
2679 // get the base domain, host, and path from the URI. |
|
2680 // e.g. for "www.bbc.co.uk", the base domain would be "bbc.co.uk". |
|
2681 // file:// URI's (i.e. with an empty host) are allowed, but any other |
|
2682 // scheme must have a non-empty host. A trailing dot in the host |
|
2683 // is acceptable. |
|
2684 bool requireHostMatch; |
|
2685 nsAutoCString baseDomain, hostFromURI, pathFromURI; |
|
2686 nsresult rv = GetBaseDomain(aHostURI, baseDomain, requireHostMatch); |
|
2687 if (NS_SUCCEEDED(rv)) |
|
2688 rv = aHostURI->GetAsciiHost(hostFromURI); |
|
2689 if (NS_SUCCEEDED(rv)) |
|
2690 rv = aHostURI->GetPath(pathFromURI); |
|
2691 if (NS_FAILED(rv)) { |
|
2692 COOKIE_LOGFAILURE(GET_COOKIE, aHostURI, nullptr, "invalid host/path from URI"); |
|
2693 return; |
|
2694 } |
|
2695 |
|
2696 // check default prefs |
|
2697 CookieStatus cookieStatus = CheckPrefs(aHostURI, aIsForeign, requireHostMatch, |
|
2698 nullptr); |
|
2699 // for GetCookie(), we don't fire rejection notifications. |
|
2700 switch (cookieStatus) { |
|
2701 case STATUS_REJECTED: |
|
2702 case STATUS_REJECTED_WITH_ERROR: |
|
2703 return; |
|
2704 default: |
|
2705 break; |
|
2706 } |
|
2707 |
|
2708 // check if aHostURI is using an https secure protocol. |
|
2709 // if it isn't, then we can't send a secure cookie over the connection. |
|
2710 // if SchemeIs fails, assume an insecure connection, to be on the safe side |
|
2711 bool isSecure; |
|
2712 if (NS_FAILED(aHostURI->SchemeIs("https", &isSecure))) { |
|
2713 isSecure = false; |
|
2714 } |
|
2715 |
|
2716 nsCookie *cookie; |
|
2717 nsAutoTArray<nsCookie*, 8> foundCookieList; |
|
2718 int64_t currentTimeInUsec = PR_Now(); |
|
2719 int64_t currentTime = currentTimeInUsec / PR_USEC_PER_SEC; |
|
2720 bool stale = false; |
|
2721 |
|
2722 nsCookieKey key(baseDomain, aAppId, aInBrowserElement); |
|
2723 EnsureReadDomain(key); |
|
2724 |
|
2725 // perform the hash lookup |
|
2726 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key); |
|
2727 if (!entry) |
|
2728 return; |
|
2729 |
|
2730 // iterate the cookies! |
|
2731 const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
|
2732 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
2733 cookie = cookies[i]; |
|
2734 |
|
2735 // check the host, since the base domain lookup is conservative. |
|
2736 // first, check for an exact host or domain cookie match, e.g. "google.com" |
|
2737 // or ".google.com"; second a subdomain match, e.g. |
|
2738 // host = "mail.google.com", cookie domain = ".google.com". |
|
2739 if (cookie->RawHost() != hostFromURI && |
|
2740 !(cookie->IsDomain() && StringEndsWith(hostFromURI, cookie->Host()))) |
|
2741 continue; |
|
2742 |
|
2743 // if the cookie is secure and the host scheme isn't, we can't send it |
|
2744 if (cookie->IsSecure() && !isSecure) |
|
2745 continue; |
|
2746 |
|
2747 // if the cookie is httpOnly and it's not going directly to the HTTP |
|
2748 // connection, don't send it |
|
2749 if (cookie->IsHttpOnly() && !aHttpBound) |
|
2750 continue; |
|
2751 |
|
2752 // calculate cookie path length, excluding trailing '/' |
|
2753 uint32_t cookiePathLen = cookie->Path().Length(); |
|
2754 if (cookiePathLen > 0 && cookie->Path().Last() == '/') |
|
2755 --cookiePathLen; |
|
2756 |
|
2757 // if the nsIURI path is shorter than the cookie path, don't send it back |
|
2758 if (!StringBeginsWith(pathFromURI, Substring(cookie->Path(), 0, cookiePathLen))) |
|
2759 continue; |
|
2760 |
|
2761 if (pathFromURI.Length() > cookiePathLen && |
|
2762 !ispathdelimiter(pathFromURI.CharAt(cookiePathLen))) { |
|
2763 /* |
|
2764 * |ispathdelimiter| tests four cases: '/', '?', '#', and ';'. |
|
2765 * '/' is the "standard" case; the '?' test allows a site at host/abc?def |
|
2766 * to receive a cookie that has a path attribute of abc. this seems |
|
2767 * strange but at least one major site (citibank, bug 156725) depends |
|
2768 * on it. The test for # and ; are put in to proactively avoid problems |
|
2769 * with other sites - these are the only other chars allowed in the path. |
|
2770 */ |
|
2771 continue; |
|
2772 } |
|
2773 |
|
2774 // check if the cookie has expired |
|
2775 if (cookie->Expiry() <= currentTime) { |
|
2776 continue; |
|
2777 } |
|
2778 |
|
2779 // all checks passed - add to list and check if lastAccessed stamp needs updating |
|
2780 foundCookieList.AppendElement(cookie); |
|
2781 if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold) |
|
2782 stale = true; |
|
2783 } |
|
2784 |
|
2785 int32_t count = foundCookieList.Length(); |
|
2786 if (count == 0) |
|
2787 return; |
|
2788 |
|
2789 // update lastAccessed timestamps. we only do this if the timestamp is stale |
|
2790 // by a certain amount, to avoid thrashing the db during pageload. |
|
2791 if (stale) { |
|
2792 // Create an array of parameters to bind to our update statement. Batching |
|
2793 // is OK here since we're updating cookies with no interleaved operations. |
|
2794 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
|
2795 mozIStorageAsyncStatement* stmt = mDBState->stmtUpdate; |
|
2796 if (mDBState->dbConn) { |
|
2797 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
|
2798 } |
|
2799 |
|
2800 for (int32_t i = 0; i < count; ++i) { |
|
2801 cookie = foundCookieList.ElementAt(i); |
|
2802 |
|
2803 if (currentTimeInUsec - cookie->LastAccessed() > kCookieStaleThreshold) |
|
2804 UpdateCookieInList(cookie, currentTimeInUsec, paramsArray); |
|
2805 } |
|
2806 // Update the database now if necessary. |
|
2807 if (paramsArray) { |
|
2808 uint32_t length; |
|
2809 paramsArray->GetLength(&length); |
|
2810 if (length) { |
|
2811 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
|
2812 NS_ASSERT_SUCCESS(rv); |
|
2813 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
2814 rv = stmt->ExecuteAsync(mDBState->updateListener, |
|
2815 getter_AddRefs(handle)); |
|
2816 NS_ASSERT_SUCCESS(rv); |
|
2817 } |
|
2818 } |
|
2819 } |
|
2820 |
|
2821 // return cookies in order of path length; longest to shortest. |
|
2822 // this is required per RFC2109. if cookies match in length, |
|
2823 // then sort by creation time (see bug 236772). |
|
2824 foundCookieList.Sort(CompareCookiesForSending()); |
|
2825 |
|
2826 for (int32_t i = 0; i < count; ++i) { |
|
2827 cookie = foundCookieList.ElementAt(i); |
|
2828 |
|
2829 // check if we have anything to write |
|
2830 if (!cookie->Name().IsEmpty() || !cookie->Value().IsEmpty()) { |
|
2831 // if we've already added a cookie to the return list, append a "; " so |
|
2832 // that subsequent cookies are delimited in the final list. |
|
2833 if (!aCookieString.IsEmpty()) { |
|
2834 aCookieString.AppendLiteral("; "); |
|
2835 } |
|
2836 |
|
2837 if (!cookie->Name().IsEmpty()) { |
|
2838 // we have a name and value - write both |
|
2839 aCookieString += cookie->Name() + NS_LITERAL_CSTRING("=") + cookie->Value(); |
|
2840 } else { |
|
2841 // just write value |
|
2842 aCookieString += cookie->Value(); |
|
2843 } |
|
2844 } |
|
2845 } |
|
2846 |
|
2847 if (!aCookieString.IsEmpty()) |
|
2848 COOKIE_LOGSUCCESS(GET_COOKIE, aHostURI, aCookieString, nullptr, false); |
|
2849 } |
|
2850 |
|
2851 // processes a single cookie, and returns true if there are more cookies |
|
2852 // to be processed |
|
2853 bool |
|
2854 nsCookieService::SetCookieInternal(nsIURI *aHostURI, |
|
2855 const nsCookieKey &aKey, |
|
2856 bool aRequireHostMatch, |
|
2857 CookieStatus aStatus, |
|
2858 nsDependentCString &aCookieHeader, |
|
2859 int64_t aServerTime, |
|
2860 bool aFromHttp, |
|
2861 nsIChannel *aChannel) |
|
2862 { |
|
2863 NS_ASSERTION(aHostURI, "null host!"); |
|
2864 |
|
2865 // create a stack-based nsCookieAttributes, to store all the |
|
2866 // attributes parsed from the cookie |
|
2867 nsCookieAttributes cookieAttributes; |
|
2868 |
|
2869 // init expiryTime such that session cookies won't prematurely expire |
|
2870 cookieAttributes.expiryTime = INT64_MAX; |
|
2871 |
|
2872 // aCookieHeader is an in/out param to point to the next cookie, if |
|
2873 // there is one. Save the present value for logging purposes |
|
2874 nsDependentCString savedCookieHeader(aCookieHeader); |
|
2875 |
|
2876 // newCookie says whether there are multiple cookies in the header; |
|
2877 // so we can handle them separately. |
|
2878 bool newCookie = ParseAttributes(aCookieHeader, cookieAttributes); |
|
2879 |
|
2880 int64_t currentTimeInUsec = PR_Now(); |
|
2881 |
|
2882 // calculate expiry time of cookie. |
|
2883 cookieAttributes.isSession = GetExpiry(cookieAttributes, aServerTime, |
|
2884 currentTimeInUsec / PR_USEC_PER_SEC); |
|
2885 if (aStatus == STATUS_ACCEPT_SESSION) { |
|
2886 // force lifetime to session. note that the expiration time, if set above, |
|
2887 // will still apply. |
|
2888 cookieAttributes.isSession = true; |
|
2889 } |
|
2890 |
|
2891 // reject cookie if it's over the size limit, per RFC2109 |
|
2892 if ((cookieAttributes.name.Length() + cookieAttributes.value.Length()) > kMaxBytesPerCookie) { |
|
2893 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie too big (> 4kb)"); |
|
2894 return newCookie; |
|
2895 } |
|
2896 |
|
2897 if (cookieAttributes.name.FindChar('\t') != kNotFound) { |
|
2898 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "invalid name character"); |
|
2899 return newCookie; |
|
2900 } |
|
2901 |
|
2902 // domain & path checks |
|
2903 if (!CheckDomain(cookieAttributes, aHostURI, aKey.mBaseDomain, aRequireHostMatch)) { |
|
2904 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the domain tests"); |
|
2905 return newCookie; |
|
2906 } |
|
2907 if (!CheckPath(cookieAttributes, aHostURI)) { |
|
2908 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "failed the path tests"); |
|
2909 return newCookie; |
|
2910 } |
|
2911 |
|
2912 // create a new nsCookie and copy attributes |
|
2913 nsRefPtr<nsCookie> cookie = |
|
2914 nsCookie::Create(cookieAttributes.name, |
|
2915 cookieAttributes.value, |
|
2916 cookieAttributes.host, |
|
2917 cookieAttributes.path, |
|
2918 cookieAttributes.expiryTime, |
|
2919 currentTimeInUsec, |
|
2920 nsCookie::GenerateUniqueCreationTime(currentTimeInUsec), |
|
2921 cookieAttributes.isSession, |
|
2922 cookieAttributes.isSecure, |
|
2923 cookieAttributes.isHttpOnly); |
|
2924 if (!cookie) |
|
2925 return newCookie; |
|
2926 |
|
2927 // check permissions from site permission list, or ask the user, |
|
2928 // to determine if we can set the cookie |
|
2929 if (mPermissionService) { |
|
2930 bool permission; |
|
2931 mPermissionService->CanSetCookie(aHostURI, |
|
2932 aChannel, |
|
2933 static_cast<nsICookie2*>(static_cast<nsCookie*>(cookie)), |
|
2934 &cookieAttributes.isSession, |
|
2935 &cookieAttributes.expiryTime, |
|
2936 &permission); |
|
2937 if (!permission) { |
|
2938 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, savedCookieHeader, "cookie rejected by permission manager"); |
|
2939 NotifyRejected(aHostURI); |
|
2940 return newCookie; |
|
2941 } |
|
2942 |
|
2943 // update isSession and expiry attributes, in case they changed |
|
2944 cookie->SetIsSession(cookieAttributes.isSession); |
|
2945 cookie->SetExpiry(cookieAttributes.expiryTime); |
|
2946 } |
|
2947 |
|
2948 // add the cookie to the list. AddInternal() takes care of logging. |
|
2949 // we get the current time again here, since it may have changed during prompting |
|
2950 AddInternal(aKey, cookie, PR_Now(), aHostURI, savedCookieHeader.get(), |
|
2951 aFromHttp); |
|
2952 return newCookie; |
|
2953 } |
|
2954 |
|
2955 // this is a backend function for adding a cookie to the list, via SetCookie. |
|
2956 // also used in the cookie manager, for profile migration from IE. |
|
2957 // it either replaces an existing cookie; or adds the cookie to the hashtable, |
|
2958 // and deletes a cookie (if maximum number of cookies has been |
|
2959 // reached). also performs list maintenance by removing expired cookies. |
|
2960 void |
|
2961 nsCookieService::AddInternal(const nsCookieKey &aKey, |
|
2962 nsCookie *aCookie, |
|
2963 int64_t aCurrentTimeInUsec, |
|
2964 nsIURI *aHostURI, |
|
2965 const char *aCookieHeader, |
|
2966 bool aFromHttp) |
|
2967 { |
|
2968 int64_t currentTime = aCurrentTimeInUsec / PR_USEC_PER_SEC; |
|
2969 |
|
2970 // if the new cookie is httponly, make sure we're not coming from script |
|
2971 if (!aFromHttp && aCookie->IsHttpOnly()) { |
|
2972 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
2973 "cookie is httponly; coming from script"); |
|
2974 return; |
|
2975 } |
|
2976 |
|
2977 nsListIter matchIter; |
|
2978 bool foundCookie = FindCookie(aKey, aCookie->Host(), |
|
2979 aCookie->Name(), aCookie->Path(), matchIter); |
|
2980 |
|
2981 nsRefPtr<nsCookie> oldCookie; |
|
2982 nsCOMPtr<nsIArray> purgedList; |
|
2983 if (foundCookie) { |
|
2984 oldCookie = matchIter.Cookie(); |
|
2985 |
|
2986 // Check if the old cookie is stale (i.e. has already expired). If so, we |
|
2987 // need to be careful about the semantics of removing it and adding the new |
|
2988 // cookie: we want the behavior wrt adding the new cookie to be the same as |
|
2989 // if it didn't exist, but we still want to fire a removal notification. |
|
2990 if (oldCookie->Expiry() <= currentTime) { |
|
2991 if (aCookie->Expiry() <= currentTime) { |
|
2992 // The new cookie has expired and the old one is stale. Nothing to do. |
|
2993 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
2994 "cookie has already expired"); |
|
2995 return; |
|
2996 } |
|
2997 |
|
2998 // Remove the stale cookie. We save notification for later, once all list |
|
2999 // modifications are complete. |
|
3000 RemoveCookieFromList(matchIter); |
|
3001 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
3002 "stale cookie was purged"); |
|
3003 purgedList = CreatePurgeList(oldCookie); |
|
3004 |
|
3005 // We've done all we need to wrt removing and notifying the stale cookie. |
|
3006 // From here on out, we pretend pretend it didn't exist, so that we |
|
3007 // preserve expected notification semantics when adding the new cookie. |
|
3008 foundCookie = false; |
|
3009 |
|
3010 } else { |
|
3011 // If the old cookie is httponly, make sure we're not coming from script. |
|
3012 if (!aFromHttp && oldCookie->IsHttpOnly()) { |
|
3013 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
3014 "previously stored cookie is httponly; coming from script"); |
|
3015 return; |
|
3016 } |
|
3017 |
|
3018 // Remove the old cookie. |
|
3019 RemoveCookieFromList(matchIter); |
|
3020 |
|
3021 // If the new cookie has expired -- i.e. the intent was simply to delete |
|
3022 // the old cookie -- then we're done. |
|
3023 if (aCookie->Expiry() <= currentTime) { |
|
3024 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
3025 "previously stored cookie was deleted"); |
|
3026 NotifyChanged(oldCookie, MOZ_UTF16("deleted")); |
|
3027 return; |
|
3028 } |
|
3029 |
|
3030 // Preserve creation time of cookie for ordering purposes. |
|
3031 aCookie->SetCreationTime(oldCookie->CreationTime()); |
|
3032 } |
|
3033 |
|
3034 } else { |
|
3035 // check if cookie has already expired |
|
3036 if (aCookie->Expiry() <= currentTime) { |
|
3037 COOKIE_LOGFAILURE(SET_COOKIE, aHostURI, aCookieHeader, |
|
3038 "cookie has already expired"); |
|
3039 return; |
|
3040 } |
|
3041 |
|
3042 // check if we have to delete an old cookie. |
|
3043 nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey); |
|
3044 if (entry && entry->GetCookies().Length() >= mMaxCookiesPerHost) { |
|
3045 nsListIter iter; |
|
3046 FindStaleCookie(entry, currentTime, iter); |
|
3047 oldCookie = iter.Cookie(); |
|
3048 |
|
3049 // remove the oldest cookie from the domain |
|
3050 RemoveCookieFromList(iter); |
|
3051 COOKIE_LOGEVICTED(oldCookie, "Too many cookies for this domain"); |
|
3052 purgedList = CreatePurgeList(oldCookie); |
|
3053 |
|
3054 } else if (mDBState->cookieCount >= ADD_TEN_PERCENT(mMaxNumberOfCookies)) { |
|
3055 int64_t maxAge = aCurrentTimeInUsec - mDBState->cookieOldestTime; |
|
3056 int64_t purgeAge = ADD_TEN_PERCENT(mCookiePurgeAge); |
|
3057 if (maxAge >= purgeAge) { |
|
3058 // we're over both size and age limits by 10%; time to purge the table! |
|
3059 // do this by: |
|
3060 // 1) removing expired cookies; |
|
3061 // 2) evicting the balance of old cookies until we reach the size limit. |
|
3062 // note that the cookieOldestTime indicator can be pessimistic - if it's |
|
3063 // older than the actual oldest cookie, we'll just purge more eagerly. |
|
3064 purgedList = PurgeCookies(aCurrentTimeInUsec); |
|
3065 } |
|
3066 } |
|
3067 } |
|
3068 |
|
3069 // Add the cookie to the db. We do not supply a params array for batching |
|
3070 // because this might result in removals and additions being out of order. |
|
3071 AddCookieToList(aKey, aCookie, mDBState, nullptr); |
|
3072 COOKIE_LOGSUCCESS(SET_COOKIE, aHostURI, aCookieHeader, aCookie, foundCookie); |
|
3073 |
|
3074 // Now that list mutations are complete, notify observers. We do it here |
|
3075 // because observers may themselves attempt to mutate the list. |
|
3076 if (purgedList) { |
|
3077 NotifyChanged(purgedList, MOZ_UTF16("batch-deleted")); |
|
3078 } |
|
3079 |
|
3080 NotifyChanged(aCookie, foundCookie ? MOZ_UTF16("changed") |
|
3081 : MOZ_UTF16("added")); |
|
3082 } |
|
3083 |
|
3084 /****************************************************************************** |
|
3085 * nsCookieService impl: |
|
3086 * private cookie header parsing functions |
|
3087 ******************************************************************************/ |
|
3088 |
|
3089 // The following comment block elucidates the function of ParseAttributes. |
|
3090 /****************************************************************************** |
|
3091 ** Augmented BNF, modified from RFC2109 Section 4.2.2 and RFC2616 Section 2.1 |
|
3092 ** please note: this BNF deviates from both specifications, and reflects this |
|
3093 ** implementation. <bnf> indicates a reference to the defined grammar "bnf". |
|
3094 |
|
3095 ** Differences from RFC2109/2616 and explanations: |
|
3096 1. implied *LWS |
|
3097 The grammar described by this specification is word-based. Except |
|
3098 where noted otherwise, linear white space (<LWS>) can be included |
|
3099 between any two adjacent words (token or quoted-string), and |
|
3100 between adjacent words and separators, without changing the |
|
3101 interpretation of a field. |
|
3102 <LWS> according to spec is SP|HT|CR|LF, but here, we allow only SP | HT. |
|
3103 |
|
3104 2. We use CR | LF as cookie separators, not ',' per spec, since ',' is in |
|
3105 common use inside values. |
|
3106 |
|
3107 3. tokens and values have looser restrictions on allowed characters than |
|
3108 spec. This is also due to certain characters being in common use inside |
|
3109 values. We allow only '=' to separate token/value pairs, and ';' to |
|
3110 terminate tokens or values. <LWS> is allowed within tokens and values |
|
3111 (see bug 206022). |
|
3112 |
|
3113 4. where appropriate, full <OCTET>s are allowed, where the spec dictates to |
|
3114 reject control chars or non-ASCII chars. This is erring on the loose |
|
3115 side, since there's probably no good reason to enforce this strictness. |
|
3116 |
|
3117 5. cookie <NAME> is optional, where spec requires it. This is a fairly |
|
3118 trivial case, but allows the flexibility of setting only a cookie <VALUE> |
|
3119 with a blank <NAME> and is required by some sites (see bug 169091). |
|
3120 |
|
3121 6. Attribute "HttpOnly", not covered in the RFCs, is supported |
|
3122 (see bug 178993). |
|
3123 |
|
3124 ** Begin BNF: |
|
3125 token = 1*<any allowed-chars except separators> |
|
3126 value = 1*<any allowed-chars except value-sep> |
|
3127 separators = ";" | "=" |
|
3128 value-sep = ";" |
|
3129 cookie-sep = CR | LF |
|
3130 allowed-chars = <any OCTET except NUL or cookie-sep> |
|
3131 OCTET = <any 8-bit sequence of data> |
|
3132 LWS = SP | HT |
|
3133 NUL = <US-ASCII NUL, null control character (0)> |
|
3134 CR = <US-ASCII CR, carriage return (13)> |
|
3135 LF = <US-ASCII LF, linefeed (10)> |
|
3136 SP = <US-ASCII SP, space (32)> |
|
3137 HT = <US-ASCII HT, horizontal-tab (9)> |
|
3138 |
|
3139 set-cookie = "Set-Cookie:" cookies |
|
3140 cookies = cookie *( cookie-sep cookie ) |
|
3141 cookie = [NAME "="] VALUE *(";" cookie-av) ; cookie NAME/VALUE must come first |
|
3142 NAME = token ; cookie name |
|
3143 VALUE = value ; cookie value |
|
3144 cookie-av = token ["=" value] |
|
3145 |
|
3146 valid values for cookie-av (checked post-parsing) are: |
|
3147 cookie-av = "Path" "=" value |
|
3148 | "Domain" "=" value |
|
3149 | "Expires" "=" value |
|
3150 | "Max-Age" "=" value |
|
3151 | "Comment" "=" value |
|
3152 | "Version" "=" value |
|
3153 | "Secure" |
|
3154 | "HttpOnly" |
|
3155 |
|
3156 ******************************************************************************/ |
|
3157 |
|
3158 // helper functions for GetTokenValue |
|
3159 static inline bool iswhitespace (char c) { return c == ' ' || c == '\t'; } |
|
3160 static inline bool isterminator (char c) { return c == '\n' || c == '\r'; } |
|
3161 static inline bool isvalueseparator (char c) { return isterminator(c) || c == ';'; } |
|
3162 static inline bool istokenseparator (char c) { return isvalueseparator(c) || c == '='; } |
|
3163 |
|
3164 // Parse a single token/value pair. |
|
3165 // Returns true if a cookie terminator is found, so caller can parse new cookie. |
|
3166 bool |
|
3167 nsCookieService::GetTokenValue(nsASingleFragmentCString::const_char_iterator &aIter, |
|
3168 nsASingleFragmentCString::const_char_iterator &aEndIter, |
|
3169 nsDependentCSubstring &aTokenString, |
|
3170 nsDependentCSubstring &aTokenValue, |
|
3171 bool &aEqualsFound) |
|
3172 { |
|
3173 nsASingleFragmentCString::const_char_iterator start, lastSpace; |
|
3174 // initialize value string to clear garbage |
|
3175 aTokenValue.Rebind(aIter, aIter); |
|
3176 |
|
3177 // find <token>, including any <LWS> between the end-of-token and the |
|
3178 // token separator. we'll remove trailing <LWS> next |
|
3179 while (aIter != aEndIter && iswhitespace(*aIter)) |
|
3180 ++aIter; |
|
3181 start = aIter; |
|
3182 while (aIter != aEndIter && !istokenseparator(*aIter)) |
|
3183 ++aIter; |
|
3184 |
|
3185 // remove trailing <LWS>; first check we're not at the beginning |
|
3186 lastSpace = aIter; |
|
3187 if (lastSpace != start) { |
|
3188 while (--lastSpace != start && iswhitespace(*lastSpace)) |
|
3189 continue; |
|
3190 ++lastSpace; |
|
3191 } |
|
3192 aTokenString.Rebind(start, lastSpace); |
|
3193 |
|
3194 aEqualsFound = (*aIter == '='); |
|
3195 if (aEqualsFound) { |
|
3196 // find <value> |
|
3197 while (++aIter != aEndIter && iswhitespace(*aIter)) |
|
3198 continue; |
|
3199 |
|
3200 start = aIter; |
|
3201 |
|
3202 // process <token> |
|
3203 // just look for ';' to terminate ('=' allowed) |
|
3204 while (aIter != aEndIter && !isvalueseparator(*aIter)) |
|
3205 ++aIter; |
|
3206 |
|
3207 // remove trailing <LWS>; first check we're not at the beginning |
|
3208 if (aIter != start) { |
|
3209 lastSpace = aIter; |
|
3210 while (--lastSpace != start && iswhitespace(*lastSpace)) |
|
3211 continue; |
|
3212 aTokenValue.Rebind(start, ++lastSpace); |
|
3213 } |
|
3214 } |
|
3215 |
|
3216 // aIter is on ';', or terminator, or EOS |
|
3217 if (aIter != aEndIter) { |
|
3218 // if on terminator, increment past & return true to process new cookie |
|
3219 if (isterminator(*aIter)) { |
|
3220 ++aIter; |
|
3221 return true; |
|
3222 } |
|
3223 // fall-through: aIter is on ';', increment and return false |
|
3224 ++aIter; |
|
3225 } |
|
3226 return false; |
|
3227 } |
|
3228 |
|
3229 // Parses attributes from cookie header. expires/max-age attributes aren't folded into the |
|
3230 // cookie struct here, because we don't know which one to use until we've parsed the header. |
|
3231 bool |
|
3232 nsCookieService::ParseAttributes(nsDependentCString &aCookieHeader, |
|
3233 nsCookieAttributes &aCookieAttributes) |
|
3234 { |
|
3235 static const char kPath[] = "path"; |
|
3236 static const char kDomain[] = "domain"; |
|
3237 static const char kExpires[] = "expires"; |
|
3238 static const char kMaxage[] = "max-age"; |
|
3239 static const char kSecure[] = "secure"; |
|
3240 static const char kHttpOnly[] = "httponly"; |
|
3241 |
|
3242 nsASingleFragmentCString::const_char_iterator tempBegin, tempEnd; |
|
3243 nsASingleFragmentCString::const_char_iterator cookieStart, cookieEnd; |
|
3244 aCookieHeader.BeginReading(cookieStart); |
|
3245 aCookieHeader.EndReading(cookieEnd); |
|
3246 |
|
3247 aCookieAttributes.isSecure = false; |
|
3248 aCookieAttributes.isHttpOnly = false; |
|
3249 |
|
3250 nsDependentCSubstring tokenString(cookieStart, cookieStart); |
|
3251 nsDependentCSubstring tokenValue (cookieStart, cookieStart); |
|
3252 bool newCookie, equalsFound; |
|
3253 |
|
3254 // extract cookie <NAME> & <VALUE> (first attribute), and copy the strings. |
|
3255 // if we find multiple cookies, return for processing |
|
3256 // note: if there's no '=', we assume token is <VALUE>. this is required by |
|
3257 // some sites (see bug 169091). |
|
3258 // XXX fix the parser to parse according to <VALUE> grammar for this case |
|
3259 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); |
|
3260 if (equalsFound) { |
|
3261 aCookieAttributes.name = tokenString; |
|
3262 aCookieAttributes.value = tokenValue; |
|
3263 } else { |
|
3264 aCookieAttributes.value = tokenString; |
|
3265 } |
|
3266 |
|
3267 // extract remaining attributes |
|
3268 while (cookieStart != cookieEnd && !newCookie) { |
|
3269 newCookie = GetTokenValue(cookieStart, cookieEnd, tokenString, tokenValue, equalsFound); |
|
3270 |
|
3271 if (!tokenValue.IsEmpty()) { |
|
3272 tokenValue.BeginReading(tempBegin); |
|
3273 tokenValue.EndReading(tempEnd); |
|
3274 } |
|
3275 |
|
3276 // decide which attribute we have, and copy the string |
|
3277 if (tokenString.LowerCaseEqualsLiteral(kPath)) |
|
3278 aCookieAttributes.path = tokenValue; |
|
3279 |
|
3280 else if (tokenString.LowerCaseEqualsLiteral(kDomain)) |
|
3281 aCookieAttributes.host = tokenValue; |
|
3282 |
|
3283 else if (tokenString.LowerCaseEqualsLiteral(kExpires)) |
|
3284 aCookieAttributes.expires = tokenValue; |
|
3285 |
|
3286 else if (tokenString.LowerCaseEqualsLiteral(kMaxage)) |
|
3287 aCookieAttributes.maxage = tokenValue; |
|
3288 |
|
3289 // ignore any tokenValue for isSecure; just set the boolean |
|
3290 else if (tokenString.LowerCaseEqualsLiteral(kSecure)) |
|
3291 aCookieAttributes.isSecure = true; |
|
3292 |
|
3293 // ignore any tokenValue for isHttpOnly (see bug 178993); |
|
3294 // just set the boolean |
|
3295 else if (tokenString.LowerCaseEqualsLiteral(kHttpOnly)) |
|
3296 aCookieAttributes.isHttpOnly = true; |
|
3297 } |
|
3298 |
|
3299 // rebind aCookieHeader, in case we need to process another cookie |
|
3300 aCookieHeader.Rebind(cookieStart, cookieEnd); |
|
3301 return newCookie; |
|
3302 } |
|
3303 |
|
3304 /****************************************************************************** |
|
3305 * nsCookieService impl: |
|
3306 * private domain & permission compliance enforcement functions |
|
3307 ******************************************************************************/ |
|
3308 |
|
3309 // Get the base domain for aHostURI; e.g. for "www.bbc.co.uk", this would be |
|
3310 // "bbc.co.uk". Only properly-formed URI's are tolerated, though a trailing |
|
3311 // dot may be present. If aHostURI is an IP address, an alias such as |
|
3312 // 'localhost', an eTLD such as 'co.uk', or the empty string, aBaseDomain will |
|
3313 // be the exact host, and aRequireHostMatch will be true to indicate that |
|
3314 // substring matches should not be performed. |
|
3315 nsresult |
|
3316 nsCookieService::GetBaseDomain(nsIURI *aHostURI, |
|
3317 nsCString &aBaseDomain, |
|
3318 bool &aRequireHostMatch) |
|
3319 { |
|
3320 // get the base domain. this will fail if the host contains a leading dot, |
|
3321 // more than one trailing dot, or is otherwise malformed. |
|
3322 nsresult rv = mTLDService->GetBaseDomain(aHostURI, 0, aBaseDomain); |
|
3323 aRequireHostMatch = rv == NS_ERROR_HOST_IS_IP_ADDRESS || |
|
3324 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS; |
|
3325 if (aRequireHostMatch) { |
|
3326 // aHostURI is either an IP address, an alias such as 'localhost', an eTLD |
|
3327 // such as 'co.uk', or the empty string. use the host as a key in such |
|
3328 // cases. |
|
3329 rv = aHostURI->GetAsciiHost(aBaseDomain); |
|
3330 } |
|
3331 NS_ENSURE_SUCCESS(rv, rv); |
|
3332 |
|
3333 // aHost (and thus aBaseDomain) may be the string '.'. If so, fail. |
|
3334 if (aBaseDomain.Length() == 1 && aBaseDomain.Last() == '.') |
|
3335 return NS_ERROR_INVALID_ARG; |
|
3336 |
|
3337 // block any URIs without a host that aren't file:// URIs. |
|
3338 if (aBaseDomain.IsEmpty()) { |
|
3339 bool isFileURI = false; |
|
3340 aHostURI->SchemeIs("file", &isFileURI); |
|
3341 if (!isFileURI) |
|
3342 return NS_ERROR_INVALID_ARG; |
|
3343 } |
|
3344 |
|
3345 return NS_OK; |
|
3346 } |
|
3347 |
|
3348 // Get the base domain for aHost; e.g. for "www.bbc.co.uk", this would be |
|
3349 // "bbc.co.uk". This is done differently than GetBaseDomain(): it is assumed |
|
3350 // that aHost is already normalized, and it may contain a leading dot |
|
3351 // (indicating that it represents a domain). A trailing dot may be present. |
|
3352 // If aHost is an IP address, an alias such as 'localhost', an eTLD such as |
|
3353 // 'co.uk', or the empty string, aBaseDomain will be the exact host, and a |
|
3354 // leading dot will be treated as an error. |
|
3355 nsresult |
|
3356 nsCookieService::GetBaseDomainFromHost(const nsACString &aHost, |
|
3357 nsCString &aBaseDomain) |
|
3358 { |
|
3359 // aHost must not be the string '.'. |
|
3360 if (aHost.Length() == 1 && aHost.Last() == '.') |
|
3361 return NS_ERROR_INVALID_ARG; |
|
3362 |
|
3363 // aHost may contain a leading dot; if so, strip it now. |
|
3364 bool domain = !aHost.IsEmpty() && aHost.First() == '.'; |
|
3365 |
|
3366 // get the base domain. this will fail if the host contains a leading dot, |
|
3367 // more than one trailing dot, or is otherwise malformed. |
|
3368 nsresult rv = mTLDService->GetBaseDomainFromHost(Substring(aHost, domain), 0, aBaseDomain); |
|
3369 if (rv == NS_ERROR_HOST_IS_IP_ADDRESS || |
|
3370 rv == NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) { |
|
3371 // aHost is either an IP address, an alias such as 'localhost', an eTLD |
|
3372 // such as 'co.uk', or the empty string. use the host as a key in such |
|
3373 // cases; however, we reject any such hosts with a leading dot, since it |
|
3374 // doesn't make sense for them to be domain cookies. |
|
3375 if (domain) |
|
3376 return NS_ERROR_INVALID_ARG; |
|
3377 |
|
3378 aBaseDomain = aHost; |
|
3379 return NS_OK; |
|
3380 } |
|
3381 return rv; |
|
3382 } |
|
3383 |
|
3384 // Normalizes the given hostname, component by component. ASCII/ACE |
|
3385 // components are lower-cased, and UTF-8 components are normalized per |
|
3386 // RFC 3454 and converted to ACE. |
|
3387 nsresult |
|
3388 nsCookieService::NormalizeHost(nsCString &aHost) |
|
3389 { |
|
3390 if (!IsASCII(aHost)) { |
|
3391 nsAutoCString host; |
|
3392 nsresult rv = mIDNService->ConvertUTF8toACE(aHost, host); |
|
3393 if (NS_FAILED(rv)) |
|
3394 return rv; |
|
3395 |
|
3396 aHost = host; |
|
3397 } |
|
3398 |
|
3399 ToLowerCase(aHost); |
|
3400 return NS_OK; |
|
3401 } |
|
3402 |
|
3403 // returns true if 'a' is equal to or a subdomain of 'b', |
|
3404 // assuming no leading dots are present. |
|
3405 static inline bool IsSubdomainOf(const nsCString &a, const nsCString &b) |
|
3406 { |
|
3407 if (a == b) |
|
3408 return true; |
|
3409 if (a.Length() > b.Length()) |
|
3410 return a[a.Length() - b.Length() - 1] == '.' && StringEndsWith(a, b); |
|
3411 return false; |
|
3412 } |
|
3413 |
|
3414 CookieStatus |
|
3415 nsCookieService::CheckPrefs(nsIURI *aHostURI, |
|
3416 bool aIsForeign, |
|
3417 bool aRequireHostMatch, |
|
3418 const char *aCookieHeader) |
|
3419 { |
|
3420 nsresult rv; |
|
3421 |
|
3422 // don't let ftp sites get/set cookies (could be a security issue) |
|
3423 bool ftp; |
|
3424 if (NS_SUCCEEDED(aHostURI->SchemeIs("ftp", &ftp)) && ftp) { |
|
3425 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "ftp sites cannot read cookies"); |
|
3426 return STATUS_REJECTED_WITH_ERROR; |
|
3427 } |
|
3428 |
|
3429 // check the permission list first; if we find an entry, it overrides |
|
3430 // default prefs. see bug 184059. |
|
3431 if (mPermissionService) { |
|
3432 nsCookieAccess access; |
|
3433 // Not passing an nsIChannel here is probably OK; our implementation |
|
3434 // doesn't do anything with it anyway. |
|
3435 rv = mPermissionService->CanAccess(aHostURI, nullptr, &access); |
|
3436 |
|
3437 // if we found an entry, use it |
|
3438 if (NS_SUCCEEDED(rv)) { |
|
3439 switch (access) { |
|
3440 case nsICookiePermission::ACCESS_DENY: |
|
3441 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, |
|
3442 aCookieHeader, "cookies are blocked for this site"); |
|
3443 return STATUS_REJECTED; |
|
3444 |
|
3445 case nsICookiePermission::ACCESS_ALLOW: |
|
3446 return STATUS_ACCEPTED; |
|
3447 |
|
3448 case nsICookiePermission::ACCESS_ALLOW_FIRST_PARTY_ONLY: |
|
3449 if (aIsForeign) { |
|
3450 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, |
|
3451 aCookieHeader, "third party cookies are blocked " |
|
3452 "for this site"); |
|
3453 return STATUS_REJECTED; |
|
3454 |
|
3455 } |
|
3456 return STATUS_ACCEPTED; |
|
3457 |
|
3458 case nsICookiePermission::ACCESS_LIMIT_THIRD_PARTY: |
|
3459 if (!aIsForeign) |
|
3460 return STATUS_ACCEPTED; |
|
3461 uint32_t priorCookieCount = 0; |
|
3462 nsAutoCString hostFromURI; |
|
3463 aHostURI->GetHost(hostFromURI); |
|
3464 CountCookiesFromHost(hostFromURI, &priorCookieCount); |
|
3465 if (priorCookieCount == 0) { |
|
3466 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, |
|
3467 aCookieHeader, "third party cookies are blocked " |
|
3468 "for this site"); |
|
3469 return STATUS_REJECTED; |
|
3470 } |
|
3471 return STATUS_ACCEPTED; |
|
3472 } |
|
3473 } |
|
3474 } |
|
3475 |
|
3476 // check default prefs |
|
3477 if (mCookieBehavior == BEHAVIOR_REJECT) { |
|
3478 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "cookies are disabled"); |
|
3479 return STATUS_REJECTED; |
|
3480 } |
|
3481 |
|
3482 // check if cookie is foreign |
|
3483 if (aIsForeign) { |
|
3484 if (mCookieBehavior == BEHAVIOR_ACCEPT && mThirdPartySession) |
|
3485 return STATUS_ACCEPT_SESSION; |
|
3486 |
|
3487 if (mCookieBehavior == BEHAVIOR_REJECTFOREIGN) { |
|
3488 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party"); |
|
3489 return STATUS_REJECTED; |
|
3490 } |
|
3491 |
|
3492 if (mCookieBehavior == BEHAVIOR_LIMITFOREIGN) { |
|
3493 uint32_t priorCookieCount = 0; |
|
3494 nsAutoCString hostFromURI; |
|
3495 aHostURI->GetHost(hostFromURI); |
|
3496 CountCookiesFromHost(hostFromURI, &priorCookieCount); |
|
3497 if (priorCookieCount == 0) { |
|
3498 COOKIE_LOGFAILURE(aCookieHeader ? SET_COOKIE : GET_COOKIE, aHostURI, aCookieHeader, "context is third party"); |
|
3499 return STATUS_REJECTED; |
|
3500 } |
|
3501 if (mThirdPartySession) |
|
3502 return STATUS_ACCEPT_SESSION; |
|
3503 } |
|
3504 } |
|
3505 |
|
3506 // if nothing has complained, accept cookie |
|
3507 return STATUS_ACCEPTED; |
|
3508 } |
|
3509 |
|
3510 // processes domain attribute, and returns true if host has permission to set for this domain. |
|
3511 bool |
|
3512 nsCookieService::CheckDomain(nsCookieAttributes &aCookieAttributes, |
|
3513 nsIURI *aHostURI, |
|
3514 const nsCString &aBaseDomain, |
|
3515 bool aRequireHostMatch) |
|
3516 { |
|
3517 // get host from aHostURI |
|
3518 nsAutoCString hostFromURI; |
|
3519 aHostURI->GetAsciiHost(hostFromURI); |
|
3520 |
|
3521 // if a domain is given, check the host has permission |
|
3522 if (!aCookieAttributes.host.IsEmpty()) { |
|
3523 // Tolerate leading '.' characters, but not if it's otherwise an empty host. |
|
3524 if (aCookieAttributes.host.Length() > 1 && |
|
3525 aCookieAttributes.host.First() == '.') { |
|
3526 aCookieAttributes.host.Cut(0, 1); |
|
3527 } |
|
3528 |
|
3529 // switch to lowercase now, to avoid case-insensitive compares everywhere |
|
3530 ToLowerCase(aCookieAttributes.host); |
|
3531 |
|
3532 // check whether the host is either an IP address, an alias such as |
|
3533 // 'localhost', an eTLD such as 'co.uk', or the empty string. in these |
|
3534 // cases, require an exact string match for the domain, and leave the cookie |
|
3535 // as a non-domain one. bug 105917 originally noted the requirement to deal |
|
3536 // with IP addresses. |
|
3537 if (aRequireHostMatch) |
|
3538 return hostFromURI.Equals(aCookieAttributes.host); |
|
3539 |
|
3540 // ensure the proposed domain is derived from the base domain; and also |
|
3541 // that the host domain is derived from the proposed domain (per RFC2109). |
|
3542 if (IsSubdomainOf(aCookieAttributes.host, aBaseDomain) && |
|
3543 IsSubdomainOf(hostFromURI, aCookieAttributes.host)) { |
|
3544 // prepend a dot to indicate a domain cookie |
|
3545 aCookieAttributes.host.Insert(NS_LITERAL_CSTRING("."), 0); |
|
3546 return true; |
|
3547 } |
|
3548 |
|
3549 /* |
|
3550 * note: RFC2109 section 4.3.2 requires that we check the following: |
|
3551 * that the portion of host not in domain does not contain a dot. |
|
3552 * this prevents hosts of the form x.y.co.nz from setting cookies in the |
|
3553 * entire .co.nz domain. however, it's only a only a partial solution and |
|
3554 * it breaks sites (IE doesn't enforce it), so we don't perform this check. |
|
3555 */ |
|
3556 return false; |
|
3557 } |
|
3558 |
|
3559 // no domain specified, use hostFromURI |
|
3560 aCookieAttributes.host = hostFromURI; |
|
3561 return true; |
|
3562 } |
|
3563 |
|
3564 bool |
|
3565 nsCookieService::CheckPath(nsCookieAttributes &aCookieAttributes, |
|
3566 nsIURI *aHostURI) |
|
3567 { |
|
3568 // if a path is given, check the host has permission |
|
3569 if (aCookieAttributes.path.IsEmpty() || aCookieAttributes.path.First() != '/') { |
|
3570 // strip down everything after the last slash to get the path, |
|
3571 // ignoring slashes in the query string part. |
|
3572 // if we can QI to nsIURL, that'll take care of the query string portion. |
|
3573 // otherwise, it's not an nsIURL and can't have a query string, so just find the last slash. |
|
3574 nsCOMPtr<nsIURL> hostURL = do_QueryInterface(aHostURI); |
|
3575 if (hostURL) { |
|
3576 hostURL->GetDirectory(aCookieAttributes.path); |
|
3577 } else { |
|
3578 aHostURI->GetPath(aCookieAttributes.path); |
|
3579 int32_t slash = aCookieAttributes.path.RFindChar('/'); |
|
3580 if (slash != kNotFound) { |
|
3581 aCookieAttributes.path.Truncate(slash + 1); |
|
3582 } |
|
3583 } |
|
3584 |
|
3585 #if 0 |
|
3586 } else { |
|
3587 /** |
|
3588 * The following test is part of the RFC2109 spec. Loosely speaking, it says that a site |
|
3589 * cannot set a cookie for a path that it is not on. See bug 155083. However this patch |
|
3590 * broke several sites -- nordea (bug 155768) and citibank (bug 156725). So this test has |
|
3591 * been disabled, unless we can evangelize these sites. |
|
3592 */ |
|
3593 // get path from aHostURI |
|
3594 nsAutoCString pathFromURI; |
|
3595 if (NS_FAILED(aHostURI->GetPath(pathFromURI)) || |
|
3596 !StringBeginsWith(pathFromURI, aCookieAttributes.path)) { |
|
3597 return false; |
|
3598 } |
|
3599 #endif |
|
3600 } |
|
3601 |
|
3602 if (aCookieAttributes.path.Length() > kMaxBytesPerPath || |
|
3603 aCookieAttributes.path.FindChar('\t') != kNotFound ) |
|
3604 return false; |
|
3605 |
|
3606 return true; |
|
3607 } |
|
3608 |
|
3609 bool |
|
3610 nsCookieService::GetExpiry(nsCookieAttributes &aCookieAttributes, |
|
3611 int64_t aServerTime, |
|
3612 int64_t aCurrentTime) |
|
3613 { |
|
3614 /* Determine when the cookie should expire. This is done by taking the difference between |
|
3615 * the server time and the time the server wants the cookie to expire, and adding that |
|
3616 * difference to the client time. This localizes the client time regardless of whether or |
|
3617 * not the TZ environment variable was set on the client. |
|
3618 * |
|
3619 * Note: We need to consider accounting for network lag here, per RFC. |
|
3620 */ |
|
3621 int64_t delta; |
|
3622 |
|
3623 // check for max-age attribute first; this overrides expires attribute |
|
3624 if (!aCookieAttributes.maxage.IsEmpty()) { |
|
3625 // obtain numeric value of maxageAttribute |
|
3626 int64_t maxage; |
|
3627 int32_t numInts = PR_sscanf(aCookieAttributes.maxage.get(), "%lld", &maxage); |
|
3628 |
|
3629 // default to session cookie if the conversion failed |
|
3630 if (numInts != 1) { |
|
3631 return true; |
|
3632 } |
|
3633 |
|
3634 delta = maxage; |
|
3635 |
|
3636 // check for expires attribute |
|
3637 } else if (!aCookieAttributes.expires.IsEmpty()) { |
|
3638 PRTime expires; |
|
3639 |
|
3640 // parse expiry time |
|
3641 if (PR_ParseTimeString(aCookieAttributes.expires.get(), true, &expires) != PR_SUCCESS) { |
|
3642 return true; |
|
3643 } |
|
3644 |
|
3645 delta = expires / int64_t(PR_USEC_PER_SEC) - aServerTime; |
|
3646 |
|
3647 // default to session cookie if no attributes found |
|
3648 } else { |
|
3649 return true; |
|
3650 } |
|
3651 |
|
3652 // if this addition overflows, expiryTime will be less than currentTime |
|
3653 // and the cookie will be expired - that's okay. |
|
3654 aCookieAttributes.expiryTime = aCurrentTime + delta; |
|
3655 |
|
3656 return false; |
|
3657 } |
|
3658 |
|
3659 /****************************************************************************** |
|
3660 * nsCookieService impl: |
|
3661 * private cookielist management functions |
|
3662 ******************************************************************************/ |
|
3663 |
|
3664 void |
|
3665 nsCookieService::RemoveAllFromMemory() |
|
3666 { |
|
3667 // clearing the hashtable will call each nsCookieEntry's dtor, |
|
3668 // which releases all their respective children. |
|
3669 mDBState->hostTable.Clear(); |
|
3670 mDBState->cookieCount = 0; |
|
3671 mDBState->cookieOldestTime = INT64_MAX; |
|
3672 } |
|
3673 |
|
3674 // stores temporary data for enumerating over the hash entries, |
|
3675 // since enumeration is done using callback functions |
|
3676 struct nsPurgeData |
|
3677 { |
|
3678 typedef nsTArray<nsListIter> ArrayType; |
|
3679 |
|
3680 nsPurgeData(int64_t aCurrentTime, |
|
3681 int64_t aPurgeTime, |
|
3682 ArrayType &aPurgeList, |
|
3683 nsIMutableArray *aRemovedList, |
|
3684 mozIStorageBindingParamsArray *aParamsArray) |
|
3685 : currentTime(aCurrentTime) |
|
3686 , purgeTime(aPurgeTime) |
|
3687 , oldestTime(INT64_MAX) |
|
3688 , purgeList(aPurgeList) |
|
3689 , removedList(aRemovedList) |
|
3690 , paramsArray(aParamsArray) |
|
3691 { |
|
3692 } |
|
3693 |
|
3694 // the current time, in seconds |
|
3695 int64_t currentTime; |
|
3696 |
|
3697 // lastAccessed time older than which cookies are eligible for purge |
|
3698 int64_t purgeTime; |
|
3699 |
|
3700 // lastAccessed time of the oldest cookie found during purge, to update our indicator |
|
3701 int64_t oldestTime; |
|
3702 |
|
3703 // list of cookies over the age limit, for purging |
|
3704 ArrayType &purgeList; |
|
3705 |
|
3706 // list of all cookies we've removed, for notification |
|
3707 nsIMutableArray *removedList; |
|
3708 |
|
3709 // The array of parameters to be bound to the statement for deletion later. |
|
3710 mozIStorageBindingParamsArray *paramsArray; |
|
3711 }; |
|
3712 |
|
3713 // comparator class for lastaccessed times of cookies. |
|
3714 class CompareCookiesByAge { |
|
3715 public: |
|
3716 bool Equals(const nsListIter &a, const nsListIter &b) const |
|
3717 { |
|
3718 return a.Cookie()->LastAccessed() == b.Cookie()->LastAccessed() && |
|
3719 a.Cookie()->CreationTime() == b.Cookie()->CreationTime(); |
|
3720 } |
|
3721 |
|
3722 bool LessThan(const nsListIter &a, const nsListIter &b) const |
|
3723 { |
|
3724 // compare by lastAccessed time, and tiebreak by creationTime. |
|
3725 int64_t result = a.Cookie()->LastAccessed() - b.Cookie()->LastAccessed(); |
|
3726 if (result != 0) |
|
3727 return result < 0; |
|
3728 |
|
3729 return a.Cookie()->CreationTime() < b.Cookie()->CreationTime(); |
|
3730 } |
|
3731 }; |
|
3732 |
|
3733 // comparator class for sorting cookies by entry and index. |
|
3734 class CompareCookiesByIndex { |
|
3735 public: |
|
3736 bool Equals(const nsListIter &a, const nsListIter &b) const |
|
3737 { |
|
3738 NS_ASSERTION(a.entry != b.entry || a.index != b.index, |
|
3739 "cookie indexes should never be equal"); |
|
3740 return false; |
|
3741 } |
|
3742 |
|
3743 bool LessThan(const nsListIter &a, const nsListIter &b) const |
|
3744 { |
|
3745 // compare by entryclass pointer, then by index. |
|
3746 if (a.entry != b.entry) |
|
3747 return a.entry < b.entry; |
|
3748 |
|
3749 return a.index < b.index; |
|
3750 } |
|
3751 }; |
|
3752 |
|
3753 PLDHashOperator |
|
3754 purgeCookiesCallback(nsCookieEntry *aEntry, |
|
3755 void *aArg) |
|
3756 { |
|
3757 nsPurgeData &data = *static_cast<nsPurgeData*>(aArg); |
|
3758 |
|
3759 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies(); |
|
3760 mozIStorageBindingParamsArray *array = data.paramsArray; |
|
3761 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ) { |
|
3762 nsListIter iter(aEntry, i); |
|
3763 nsCookie *cookie = cookies[i]; |
|
3764 |
|
3765 // check if the cookie has expired |
|
3766 if (cookie->Expiry() <= data.currentTime) { |
|
3767 data.removedList->AppendElement(cookie, false); |
|
3768 COOKIE_LOGEVICTED(cookie, "Cookie expired"); |
|
3769 |
|
3770 // remove from list; do not increment our iterator |
|
3771 gCookieService->RemoveCookieFromList(iter, array); |
|
3772 |
|
3773 } else { |
|
3774 // check if the cookie is over the age limit |
|
3775 if (cookie->LastAccessed() <= data.purgeTime) { |
|
3776 data.purgeList.AppendElement(iter); |
|
3777 |
|
3778 } else if (cookie->LastAccessed() < data.oldestTime) { |
|
3779 // reset our indicator |
|
3780 data.oldestTime = cookie->LastAccessed(); |
|
3781 } |
|
3782 |
|
3783 ++i; |
|
3784 } |
|
3785 } |
|
3786 return PL_DHASH_NEXT; |
|
3787 } |
|
3788 |
|
3789 // purges expired and old cookies in a batch operation. |
|
3790 already_AddRefed<nsIArray> |
|
3791 nsCookieService::PurgeCookies(int64_t aCurrentTimeInUsec) |
|
3792 { |
|
3793 NS_ASSERTION(mDBState->hostTable.Count() > 0, "table is empty"); |
|
3794 EnsureReadComplete(); |
|
3795 |
|
3796 #ifdef PR_LOGGING |
|
3797 uint32_t initialCookieCount = mDBState->cookieCount; |
|
3798 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
3799 ("PurgeCookies(): beginning purge with %ld cookies and %lld oldest age", |
|
3800 mDBState->cookieCount, aCurrentTimeInUsec - mDBState->cookieOldestTime)); |
|
3801 #endif |
|
3802 |
|
3803 nsAutoTArray<nsListIter, kMaxNumberOfCookies> purgeList; |
|
3804 |
|
3805 nsCOMPtr<nsIMutableArray> removedList = do_CreateInstance(NS_ARRAY_CONTRACTID); |
|
3806 |
|
3807 // Create a params array to batch the removals. This is OK here because |
|
3808 // all the removals are in order, and there are no interleaved additions. |
|
3809 mozIStorageAsyncStatement *stmt = mDBState->stmtDelete; |
|
3810 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray; |
|
3811 if (mDBState->dbConn) { |
|
3812 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
|
3813 } |
|
3814 |
|
3815 nsPurgeData data(aCurrentTimeInUsec / PR_USEC_PER_SEC, |
|
3816 aCurrentTimeInUsec - mCookiePurgeAge, purgeList, removedList, paramsArray); |
|
3817 mDBState->hostTable.EnumerateEntries(purgeCookiesCallback, &data); |
|
3818 |
|
3819 #ifdef PR_LOGGING |
|
3820 uint32_t postExpiryCookieCount = mDBState->cookieCount; |
|
3821 #endif |
|
3822 |
|
3823 // now we have a list of iterators for cookies over the age limit. |
|
3824 // sort them by age, and then we'll see how many to remove... |
|
3825 purgeList.Sort(CompareCookiesByAge()); |
|
3826 |
|
3827 // only remove old cookies until we reach the max cookie limit, no more. |
|
3828 uint32_t excess = mDBState->cookieCount > mMaxNumberOfCookies ? |
|
3829 mDBState->cookieCount - mMaxNumberOfCookies : 0; |
|
3830 if (purgeList.Length() > excess) { |
|
3831 // We're not purging everything in the list, so update our indicator. |
|
3832 data.oldestTime = purgeList[excess].Cookie()->LastAccessed(); |
|
3833 |
|
3834 purgeList.SetLength(excess); |
|
3835 } |
|
3836 |
|
3837 // sort the list again, this time grouping cookies with a common entryclass |
|
3838 // together, and with ascending index. this allows us to iterate backwards |
|
3839 // over the list removing cookies, without having to adjust indexes as we go. |
|
3840 purgeList.Sort(CompareCookiesByIndex()); |
|
3841 for (nsPurgeData::ArrayType::index_type i = purgeList.Length(); i--; ) { |
|
3842 nsCookie *cookie = purgeList[i].Cookie(); |
|
3843 removedList->AppendElement(cookie, false); |
|
3844 COOKIE_LOGEVICTED(cookie, "Cookie too old"); |
|
3845 |
|
3846 RemoveCookieFromList(purgeList[i], paramsArray); |
|
3847 } |
|
3848 |
|
3849 // Update the database if we have entries to purge. |
|
3850 if (paramsArray) { |
|
3851 uint32_t length; |
|
3852 paramsArray->GetLength(&length); |
|
3853 if (length) { |
|
3854 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
|
3855 NS_ASSERT_SUCCESS(rv); |
|
3856 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
3857 rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle)); |
|
3858 NS_ASSERT_SUCCESS(rv); |
|
3859 } |
|
3860 } |
|
3861 |
|
3862 // reset the oldest time indicator |
|
3863 mDBState->cookieOldestTime = data.oldestTime; |
|
3864 |
|
3865 COOKIE_LOGSTRING(PR_LOG_DEBUG, |
|
3866 ("PurgeCookies(): %ld expired; %ld purged; %ld remain; %lld oldest age", |
|
3867 initialCookieCount - postExpiryCookieCount, |
|
3868 postExpiryCookieCount - mDBState->cookieCount, |
|
3869 mDBState->cookieCount, |
|
3870 aCurrentTimeInUsec - mDBState->cookieOldestTime)); |
|
3871 |
|
3872 return removedList.forget(); |
|
3873 } |
|
3874 |
|
3875 // find whether a given cookie has been previously set. this is provided by the |
|
3876 // nsICookieManager2 interface. |
|
3877 NS_IMETHODIMP |
|
3878 nsCookieService::CookieExists(nsICookie2 *aCookie, |
|
3879 bool *aFoundCookie) |
|
3880 { |
|
3881 NS_ENSURE_ARG_POINTER(aCookie); |
|
3882 |
|
3883 if (!mDBState) { |
|
3884 NS_WARNING("No DBState! Profile already closed?"); |
|
3885 return NS_ERROR_NOT_AVAILABLE; |
|
3886 } |
|
3887 |
|
3888 nsAutoCString host, name, path; |
|
3889 nsresult rv = aCookie->GetHost(host); |
|
3890 NS_ENSURE_SUCCESS(rv, rv); |
|
3891 rv = aCookie->GetName(name); |
|
3892 NS_ENSURE_SUCCESS(rv, rv); |
|
3893 rv = aCookie->GetPath(path); |
|
3894 NS_ENSURE_SUCCESS(rv, rv); |
|
3895 |
|
3896 nsAutoCString baseDomain; |
|
3897 rv = GetBaseDomainFromHost(host, baseDomain); |
|
3898 NS_ENSURE_SUCCESS(rv, rv); |
|
3899 |
|
3900 nsListIter iter; |
|
3901 *aFoundCookie = FindCookie(DEFAULT_APP_KEY(baseDomain), host, name, path, iter); |
|
3902 return NS_OK; |
|
3903 } |
|
3904 |
|
3905 // For a given base domain, find either an expired cookie or the oldest cookie |
|
3906 // by lastAccessed time. |
|
3907 void |
|
3908 nsCookieService::FindStaleCookie(nsCookieEntry *aEntry, |
|
3909 int64_t aCurrentTime, |
|
3910 nsListIter &aIter) |
|
3911 { |
|
3912 aIter.entry = nullptr; |
|
3913 |
|
3914 int64_t oldestTime = 0; |
|
3915 const nsCookieEntry::ArrayType &cookies = aEntry->GetCookies(); |
|
3916 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
3917 nsCookie *cookie = cookies[i]; |
|
3918 |
|
3919 // If we found an expired cookie, we're done. |
|
3920 if (cookie->Expiry() <= aCurrentTime) { |
|
3921 aIter.entry = aEntry; |
|
3922 aIter.index = i; |
|
3923 return; |
|
3924 } |
|
3925 |
|
3926 // Check if we've found the oldest cookie so far. |
|
3927 if (!aIter.entry || oldestTime > cookie->LastAccessed()) { |
|
3928 oldestTime = cookie->LastAccessed(); |
|
3929 aIter.entry = aEntry; |
|
3930 aIter.index = i; |
|
3931 } |
|
3932 } |
|
3933 } |
|
3934 |
|
3935 // count the number of cookies stored by a particular host. this is provided by the |
|
3936 // nsICookieManager2 interface. |
|
3937 NS_IMETHODIMP |
|
3938 nsCookieService::CountCookiesFromHost(const nsACString &aHost, |
|
3939 uint32_t *aCountFromHost) |
|
3940 { |
|
3941 if (!mDBState) { |
|
3942 NS_WARNING("No DBState! Profile already closed?"); |
|
3943 return NS_ERROR_NOT_AVAILABLE; |
|
3944 } |
|
3945 |
|
3946 // first, normalize the hostname, and fail if it contains illegal characters. |
|
3947 nsAutoCString host(aHost); |
|
3948 nsresult rv = NormalizeHost(host); |
|
3949 NS_ENSURE_SUCCESS(rv, rv); |
|
3950 |
|
3951 nsAutoCString baseDomain; |
|
3952 rv = GetBaseDomainFromHost(host, baseDomain); |
|
3953 NS_ENSURE_SUCCESS(rv, rv); |
|
3954 |
|
3955 nsCookieKey key = DEFAULT_APP_KEY(baseDomain); |
|
3956 EnsureReadDomain(key); |
|
3957 |
|
3958 // Return a count of all cookies, including expired. |
|
3959 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key); |
|
3960 *aCountFromHost = entry ? entry->GetCookies().Length() : 0; |
|
3961 return NS_OK; |
|
3962 } |
|
3963 |
|
3964 // get an enumerator of cookies stored by a particular host. this is provided by the |
|
3965 // nsICookieManager2 interface. |
|
3966 NS_IMETHODIMP |
|
3967 nsCookieService::GetCookiesFromHost(const nsACString &aHost, |
|
3968 nsISimpleEnumerator **aEnumerator) |
|
3969 { |
|
3970 if (!mDBState) { |
|
3971 NS_WARNING("No DBState! Profile already closed?"); |
|
3972 return NS_ERROR_NOT_AVAILABLE; |
|
3973 } |
|
3974 |
|
3975 // first, normalize the hostname, and fail if it contains illegal characters. |
|
3976 nsAutoCString host(aHost); |
|
3977 nsresult rv = NormalizeHost(host); |
|
3978 NS_ENSURE_SUCCESS(rv, rv); |
|
3979 |
|
3980 nsAutoCString baseDomain; |
|
3981 rv = GetBaseDomainFromHost(host, baseDomain); |
|
3982 NS_ENSURE_SUCCESS(rv, rv); |
|
3983 |
|
3984 nsCookieKey key = DEFAULT_APP_KEY(baseDomain); |
|
3985 EnsureReadDomain(key); |
|
3986 |
|
3987 nsCookieEntry *entry = mDBState->hostTable.GetEntry(key); |
|
3988 if (!entry) |
|
3989 return NS_NewEmptyEnumerator(aEnumerator); |
|
3990 |
|
3991 nsCOMArray<nsICookie> cookieList(mMaxCookiesPerHost); |
|
3992 const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
|
3993 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
3994 cookieList.AppendObject(cookies[i]); |
|
3995 } |
|
3996 |
|
3997 return NS_NewArrayEnumerator(aEnumerator, cookieList); |
|
3998 } |
|
3999 |
|
4000 namespace { |
|
4001 |
|
4002 /** |
|
4003 * This structure is used as a in/out parameter when enumerating the cookies |
|
4004 * for an app. |
|
4005 * It will contain the app id and onlyBrowserElement flag information as input |
|
4006 * and will contain the array of matching cookies as output. |
|
4007 */ |
|
4008 struct GetCookiesForAppStruct { |
|
4009 uint32_t appId; |
|
4010 bool onlyBrowserElement; |
|
4011 nsCOMArray<nsICookie> cookies; |
|
4012 |
|
4013 GetCookiesForAppStruct() MOZ_DELETE; |
|
4014 GetCookiesForAppStruct(uint32_t aAppId, bool aOnlyBrowserElement) |
|
4015 : appId(aAppId) |
|
4016 , onlyBrowserElement(aOnlyBrowserElement) |
|
4017 {} |
|
4018 }; |
|
4019 |
|
4020 } // anonymous namespace |
|
4021 |
|
4022 /* static */ PLDHashOperator |
|
4023 nsCookieService::GetCookiesForApp(nsCookieEntry* entry, void* arg) |
|
4024 { |
|
4025 GetCookiesForAppStruct* data = static_cast<GetCookiesForAppStruct*>(arg); |
|
4026 |
|
4027 if (entry->mAppId != data->appId || |
|
4028 (data->onlyBrowserElement && !entry->mInBrowserElement)) { |
|
4029 return PL_DHASH_NEXT; |
|
4030 } |
|
4031 |
|
4032 const nsCookieEntry::ArrayType& cookies = entry->GetCookies(); |
|
4033 |
|
4034 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
4035 data->cookies.AppendObject(cookies[i]); |
|
4036 } |
|
4037 |
|
4038 return PL_DHASH_NEXT; |
|
4039 } |
|
4040 |
|
4041 NS_IMETHODIMP |
|
4042 nsCookieService::GetCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement, |
|
4043 nsISimpleEnumerator** aEnumerator) |
|
4044 { |
|
4045 if (!mDBState) { |
|
4046 NS_WARNING("No DBState! Profile already closed?"); |
|
4047 return NS_ERROR_NOT_AVAILABLE; |
|
4048 } |
|
4049 |
|
4050 NS_ENSURE_TRUE(aAppId != NECKO_UNKNOWN_APP_ID, NS_ERROR_INVALID_ARG); |
|
4051 |
|
4052 GetCookiesForAppStruct data(aAppId, aOnlyBrowserElement); |
|
4053 mDBState->hostTable.EnumerateEntries(GetCookiesForApp, &data); |
|
4054 |
|
4055 return NS_NewArrayEnumerator(aEnumerator, data.cookies); |
|
4056 } |
|
4057 |
|
4058 NS_IMETHODIMP |
|
4059 nsCookieService::RemoveCookiesForApp(uint32_t aAppId, bool aOnlyBrowserElement) |
|
4060 { |
|
4061 nsCOMPtr<nsISimpleEnumerator> enumerator; |
|
4062 nsresult rv = GetCookiesForApp(aAppId, aOnlyBrowserElement, |
|
4063 getter_AddRefs(enumerator)); |
|
4064 |
|
4065 NS_ENSURE_SUCCESS(rv, rv); |
|
4066 |
|
4067 bool hasMore; |
|
4068 while (NS_SUCCEEDED(enumerator->HasMoreElements(&hasMore)) && hasMore) { |
|
4069 nsCOMPtr<nsISupports> supports; |
|
4070 nsCOMPtr<nsICookie> cookie; |
|
4071 rv = enumerator->GetNext(getter_AddRefs(supports)); |
|
4072 NS_ENSURE_SUCCESS(rv, rv); |
|
4073 |
|
4074 cookie = do_QueryInterface(supports); |
|
4075 |
|
4076 nsAutoCString host; |
|
4077 cookie->GetHost(host); |
|
4078 |
|
4079 nsAutoCString name; |
|
4080 cookie->GetName(name); |
|
4081 |
|
4082 nsAutoCString path; |
|
4083 cookie->GetPath(path); |
|
4084 |
|
4085 // nsICookie do not carry the appId/inBrowserElement information. |
|
4086 // That means we have to guess. This is easy for appId but not for |
|
4087 // inBrowserElement flag. |
|
4088 // A simple solution is to always ask to remove the cookie with |
|
4089 // inBrowserElement = true and only ask for the other one to be removed if |
|
4090 // we happen to be in the case of !aOnlyBrowserElement. |
|
4091 // Anyway, with this solution, we will likely be looking for unexistant |
|
4092 // cookies. |
|
4093 // |
|
4094 // NOTE: we could make this better by getting nsCookieEntry objects instead |
|
4095 // of plain nsICookie. |
|
4096 Remove(host, aAppId, true, name, path, false); |
|
4097 if (!aOnlyBrowserElement) { |
|
4098 Remove(host, aAppId, false, name, path, false); |
|
4099 } |
|
4100 } |
|
4101 |
|
4102 return NS_OK; |
|
4103 } |
|
4104 |
|
4105 // find an exact cookie specified by host, name, and path that hasn't expired. |
|
4106 bool |
|
4107 nsCookieService::FindCookie(const nsCookieKey &aKey, |
|
4108 const nsAFlatCString &aHost, |
|
4109 const nsAFlatCString &aName, |
|
4110 const nsAFlatCString &aPath, |
|
4111 nsListIter &aIter) |
|
4112 { |
|
4113 EnsureReadDomain(aKey); |
|
4114 |
|
4115 nsCookieEntry *entry = mDBState->hostTable.GetEntry(aKey); |
|
4116 if (!entry) |
|
4117 return false; |
|
4118 |
|
4119 const nsCookieEntry::ArrayType &cookies = entry->GetCookies(); |
|
4120 for (nsCookieEntry::IndexType i = 0; i < cookies.Length(); ++i) { |
|
4121 nsCookie *cookie = cookies[i]; |
|
4122 |
|
4123 if (aHost.Equals(cookie->Host()) && |
|
4124 aPath.Equals(cookie->Path()) && |
|
4125 aName.Equals(cookie->Name())) { |
|
4126 aIter = nsListIter(entry, i); |
|
4127 return true; |
|
4128 } |
|
4129 } |
|
4130 |
|
4131 return false; |
|
4132 } |
|
4133 |
|
4134 // remove a cookie from the hashtable, and update the iterator state. |
|
4135 void |
|
4136 nsCookieService::RemoveCookieFromList(const nsListIter &aIter, |
|
4137 mozIStorageBindingParamsArray *aParamsArray) |
|
4138 { |
|
4139 // if it's a non-session cookie, remove it from the db |
|
4140 if (!aIter.Cookie()->IsSession() && mDBState->dbConn) { |
|
4141 // Use the asynchronous binding methods to ensure that we do not acquire |
|
4142 // the database lock. |
|
4143 mozIStorageAsyncStatement *stmt = mDBState->stmtDelete; |
|
4144 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray); |
|
4145 if (!paramsArray) { |
|
4146 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
|
4147 } |
|
4148 |
|
4149 nsCOMPtr<mozIStorageBindingParams> params; |
|
4150 paramsArray->NewBindingParams(getter_AddRefs(params)); |
|
4151 |
|
4152 DebugOnly<nsresult> rv = |
|
4153 params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), |
|
4154 aIter.Cookie()->Name()); |
|
4155 NS_ASSERT_SUCCESS(rv); |
|
4156 |
|
4157 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), |
|
4158 aIter.Cookie()->Host()); |
|
4159 NS_ASSERT_SUCCESS(rv); |
|
4160 |
|
4161 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), |
|
4162 aIter.Cookie()->Path()); |
|
4163 NS_ASSERT_SUCCESS(rv); |
|
4164 |
|
4165 rv = paramsArray->AddParams(params); |
|
4166 NS_ASSERT_SUCCESS(rv); |
|
4167 |
|
4168 // If we weren't given a params array, we'll need to remove it ourselves. |
|
4169 if (!aParamsArray) { |
|
4170 rv = stmt->BindParameters(paramsArray); |
|
4171 NS_ASSERT_SUCCESS(rv); |
|
4172 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
4173 rv = stmt->ExecuteAsync(mDBState->removeListener, getter_AddRefs(handle)); |
|
4174 NS_ASSERT_SUCCESS(rv); |
|
4175 } |
|
4176 } |
|
4177 |
|
4178 if (aIter.entry->GetCookies().Length() == 1) { |
|
4179 // we're removing the last element in the array - so just remove the entry |
|
4180 // from the hash. note that the entryclass' dtor will take care of |
|
4181 // releasing this last element for us! |
|
4182 mDBState->hostTable.RawRemoveEntry(aIter.entry); |
|
4183 |
|
4184 } else { |
|
4185 // just remove the element from the list |
|
4186 aIter.entry->GetCookies().RemoveElementAt(aIter.index); |
|
4187 } |
|
4188 |
|
4189 --mDBState->cookieCount; |
|
4190 } |
|
4191 |
|
4192 void |
|
4193 bindCookieParameters(mozIStorageBindingParamsArray *aParamsArray, |
|
4194 const nsCookieKey &aKey, |
|
4195 const nsCookie *aCookie) |
|
4196 { |
|
4197 NS_ASSERTION(aParamsArray, "Null params array passed to bindCookieParameters!"); |
|
4198 NS_ASSERTION(aCookie, "Null cookie passed to bindCookieParameters!"); |
|
4199 |
|
4200 // Use the asynchronous binding methods to ensure that we do not acquire the |
|
4201 // database lock. |
|
4202 nsCOMPtr<mozIStorageBindingParams> params; |
|
4203 DebugOnly<nsresult> rv = |
|
4204 aParamsArray->NewBindingParams(getter_AddRefs(params)); |
|
4205 NS_ASSERT_SUCCESS(rv); |
|
4206 |
|
4207 // Bind our values to params |
|
4208 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("baseDomain"), |
|
4209 aKey.mBaseDomain); |
|
4210 NS_ASSERT_SUCCESS(rv); |
|
4211 |
|
4212 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("appId"), |
|
4213 aKey.mAppId); |
|
4214 NS_ASSERT_SUCCESS(rv); |
|
4215 |
|
4216 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("inBrowserElement"), |
|
4217 aKey.mInBrowserElement ? 1 : 0); |
|
4218 NS_ASSERT_SUCCESS(rv); |
|
4219 |
|
4220 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), |
|
4221 aCookie->Name()); |
|
4222 NS_ASSERT_SUCCESS(rv); |
|
4223 |
|
4224 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("value"), |
|
4225 aCookie->Value()); |
|
4226 NS_ASSERT_SUCCESS(rv); |
|
4227 |
|
4228 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), |
|
4229 aCookie->Host()); |
|
4230 NS_ASSERT_SUCCESS(rv); |
|
4231 |
|
4232 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), |
|
4233 aCookie->Path()); |
|
4234 NS_ASSERT_SUCCESS(rv); |
|
4235 |
|
4236 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("expiry"), |
|
4237 aCookie->Expiry()); |
|
4238 NS_ASSERT_SUCCESS(rv); |
|
4239 |
|
4240 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"), |
|
4241 aCookie->LastAccessed()); |
|
4242 NS_ASSERT_SUCCESS(rv); |
|
4243 |
|
4244 rv = params->BindInt64ByName(NS_LITERAL_CSTRING("creationTime"), |
|
4245 aCookie->CreationTime()); |
|
4246 NS_ASSERT_SUCCESS(rv); |
|
4247 |
|
4248 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isSecure"), |
|
4249 aCookie->IsSecure()); |
|
4250 NS_ASSERT_SUCCESS(rv); |
|
4251 |
|
4252 rv = params->BindInt32ByName(NS_LITERAL_CSTRING("isHttpOnly"), |
|
4253 aCookie->IsHttpOnly()); |
|
4254 NS_ASSERT_SUCCESS(rv); |
|
4255 |
|
4256 // Bind the params to the array. |
|
4257 rv = aParamsArray->AddParams(params); |
|
4258 NS_ASSERT_SUCCESS(rv); |
|
4259 } |
|
4260 |
|
4261 void |
|
4262 nsCookieService::AddCookieToList(const nsCookieKey &aKey, |
|
4263 nsCookie *aCookie, |
|
4264 DBState *aDBState, |
|
4265 mozIStorageBindingParamsArray *aParamsArray, |
|
4266 bool aWriteToDB) |
|
4267 { |
|
4268 NS_ASSERTION(!(aDBState->dbConn && !aWriteToDB && aParamsArray), |
|
4269 "Not writing to the DB but have a params array?"); |
|
4270 NS_ASSERTION(!(!aDBState->dbConn && aParamsArray), |
|
4271 "Do not have a DB connection but have a params array?"); |
|
4272 |
|
4273 nsCookieEntry *entry = aDBState->hostTable.PutEntry(aKey); |
|
4274 NS_ASSERTION(entry, "can't insert element into a null entry!"); |
|
4275 |
|
4276 entry->GetCookies().AppendElement(aCookie); |
|
4277 ++aDBState->cookieCount; |
|
4278 |
|
4279 // keep track of the oldest cookie, for when it comes time to purge |
|
4280 if (aCookie->LastAccessed() < aDBState->cookieOldestTime) |
|
4281 aDBState->cookieOldestTime = aCookie->LastAccessed(); |
|
4282 |
|
4283 // if it's a non-session cookie and hasn't just been read from the db, write it out. |
|
4284 if (aWriteToDB && !aCookie->IsSession() && aDBState->dbConn) { |
|
4285 mozIStorageAsyncStatement *stmt = aDBState->stmtInsert; |
|
4286 nsCOMPtr<mozIStorageBindingParamsArray> paramsArray(aParamsArray); |
|
4287 if (!paramsArray) { |
|
4288 stmt->NewBindingParamsArray(getter_AddRefs(paramsArray)); |
|
4289 } |
|
4290 bindCookieParameters(paramsArray, aKey, aCookie); |
|
4291 |
|
4292 // If we were supplied an array to store parameters, we shouldn't call |
|
4293 // executeAsync - someone up the stack will do this for us. |
|
4294 if (!aParamsArray) { |
|
4295 DebugOnly<nsresult> rv = stmt->BindParameters(paramsArray); |
|
4296 NS_ASSERT_SUCCESS(rv); |
|
4297 nsCOMPtr<mozIStoragePendingStatement> handle; |
|
4298 rv = stmt->ExecuteAsync(mDBState->insertListener, getter_AddRefs(handle)); |
|
4299 NS_ASSERT_SUCCESS(rv); |
|
4300 } |
|
4301 } |
|
4302 } |
|
4303 |
|
4304 void |
|
4305 nsCookieService::UpdateCookieInList(nsCookie *aCookie, |
|
4306 int64_t aLastAccessed, |
|
4307 mozIStorageBindingParamsArray *aParamsArray) |
|
4308 { |
|
4309 NS_ASSERTION(aCookie, "Passing a null cookie to UpdateCookieInList!"); |
|
4310 |
|
4311 // udpate the lastAccessed timestamp |
|
4312 aCookie->SetLastAccessed(aLastAccessed); |
|
4313 |
|
4314 // if it's a non-session cookie, update it in the db too |
|
4315 if (!aCookie->IsSession() && aParamsArray) { |
|
4316 // Create our params holder. |
|
4317 nsCOMPtr<mozIStorageBindingParams> params; |
|
4318 aParamsArray->NewBindingParams(getter_AddRefs(params)); |
|
4319 |
|
4320 // Bind our parameters. |
|
4321 DebugOnly<nsresult> rv = |
|
4322 params->BindInt64ByName(NS_LITERAL_CSTRING("lastAccessed"), |
|
4323 aLastAccessed); |
|
4324 NS_ASSERT_SUCCESS(rv); |
|
4325 |
|
4326 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("name"), |
|
4327 aCookie->Name()); |
|
4328 NS_ASSERT_SUCCESS(rv); |
|
4329 |
|
4330 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("host"), |
|
4331 aCookie->Host()); |
|
4332 NS_ASSERT_SUCCESS(rv); |
|
4333 |
|
4334 rv = params->BindUTF8StringByName(NS_LITERAL_CSTRING("path"), |
|
4335 aCookie->Path()); |
|
4336 NS_ASSERT_SUCCESS(rv); |
|
4337 |
|
4338 // Add our bound parameters to the array. |
|
4339 rv = aParamsArray->AddParams(params); |
|
4340 NS_ASSERT_SUCCESS(rv); |
|
4341 } |
|
4342 } |
|
4343 |
|
4344 size_t |
|
4345 nsCookieService::SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const |
|
4346 { |
|
4347 size_t n = aMallocSizeOf(this); |
|
4348 |
|
4349 if (mDefaultDBState) { |
|
4350 n += mDefaultDBState->SizeOfIncludingThis(aMallocSizeOf); |
|
4351 } |
|
4352 if (mPrivateDBState) { |
|
4353 n += mPrivateDBState->SizeOfIncludingThis(aMallocSizeOf); |
|
4354 } |
|
4355 |
|
4356 return n; |
|
4357 } |
|
4358 |
|
4359 MOZ_DEFINE_MALLOC_SIZE_OF(CookieServiceMallocSizeOf) |
|
4360 |
|
4361 NS_IMETHODIMP |
|
4362 nsCookieService::CollectReports(nsIHandleReportCallback* aHandleReport, |
|
4363 nsISupports* aData) |
|
4364 { |
|
4365 return MOZ_COLLECT_REPORT( |
|
4366 "explicit/cookie-service", KIND_HEAP, UNITS_BYTES, |
|
4367 SizeOfIncludingThis(CookieServiceMallocSizeOf), |
|
4368 "Memory used by the cookie service."); |
|
4369 } |