mirror of
https://github.com/classilla/tenfourfox.git
synced 2024-10-10 13:23:42 +00:00
268 lines
7.7 KiB
JavaScript
268 lines
7.7 KiB
JavaScript
|
"use strict";
|
||
|
|
||
|
const { interfaces: Ci, utils: Cu } = Components;
|
||
|
Cu.import("resource://gre/modules/ExtensionUtils.jsm");
|
||
|
var {
|
||
|
EventManager,
|
||
|
runSafe,
|
||
|
} = ExtensionUtils;
|
||
|
|
||
|
// Cookies from private tabs currently can't be enumerated.
|
||
|
var DEFAULT_STORE = "firefox-default";
|
||
|
|
||
|
function convert(cookie) {
|
||
|
let result = {
|
||
|
name: cookie.name,
|
||
|
value: cookie.value,
|
||
|
domain: cookie.host,
|
||
|
hostOnly: !cookie.isDomain,
|
||
|
path: cookie.path,
|
||
|
secure: cookie.isSecure,
|
||
|
httpOnly: cookie.isHttpOnly,
|
||
|
session: cookie.isSession,
|
||
|
storeId: DEFAULT_STORE,
|
||
|
};
|
||
|
|
||
|
if (!cookie.isSession) {
|
||
|
result.expirationDate = cookie.expiry;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
function* query(detailsIn, props) {
|
||
|
// Different callers want to filter on different properties. |props|
|
||
|
// tells us which ones they're interested in.
|
||
|
let details = {};
|
||
|
props.map(property => {
|
||
|
if (detailsIn[property] !== null) {
|
||
|
details[property] = detailsIn[property];
|
||
|
}
|
||
|
});
|
||
|
|
||
|
// We can use getCookiesFromHost for faster searching.
|
||
|
let enumerator;
|
||
|
if ("url" in details) {
|
||
|
try {
|
||
|
let uri = Services.io.newURI(details.url, null, null);
|
||
|
enumerator = Services.cookies.getCookiesFromHost(uri.host);
|
||
|
} catch (ex) {
|
||
|
// This often happens for about: URLs
|
||
|
return;
|
||
|
}
|
||
|
} else if ("domain" in details) {
|
||
|
enumerator = Services.cookies.getCookiesFromHost(details.domain);
|
||
|
} else {
|
||
|
enumerator = Services.cookies.enumerator;
|
||
|
}
|
||
|
|
||
|
// Based on nsCookieService::GetCookieStringInternal
|
||
|
function matches(cookie) {
|
||
|
function domainMatches(host) {
|
||
|
return cookie.rawHost == host || (cookie.isDomain && host.endsWith(cookie.host));
|
||
|
}
|
||
|
|
||
|
function pathMatches(path) {
|
||
|
// Calculate cookie path length, excluding trailing '/'.
|
||
|
let length = cookie.path.length;
|
||
|
if (cookie.path.endsWith("/")) {
|
||
|
length -= 1;
|
||
|
}
|
||
|
|
||
|
// If the path is shorter than the cookie path, don't send it back.
|
||
|
if (!path.startsWith(cookie.path.substring(0, length))) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
let pathDelimiter = ["/", "?", "#", ";"];
|
||
|
if (path.length > length && !pathDelimiter.includes(path.charAt(length))) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// "Restricts the retrieved cookies to those that would match the given URL."
|
||
|
if ("url" in details) {
|
||
|
let uri = Services.io.newURI(details.url, null, null);
|
||
|
|
||
|
if (!domainMatches(uri.host)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (cookie.isSecure && uri.scheme != "https") {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!pathMatches(uri.path)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ("name" in details && details.name != cookie.name) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// "Restricts the retrieved cookies to those whose domains match or are subdomains of this one."
|
||
|
if ("domain" in details) {
|
||
|
if (cookie.rawHost != details.domain &&
|
||
|
!cookie.rawHost.endsWith("." + details.domain)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// "Restricts the retrieved cookies to those whose path exactly matches this string.""
|
||
|
if ("path" in details && details.path != cookie.path) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ("secure" in details && details.secure != cookie.isSecure) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ("session" in details && details.session != cookie.isSession) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if ("storeId" in details && details.storeId != DEFAULT_STORE) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
while (enumerator.hasMoreElements()) {
|
||
|
let cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
|
||
|
if (matches(cookie)) {
|
||
|
yield cookie;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
extensions.registerSchemaAPI("cookies", "cookies", (extension, context) => {
|
||
|
let self = {
|
||
|
cookies: {
|
||
|
get: function(details, callback) {
|
||
|
// FIXME: We don't sort by length of path and creation time.
|
||
|
for (let cookie of query(details, ["url", "name", "storeId"])) {
|
||
|
runSafe(context, callback, convert(cookie));
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Found no match.
|
||
|
runSafe(context, callback, null);
|
||
|
},
|
||
|
|
||
|
getAll: function(details, callback) {
|
||
|
let allowed = ["url", "name", "domain", "path", "secure", "session", "storeId"];
|
||
|
let result = [];
|
||
|
for (let cookie of query(details, allowed)) {
|
||
|
result.push(convert(cookie));
|
||
|
}
|
||
|
|
||
|
runSafe(context, callback, result);
|
||
|
},
|
||
|
|
||
|
set: function(details, callback) {
|
||
|
let uri = Services.io.newURI(details.url, null, null);
|
||
|
|
||
|
let domain;
|
||
|
if (details.domain !== null) {
|
||
|
domain = "." + details.domain;
|
||
|
} else {
|
||
|
domain = uri.host; // "If omitted, the cookie becomes a host-only cookie."
|
||
|
}
|
||
|
|
||
|
let path;
|
||
|
if (details.path !== null) {
|
||
|
path = details.path;
|
||
|
} else {
|
||
|
// Chrome seems to trim the path after the last slash.
|
||
|
// /x/abc/ddd == /x/abc
|
||
|
// /xxxx?abc == /
|
||
|
// We always have at least one slash.
|
||
|
let index = uri.path.slice(1).lastIndexOf("/");
|
||
|
if (index == -1) {
|
||
|
path = "/";
|
||
|
} else {
|
||
|
path = uri.path.slice(0, index + 1); // This removes the last slash.
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let name = details.name !== null ? details.name : "";
|
||
|
let value = details.value !== null ? details.value : "";
|
||
|
let secure = details.secure !== null ? details.secure : false;
|
||
|
let httpOnly = details.httpOnly !== null ? details.httpOnly : false;
|
||
|
let isSession = details.expirationDate === null;
|
||
|
let expiry = isSession ? 0 : details.expirationDate;
|
||
|
// Ingore storeID.
|
||
|
|
||
|
Services.cookies.add(domain, path, name, value, secure, httpOnly, isSession, expiry);
|
||
|
if (callback) {
|
||
|
self.cookies.get(details, callback);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
remove: function(details, callback) {
|
||
|
for (let cookie of query(details, ["url", "name", "storeId"])) {
|
||
|
Services.cookies.remove(cookie.host, cookie.name, cookie.path, false);
|
||
|
if (callback) {
|
||
|
runSafe(context, callback, {
|
||
|
url: details.url,
|
||
|
name: details.name,
|
||
|
storeId: DEFAULT_STORE,
|
||
|
});
|
||
|
}
|
||
|
// Todo: could there be multiple per subdomain?
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (callback) {
|
||
|
runSafe(context, callback, null);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getAllCookieStores: function(callback) {
|
||
|
// Todo: list all the tabIds for non-private tabs
|
||
|
runSafe(context, callback, [{id: DEFAULT_STORE, tabIds: []}]);
|
||
|
},
|
||
|
|
||
|
onChanged: new EventManager(context, "cookies.onChanged", fire => {
|
||
|
let observer = (subject, topic, data) => {
|
||
|
let notify = (removed, cookie, cause) => {
|
||
|
fire({removed, cookie: convert(cookie.QueryInterface(Ci.nsICookie2)), cause});
|
||
|
};
|
||
|
|
||
|
// We do our best effort here to map the incompatible states.
|
||
|
switch (data) {
|
||
|
case "deleted":
|
||
|
notify(true, subject, "explicit");
|
||
|
break;
|
||
|
case "added":
|
||
|
notify(false, subject, "explicit");
|
||
|
break;
|
||
|
case "changed":
|
||
|
notify(false, subject, "overwrite");
|
||
|
break;
|
||
|
case "batch-deleted":
|
||
|
for (let i = 0; i < subject.length; subject++) {
|
||
|
let cookie = subject.queryElementAt(i, Ci.nsICookie2);
|
||
|
if (!cookie.isSession && cookie.expiry < Date.now()) {
|
||
|
notify(true, cookie, "expired");
|
||
|
} else {
|
||
|
notify(true, cookie, "evicted");
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
Services.obs.addObserver(observer, "cookie-changed", false);
|
||
|
return () => Services.obs.removeObserver(observer, "cookie-changed");
|
||
|
}).api(),
|
||
|
},
|
||
|
};
|
||
|
|
||
|
return self;
|
||
|
});
|