--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -23,16 +23,20 @@ var gTrackedMessageManager = new Map();
// Maximum number of cookies/local storage key-value-pairs that can be sent
// over the wire to the client in one request.
const MAX_STORE_OBJECT_COUNT = 50;
// Delay for the batch job that sends the accumulated update packets to the
// client (ms).
const BATCH_DELAY = 200;
+// MAX_COOKIE_EXPIRY should be 2^63-1, but JavaScript can't handle that
+// precision.
+const MAX_COOKIE_EXPIRY = Math.pow(2, 62);
+
// A RegExp for characters that cannot appear in a file/directory name. This is
// used to sanitize the host name for indexed db to lookup whether the file is
// present in <profileDir>/storage/default/ location
var illegalFileNameCharacters = [
"[",
// Control characters \001 to \036
"\\x00-\\x24",
// Special characters
@@ -627,23 +631,62 @@ StorageActors.createActor({
case "reload":
this.storageActor.update("reloaded", "cookies", hosts);
break;
}
return null;
},
+ /**
+ * This method marks the table as editable.
+ *
+ * @return {Array}
+ * An array of column header ids.
+ */
+ getEditableFields: method(Task.async(function*() {
+ return [
+ "expires",
+ "creationTime",
+ "lastAccessed",
+ "value",
+ "isDomain",
+ "isSecure",
+ "isHttpOnly"
+ ];
+ }), {
+ request: {},
+ response: {
+ value: RetVal("json")
+ }
+ }),
+
+ /**
+ * Pass the cellEdit command from the content to the chrome process.
+ *
+ * @param {Object} data
+ * See cellEditInParent() for format details.
+ */
+ cellEdit: method(Task.async(function*(data) {
+ this.cellEditInParent(data);
+ }), {
+ request: {
+ data: Arg(0, "json"),
+ },
+ response: {}
+ }),
+
maybeSetupChildProcess: function() {
cookieHelpers.onCookieChanged = this.onCookieChanged.bind(this);
if (!DebuggerServer.isInChildProcess) {
this.getCookiesFromHost = cookieHelpers.getCookiesFromHost;
this.addCookieObservers = cookieHelpers.addCookieObservers;
this.removeCookieObservers = cookieHelpers.removeCookieObservers;
+ this.cellEditInParent = cookieHelpers.cellEditInParent;
return;
}
const { sendSyncMessage, addMessageListener } =
this.conn.parentMessageManager;
this.conn.setupInParent({
module: "devtools/server/actors/storage",
@@ -651,16 +694,18 @@ StorageActors.createActor({
});
this.getCookiesFromHost =
callParentProcess.bind(null, "getCookiesFromHost");
this.addCookieObservers =
callParentProcess.bind(null, "addCookieObservers");
this.removeCookieObservers =
callParentProcess.bind(null, "removeCookieObservers");
+ this.cellEditInParent =
+ callParentProcess.bind(null, "cellEditInParent");
addMessageListener("storage:storage-cookie-request-child",
cookieHelpers.handleParentRequest);
function callParentProcess(methodName, ...args) {
let reply = sendSyncMessage("storage:storage-cookie-request-parent", {
method: methodName,
args: args
@@ -690,22 +735,103 @@ var cookieHelpers = {
host = "";
}
let cookies = Services.cookies.getCookiesFromHost(host);
let store = [];
while (cookies.hasMoreElements()) {
let cookie = cookies.getNext().QueryInterface(Ci.nsICookie2);
- store.push(cookie);
+ let expiry = new Date(cookie.expiry * 1000);
+
+ // We only allow access to non-expired cookies. Expired cookies are not
+ // erased until their host and path are hit e.g. by loading a page that
+ // uses the expired cookie. When cookie.expiry * 1000 is a number too
+ // large to be used with the Date object we allow it as the date would be
+ // in the far distant future (see note on MAX_COOKIE_EXPIRY).
+ if (expiry > Date.now() || isNaN(expiry.getTime())) {
+ store.push(cookie);
+ }
}
return store;
},
+ /**
+ * Apply the results of a cell edit.
+ *
+ * @param {Object} data
+ * An object in the following format:
+ * {
+ * name: "optimizelyBuckets",
+ * path: "/",
+ * host: ".mozilla.org",
+ * expires: "Mon, 02 Jun 2025 12:37:37 GMT",
+ * creationTime: "Tue, 18 Nov 2014 16:21:18 GMT",
+ * lastAccessed: "Wed, 17 Feb 2016 10:06:23 GMT",
+ * value: "%7BHelloo%7D",
+ * isDomain: "true",
+ * isSecure: "false",
+ * isHttpOnly: "false",
+ * change: {
+ * field: "value",
+ * oldValue: "%7BHello%7D",
+ * newValue: "%7BHelloo%7D"
+ * }
+ * }
+ *
+ * The object is made from header ids and their accompanying values.
+ * The change object contains information about which value was
+ * changed.
+ */
+ cellEditInParent: function(data) {
+ let cookie = null;
+ let enumerator = Services.cookies.getCookiesFromHost(data.host);
+ while (enumerator.hasMoreElements()) {
+ cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
+ if (cookie.name === data.name && cookie.host === data.host) {
+ cookie = Object.assign({}, cookie);
+ break;
+ }
+ }
+
+ if (!cookie) {
+ return;
+ }
+
+ // Because our values are all strings we need to coerce them into true
+ // boolean values.
+ if ((data.change.field === "isSecure" ||
+ data.change.field === "isHttpOnly" ||
+ data.change.field === "isSession") &&
+ (data.change.newValue === "true" ||
+ data.change.newValue === "false")) {
+ data.change.newValue = data.change.newValue === "true";
+ }
+
+ // Apply changes.
+ cookie[data.change.field] = data.change.newValue;
+
+ // cookie.isSession is not always set correctly on session cookies so we
+ // need to trust cookie.expires instead.
+ cookie.isSession = !cookie.expires;
+
+ // Add the edited cookie.
+ Services.cookies.add(
+ cookie.host,
+ cookie.path,
+ cookie.name,
+ cookie.value,
+ cookie.isSecure,
+ cookie.isHttpOnly,
+ cookie.isSession,
+ cookie.isSession ? MAX_COOKIE_EXPIRY : cookie.expiry
+ );
+ },
+
addCookieObservers: function() {
Services.obs.addObserver(cookieHelpers, "cookie-changed", false);
return null;
},
removeCookieObservers: function() {
Services.obs.removeObserver(cookieHelpers, "cookie-changed", false);
return null;
@@ -735,16 +861,19 @@ var cookieHelpers = {
case "getCookiesFromHost":
let host = msg.data.args[0];
let cookies = cookieHelpers.getCookiesFromHost(host);
return JSON.stringify(cookies);
case "addCookieObservers":
return cookieHelpers.addCookieObservers();
case "removeCookieObservers":
return cookieHelpers.removeCookieObservers();
+ case "cellEditInParent":
+ let rowdata = JSON.parse(msg.data.args[0]);
+ return cookieHelpers.cellEditInParent(rowdata);
default:
console.error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD", msg.json.method);
throw new Error("ERR_DIRECTOR_PARENT_UNKNOWN_METHOD");
}
},
};
/**