|
1 /* Any copyright is dedicated to the Public Domain. |
|
2 * http://creativecommons.org/publicdomain/zero/1.0/ |
|
3 */ |
|
4 |
|
5 Components.utils.import("resource://gre/modules/Services.jsm"); |
|
6 Components.utils.import("resource://gre/modules/NetUtil.jsm"); |
|
7 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
8 |
|
9 const Cc = Components.classes; |
|
10 const Ci = Components.interfaces; |
|
11 const Cr = Components.results; |
|
12 |
|
13 XPCOMUtils.defineLazyServiceGetter(Services, "cookies", |
|
14 "@mozilla.org/cookieService;1", |
|
15 "nsICookieService"); |
|
16 XPCOMUtils.defineLazyServiceGetter(Services, "cookiemgr", |
|
17 "@mozilla.org/cookiemanager;1", |
|
18 "nsICookieManager2"); |
|
19 |
|
20 XPCOMUtils.defineLazyServiceGetter(Services, "etld", |
|
21 "@mozilla.org/network/effective-tld-service;1", |
|
22 "nsIEffectiveTLDService"); |
|
23 |
|
24 function do_check_throws(f, result, stack) |
|
25 { |
|
26 if (!stack) |
|
27 stack = Components.stack.caller; |
|
28 |
|
29 try { |
|
30 f(); |
|
31 } catch (exc) { |
|
32 if (exc.result == result) |
|
33 return; |
|
34 do_throw("expected result " + result + ", caught " + exc, stack); |
|
35 } |
|
36 do_throw("expected result " + result + ", none thrown", stack); |
|
37 } |
|
38 |
|
39 // Helper to step a generator function and catch a StopIteration exception. |
|
40 function do_run_generator(generator) |
|
41 { |
|
42 try { |
|
43 generator.next(); |
|
44 } catch (e) { |
|
45 if (e != StopIteration) |
|
46 do_throw("caught exception " + e, Components.stack.caller); |
|
47 } |
|
48 } |
|
49 |
|
50 // Helper to finish a generator function test. |
|
51 function do_finish_generator_test(generator) |
|
52 { |
|
53 do_execute_soon(function() { |
|
54 generator.close(); |
|
55 do_test_finished(); |
|
56 }); |
|
57 } |
|
58 |
|
59 function _observer(generator, topic) { |
|
60 Services.obs.addObserver(this, topic, false); |
|
61 |
|
62 this.generator = generator; |
|
63 this.topic = topic; |
|
64 } |
|
65 |
|
66 _observer.prototype = { |
|
67 observe: function (subject, topic, data) { |
|
68 do_check_eq(this.topic, topic); |
|
69 |
|
70 Services.obs.removeObserver(this, this.topic); |
|
71 |
|
72 // Continue executing the generator function. |
|
73 if (this.generator) |
|
74 do_run_generator(this.generator); |
|
75 |
|
76 this.generator = null; |
|
77 this.topic = null; |
|
78 } |
|
79 } |
|
80 |
|
81 // Close the cookie database. If a generator is supplied, it will be invoked |
|
82 // once the close is complete. |
|
83 function do_close_profile(generator, cleanse) { |
|
84 // Register an observer for db close. |
|
85 let obs = new _observer(generator, "cookie-db-closed"); |
|
86 |
|
87 // Close the db. |
|
88 let service = Services.cookies.QueryInterface(Ci.nsIObserver); |
|
89 service.observe(null, "profile-before-change", cleanse ? cleanse : ""); |
|
90 } |
|
91 |
|
92 // Load the cookie database. If a generator is supplied, it will be invoked |
|
93 // once the load is complete. |
|
94 function do_load_profile(generator) { |
|
95 // Register an observer for read completion. |
|
96 let obs = new _observer(generator, "cookie-db-read"); |
|
97 |
|
98 // Load the profile. |
|
99 let service = Services.cookies.QueryInterface(Ci.nsIObserver); |
|
100 service.observe(null, "profile-do-change", ""); |
|
101 } |
|
102 |
|
103 // Set a single session cookie using http and test the cookie count |
|
104 // against 'expected' |
|
105 function do_set_single_http_cookie(uri, channel, expected) { |
|
106 Services.cookies.setCookieStringFromHttp(uri, null, null, "foo=bar", null, channel); |
|
107 do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected); |
|
108 } |
|
109 |
|
110 // Set four cookies; with & without channel, http and non-http; and test |
|
111 // the cookie count against 'expected' after each set. |
|
112 function do_set_cookies(uri, channel, session, expected) { |
|
113 let suffix = session ? "" : "; max-age=1000"; |
|
114 |
|
115 // without channel |
|
116 Services.cookies.setCookieString(uri, null, "oh=hai" + suffix, null); |
|
117 do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[0]); |
|
118 // with channel |
|
119 Services.cookies.setCookieString(uri, null, "can=has" + suffix, channel); |
|
120 do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[1]); |
|
121 // without channel, from http |
|
122 Services.cookies.setCookieStringFromHttp(uri, null, null, "cheez=burger" + suffix, null, null); |
|
123 do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[2]); |
|
124 // with channel, from http |
|
125 Services.cookies.setCookieStringFromHttp(uri, null, null, "hot=dog" + suffix, null, channel); |
|
126 do_check_eq(Services.cookiemgr.countCookiesFromHost(uri.host), expected[3]); |
|
127 } |
|
128 |
|
129 function do_count_enumerator(enumerator) { |
|
130 let i = 0; |
|
131 while (enumerator.hasMoreElements()) { |
|
132 enumerator.getNext(); |
|
133 ++i; |
|
134 } |
|
135 return i; |
|
136 } |
|
137 |
|
138 function do_count_cookies() { |
|
139 return do_count_enumerator(Services.cookiemgr.enumerator); |
|
140 } |
|
141 |
|
142 // Helper object to store cookie data. |
|
143 function Cookie(name, |
|
144 value, |
|
145 host, |
|
146 path, |
|
147 expiry, |
|
148 lastAccessed, |
|
149 creationTime, |
|
150 isSession, |
|
151 isSecure, |
|
152 isHttpOnly) |
|
153 { |
|
154 this.name = name; |
|
155 this.value = value; |
|
156 this.host = host; |
|
157 this.path = path; |
|
158 this.expiry = expiry; |
|
159 this.lastAccessed = lastAccessed; |
|
160 this.creationTime = creationTime; |
|
161 this.isSession = isSession; |
|
162 this.isSecure = isSecure; |
|
163 this.isHttpOnly = isHttpOnly; |
|
164 |
|
165 let strippedHost = host.charAt(0) == '.' ? host.slice(1) : host; |
|
166 |
|
167 try { |
|
168 this.baseDomain = Services.etld.getBaseDomainFromHost(strippedHost); |
|
169 } catch (e) { |
|
170 if (e.result == Cr.NS_ERROR_HOST_IS_IP_ADDRESS || |
|
171 e.result == Cr.NS_ERROR_INSUFFICIENT_DOMAIN_LEVELS) |
|
172 this.baseDomain = strippedHost; |
|
173 } |
|
174 } |
|
175 |
|
176 // Object representing a database connection and associated statements. The |
|
177 // implementation varies depending on schema version. |
|
178 function CookieDatabaseConnection(file, schema) |
|
179 { |
|
180 // Manually generate a cookies.sqlite file with appropriate rows, columns, |
|
181 // and schema version. If it already exists, just set up our statements. |
|
182 let exists = file.exists(); |
|
183 |
|
184 this.db = Services.storage.openDatabase(file); |
|
185 this.schema = schema; |
|
186 if (!exists) |
|
187 this.db.schemaVersion = schema; |
|
188 |
|
189 switch (schema) { |
|
190 case 1: |
|
191 { |
|
192 if (!exists) { |
|
193 this.db.executeSimpleSQL( |
|
194 "CREATE TABLE moz_cookies ( \ |
|
195 id INTEGER PRIMARY KEY, \ |
|
196 name TEXT, \ |
|
197 value TEXT, \ |
|
198 host TEXT, \ |
|
199 path TEXT, \ |
|
200 expiry INTEGER, \ |
|
201 isSecure INTEGER, \ |
|
202 isHttpOnly INTEGER)"); |
|
203 } |
|
204 |
|
205 this.stmtInsert = this.db.createStatement( |
|
206 "INSERT INTO moz_cookies ( \ |
|
207 id, \ |
|
208 name, \ |
|
209 value, \ |
|
210 host, \ |
|
211 path, \ |
|
212 expiry, \ |
|
213 isSecure, \ |
|
214 isHttpOnly) \ |
|
215 VALUES ( \ |
|
216 :id, \ |
|
217 :name, \ |
|
218 :value, \ |
|
219 :host, \ |
|
220 :path, \ |
|
221 :expiry, \ |
|
222 :isSecure, \ |
|
223 :isHttpOnly)"); |
|
224 |
|
225 this.stmtDelete = this.db.createStatement( |
|
226 "DELETE FROM moz_cookies WHERE id = :id"); |
|
227 |
|
228 break; |
|
229 } |
|
230 |
|
231 case 2: |
|
232 { |
|
233 if (!exists) { |
|
234 this.db.executeSimpleSQL( |
|
235 "CREATE TABLE moz_cookies ( \ |
|
236 id INTEGER PRIMARY KEY, \ |
|
237 name TEXT, \ |
|
238 value TEXT, \ |
|
239 host TEXT, \ |
|
240 path TEXT, \ |
|
241 expiry INTEGER, \ |
|
242 lastAccessed INTEGER, \ |
|
243 isSecure INTEGER, \ |
|
244 isHttpOnly INTEGER)"); |
|
245 } |
|
246 |
|
247 this.stmtInsert = this.db.createStatement( |
|
248 "INSERT OR REPLACE INTO moz_cookies ( \ |
|
249 id, \ |
|
250 name, \ |
|
251 value, \ |
|
252 host, \ |
|
253 path, \ |
|
254 expiry, \ |
|
255 lastAccessed, \ |
|
256 isSecure, \ |
|
257 isHttpOnly) \ |
|
258 VALUES ( \ |
|
259 :id, \ |
|
260 :name, \ |
|
261 :value, \ |
|
262 :host, \ |
|
263 :path, \ |
|
264 :expiry, \ |
|
265 :lastAccessed, \ |
|
266 :isSecure, \ |
|
267 :isHttpOnly)"); |
|
268 |
|
269 this.stmtDelete = this.db.createStatement( |
|
270 "DELETE FROM moz_cookies WHERE id = :id"); |
|
271 |
|
272 this.stmtUpdate = this.db.createStatement( |
|
273 "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id"); |
|
274 |
|
275 break; |
|
276 } |
|
277 |
|
278 case 3: |
|
279 { |
|
280 if (!exists) { |
|
281 this.db.executeSimpleSQL( |
|
282 "CREATE TABLE moz_cookies ( \ |
|
283 id INTEGER PRIMARY KEY, \ |
|
284 baseDomain TEXT, \ |
|
285 name TEXT, \ |
|
286 value TEXT, \ |
|
287 host TEXT, \ |
|
288 path TEXT, \ |
|
289 expiry INTEGER, \ |
|
290 lastAccessed INTEGER, \ |
|
291 isSecure INTEGER, \ |
|
292 isHttpOnly INTEGER)"); |
|
293 |
|
294 this.db.executeSimpleSQL( |
|
295 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"); |
|
296 } |
|
297 |
|
298 this.stmtInsert = this.db.createStatement( |
|
299 "INSERT INTO moz_cookies ( \ |
|
300 id, \ |
|
301 baseDomain, \ |
|
302 name, \ |
|
303 value, \ |
|
304 host, \ |
|
305 path, \ |
|
306 expiry, \ |
|
307 lastAccessed, \ |
|
308 isSecure, \ |
|
309 isHttpOnly) \ |
|
310 VALUES ( \ |
|
311 :id, \ |
|
312 :baseDomain, \ |
|
313 :name, \ |
|
314 :value, \ |
|
315 :host, \ |
|
316 :path, \ |
|
317 :expiry, \ |
|
318 :lastAccessed, \ |
|
319 :isSecure, \ |
|
320 :isHttpOnly)"); |
|
321 |
|
322 this.stmtDelete = this.db.createStatement( |
|
323 "DELETE FROM moz_cookies WHERE id = :id"); |
|
324 |
|
325 this.stmtUpdate = this.db.createStatement( |
|
326 "UPDATE moz_cookies SET lastAccessed = :lastAccessed WHERE id = :id"); |
|
327 |
|
328 break; |
|
329 } |
|
330 |
|
331 case 4: |
|
332 { |
|
333 if (!exists) { |
|
334 this.db.executeSimpleSQL( |
|
335 "CREATE TABLE moz_cookies ( \ |
|
336 id INTEGER PRIMARY KEY, \ |
|
337 baseDomain TEXT, \ |
|
338 name TEXT, \ |
|
339 value TEXT, \ |
|
340 host TEXT, \ |
|
341 path TEXT, \ |
|
342 expiry INTEGER, \ |
|
343 lastAccessed INTEGER, \ |
|
344 creationTime INTEGER, \ |
|
345 isSecure INTEGER, \ |
|
346 isHttpOnly INTEGER \ |
|
347 CONSTRAINT moz_uniqueid UNIQUE (name, host, path))"); |
|
348 |
|
349 this.db.executeSimpleSQL( |
|
350 "CREATE INDEX moz_basedomain ON moz_cookies (baseDomain)"); |
|
351 |
|
352 this.db.executeSimpleSQL( |
|
353 "PRAGMA journal_mode = WAL"); |
|
354 } |
|
355 |
|
356 this.stmtInsert = this.db.createStatement( |
|
357 "INSERT INTO moz_cookies ( \ |
|
358 baseDomain, \ |
|
359 name, \ |
|
360 value, \ |
|
361 host, \ |
|
362 path, \ |
|
363 expiry, \ |
|
364 lastAccessed, \ |
|
365 creationTime, \ |
|
366 isSecure, \ |
|
367 isHttpOnly) \ |
|
368 VALUES ( \ |
|
369 :baseDomain, \ |
|
370 :name, \ |
|
371 :value, \ |
|
372 :host, \ |
|
373 :path, \ |
|
374 :expiry, \ |
|
375 :lastAccessed, \ |
|
376 :creationTime, \ |
|
377 :isSecure, \ |
|
378 :isHttpOnly)"); |
|
379 |
|
380 this.stmtDelete = this.db.createStatement( |
|
381 "DELETE FROM moz_cookies \ |
|
382 WHERE name = :name AND host = :host AND path = :path"); |
|
383 |
|
384 this.stmtUpdate = this.db.createStatement( |
|
385 "UPDATE moz_cookies SET lastAccessed = :lastAccessed \ |
|
386 WHERE name = :name AND host = :host AND path = :path"); |
|
387 |
|
388 break; |
|
389 } |
|
390 |
|
391 default: |
|
392 do_throw("unrecognized schemaVersion!"); |
|
393 } |
|
394 } |
|
395 |
|
396 CookieDatabaseConnection.prototype = |
|
397 { |
|
398 insertCookie: function(cookie) |
|
399 { |
|
400 if (!(cookie instanceof Cookie)) |
|
401 do_throw("not a cookie"); |
|
402 |
|
403 switch (this.schema) |
|
404 { |
|
405 case 1: |
|
406 this.stmtInsert.bindByName("id", cookie.creationTime); |
|
407 this.stmtInsert.bindByName("name", cookie.name); |
|
408 this.stmtInsert.bindByName("value", cookie.value); |
|
409 this.stmtInsert.bindByName("host", cookie.host); |
|
410 this.stmtInsert.bindByName("path", cookie.path); |
|
411 this.stmtInsert.bindByName("expiry", cookie.expiry); |
|
412 this.stmtInsert.bindByName("isSecure", cookie.isSecure); |
|
413 this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); |
|
414 break; |
|
415 |
|
416 case 2: |
|
417 this.stmtInsert.bindByName("id", cookie.creationTime); |
|
418 this.stmtInsert.bindByName("name", cookie.name); |
|
419 this.stmtInsert.bindByName("value", cookie.value); |
|
420 this.stmtInsert.bindByName("host", cookie.host); |
|
421 this.stmtInsert.bindByName("path", cookie.path); |
|
422 this.stmtInsert.bindByName("expiry", cookie.expiry); |
|
423 this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed); |
|
424 this.stmtInsert.bindByName("isSecure", cookie.isSecure); |
|
425 this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); |
|
426 break; |
|
427 |
|
428 case 3: |
|
429 this.stmtInsert.bindByName("id", cookie.creationTime); |
|
430 this.stmtInsert.bindByName("baseDomain", cookie.baseDomain); |
|
431 this.stmtInsert.bindByName("name", cookie.name); |
|
432 this.stmtInsert.bindByName("value", cookie.value); |
|
433 this.stmtInsert.bindByName("host", cookie.host); |
|
434 this.stmtInsert.bindByName("path", cookie.path); |
|
435 this.stmtInsert.bindByName("expiry", cookie.expiry); |
|
436 this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed); |
|
437 this.stmtInsert.bindByName("isSecure", cookie.isSecure); |
|
438 this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); |
|
439 break; |
|
440 |
|
441 case 4: |
|
442 this.stmtInsert.bindByName("baseDomain", cookie.baseDomain); |
|
443 this.stmtInsert.bindByName("name", cookie.name); |
|
444 this.stmtInsert.bindByName("value", cookie.value); |
|
445 this.stmtInsert.bindByName("host", cookie.host); |
|
446 this.stmtInsert.bindByName("path", cookie.path); |
|
447 this.stmtInsert.bindByName("expiry", cookie.expiry); |
|
448 this.stmtInsert.bindByName("lastAccessed", cookie.lastAccessed); |
|
449 this.stmtInsert.bindByName("creationTime", cookie.creationTime); |
|
450 this.stmtInsert.bindByName("isSecure", cookie.isSecure); |
|
451 this.stmtInsert.bindByName("isHttpOnly", cookie.isHttpOnly); |
|
452 break; |
|
453 |
|
454 default: |
|
455 do_throw("unrecognized schemaVersion!"); |
|
456 } |
|
457 |
|
458 do_execute_stmt(this.stmtInsert); |
|
459 }, |
|
460 |
|
461 deleteCookie: function(cookie) |
|
462 { |
|
463 if (!(cookie instanceof Cookie)) |
|
464 do_throw("not a cookie"); |
|
465 |
|
466 switch (this.db.schemaVersion) |
|
467 { |
|
468 case 1: |
|
469 case 2: |
|
470 case 3: |
|
471 this.stmtDelete.bindByName("id", cookie.creationTime); |
|
472 break; |
|
473 |
|
474 case 4: |
|
475 this.stmtDelete.bindByName("name", cookie.name); |
|
476 this.stmtDelete.bindByName("host", cookie.host); |
|
477 this.stmtDelete.bindByName("path", cookie.path); |
|
478 break; |
|
479 |
|
480 default: |
|
481 do_throw("unrecognized schemaVersion!"); |
|
482 } |
|
483 |
|
484 do_execute_stmt(this.stmtDelete); |
|
485 }, |
|
486 |
|
487 updateCookie: function(cookie) |
|
488 { |
|
489 if (!(cookie instanceof Cookie)) |
|
490 do_throw("not a cookie"); |
|
491 |
|
492 switch (this.db.schemaVersion) |
|
493 { |
|
494 case 1: |
|
495 do_throw("can't update a schema 1 cookie!"); |
|
496 |
|
497 case 2: |
|
498 case 3: |
|
499 this.stmtUpdate.bindByName("id", cookie.creationTime); |
|
500 this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed); |
|
501 break; |
|
502 |
|
503 case 4: |
|
504 this.stmtDelete.bindByName("name", cookie.name); |
|
505 this.stmtDelete.bindByName("host", cookie.host); |
|
506 this.stmtDelete.bindByName("path", cookie.path); |
|
507 this.stmtUpdate.bindByName("lastAccessed", cookie.lastAccessed); |
|
508 break; |
|
509 |
|
510 default: |
|
511 do_throw("unrecognized schemaVersion!"); |
|
512 } |
|
513 |
|
514 do_execute_stmt(this.stmtUpdate); |
|
515 }, |
|
516 |
|
517 close: function() |
|
518 { |
|
519 this.stmtInsert.finalize(); |
|
520 this.stmtDelete.finalize(); |
|
521 if (this.stmtUpdate) |
|
522 this.stmtUpdate.finalize(); |
|
523 this.db.close(); |
|
524 |
|
525 this.stmtInsert = null; |
|
526 this.stmtDelete = null; |
|
527 this.stmtUpdate = null; |
|
528 this.db = null; |
|
529 } |
|
530 } |
|
531 |
|
532 function do_get_cookie_file(profile) |
|
533 { |
|
534 let file = profile.clone(); |
|
535 file.append("cookies.sqlite"); |
|
536 return file; |
|
537 } |
|
538 |
|
539 // Count the cookies from 'host' in a database. If 'host' is null, count all |
|
540 // cookies. |
|
541 function do_count_cookies_in_db(connection, host) |
|
542 { |
|
543 let select = null; |
|
544 if (host) { |
|
545 select = connection.createStatement( |
|
546 "SELECT COUNT(1) FROM moz_cookies WHERE host = :host"); |
|
547 select.bindByName("host", host); |
|
548 } else { |
|
549 select = connection.createStatement( |
|
550 "SELECT COUNT(1) FROM moz_cookies"); |
|
551 } |
|
552 |
|
553 select.executeStep(); |
|
554 let result = select.getInt32(0); |
|
555 select.reset(); |
|
556 select.finalize(); |
|
557 return result; |
|
558 } |
|
559 |
|
560 // Execute 'stmt', ensuring that we reset it if it throws. |
|
561 function do_execute_stmt(stmt) |
|
562 { |
|
563 try { |
|
564 stmt.executeStep(); |
|
565 stmt.reset(); |
|
566 } catch (e) { |
|
567 stmt.reset(); |
|
568 throw e; |
|
569 } |
|
570 } |