Bug 1588114 - [remote] Implement Network.setCookie and Network.setCookies. r=remote-protocol-reviewers,maja_zf

Differential Revision: https://phabricator.services.mozilla.com/D62119

--HG--
extra : moz-landing-system : lando
This commit is contained in:
David Burns 2020-02-10 21:14:16 +00:00
Родитель 96552e75e7
Коммит 5942abaa97
6 изменённых файлов: 534 добавлений и 6 удалений

Просмотреть файл

@ -6,21 +6,24 @@
var EXPORTED_SYMBOLS = ["Network"]; var EXPORTED_SYMBOLS = ["Network"];
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import( const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm" "resource://gre/modules/XPCOMUtils.jsm"
); );
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]); XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { Domain } = ChromeUtils.import( const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm" "chrome://remote/content/domains/Domain.jsm"
); );
const { NetworkObserver } = ChromeUtils.import( const { NetworkObserver } = ChromeUtils.import(
"chrome://remote/content/domains/parent/network/NetworkObserver.jsm" "chrome://remote/content/domains/parent/network/NetworkObserver.jsm"
); );
const MAX_COOKIE_EXPIRY = Number.MAX_SAFE_INTEGER;
const LOAD_CAUSE_STRINGS = { const LOAD_CAUSE_STRINGS = {
[Ci.nsIContentPolicy.TYPE_INVALID]: "Invalid", [Ci.nsIContentPolicy.TYPE_INVALID]: "Invalid",
[Ci.nsIContentPolicy.TYPE_OTHER]: "Other", [Ci.nsIContentPolicy.TYPE_OTHER]: "Other",
@ -203,6 +206,134 @@ class Network extends Domain {
return { cookies }; return { cookies };
} }
/**
* Sets a cookie with the given cookie data.
*
* Note that it may overwrite equivalent cookies if they exist.
*
* @param {Object} cookie
* @param {string} name
* Cookie name.
* @param {string} value
* Cookie value.
* @param {string=} domain
* Cookie domain.
* @param {number=} expires
* Cookie expiration date, session cookie if not set.
* @param {boolean=} httpOnly
* True if cookie is http-only.
* @param {string=} path
* Cookie path.
* @param {string=} sameSite
* Cookie SameSite type.
* @param {boolean=} secure
* True if cookie is secure.
* @param {string=} url
* The request-URI to associate with the setting of the cookie.
* This value can affect the default domain and path values of the
* created cookie.
*
* @return {boolean}
* True if successfully set cookie.
*/
setCookie(cookie) {
if (typeof cookie.name != "string") {
throw new TypeError("name: string value expected");
}
if (typeof cookie.value != "string") {
throw new TypeError("value: string value expected");
}
if (
typeof cookie.url == "undefined" &&
typeof cookie.domain == "undefined"
) {
throw new TypeError(
"At least one of the url and domain needs to be specified"
);
}
// Retrieve host. Check domain first because it has precedence.
let hostname = cookie.domain || "";
let cookieURL;
if (hostname.length == 0) {
try {
cookieURL = new URL(cookie.url);
} catch (e) {
return { success: false };
}
if (!["http:", "https:"].includes(cookieURL.protocol)) {
throw new TypeError(`Invalid protocol ${cookieURL.protocol}`);
}
if (cookieURL.protocol == "https:") {
cookie.secure = true;
}
hostname = cookieURL.hostname;
}
if (typeof cookie.path == "undefined") {
cookie.path = "/";
}
let isSession = false;
if (typeof cookie.expires == "undefined") {
isSession = true;
cookie.expires = MAX_COOKIE_EXPIRY;
}
const sameSiteMap = new Map([
["None", Ci.nsICookie.SAMESITE_NONE],
["Lax", Ci.nsICookie.SAMESITE_LAX],
["Strict", Ci.nsICookie.SAMESITE_STRICT],
]);
let success = true;
try {
Services.cookies.add(
hostname,
cookie.path,
cookie.name,
cookie.value,
cookie.secure,
cookie.httpOnly || false,
isSession,
cookie.expires,
{} /* originAttributes */,
sameSiteMap.get(cookie.sameSite)
);
} catch (e) {
success = false;
}
return { success };
}
/**
* Sets given cookies.
*
* @param {Object} options
* @param {Array.<Cookie>} cookies
* Cookies to be set.
*/
setCookies(options = {}) {
const { cookies } = options;
if (!Array.isArray(cookies)) {
throw new TypeError("Invalid parameters (cookies: array expected)");
}
cookies.forEach(cookie => {
const { success } = this.setCookie(cookie);
if (!success) {
throw new Error("Invalid cookie fields");
}
});
}
/** /**
* Toggles ignoring cache for each request. If true, cache will not be used. * Toggles ignoring cache for each request. If true, cache will not be used.
* *

Просмотреть файл

@ -15,6 +15,8 @@ support-files =
[browser_getCookies.js] [browser_getCookies.js]
skip-if = (os == 'win' && os_version == '10.0' && ccov) # Bug 1605650 skip-if = (os == 'win' && os_version == '10.0' && ccov) # Bug 1605650
[browser_requestWillBeSent.js] [browser_requestWillBeSent.js]
[browser_setUserAgentOverride.js] [browser_setCookie.js]
[browser_setCookies.js]
[browser_setCacheDisabled.js] [browser_setCacheDisabled.js]
skip-if = true # Bug 1610382 skip-if = true # Bug 1610382
[browser_setUserAgentOverride.js]

Просмотреть файл

@ -105,7 +105,7 @@ add_task(async function expiry({ client }) {
const cookie = { const cookie = {
name: "foo", name: "foo",
value: "bar", value: "bar",
expires: date, expires: Math.floor(date.getTime() / 1000),
session: false, session: false,
}; };

Просмотреть файл

@ -0,0 +1,298 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const SJS_PATH = "/browser/remote/test/browser/network/sjs-cookies.sjs";
const DEFAULT_HOST = "example.org";
const ALT_HOST = "foo.example.org";
const SECURE_HOST = "example.com";
const DEFAULT_URL = `http://${DEFAULT_HOST}`;
add_task(async function failureWithoutArguments({ client }) {
const { Network } = client;
let errorThrown = false;
try {
await Network.setCookie();
} catch (e) {
errorThrown = true;
}
ok(errorThrown, "Fails without any arguments");
});
add_task(async function failureWithMissingNameAndValue({ client }) {
const { Network } = client;
let errorThrown = false;
try {
await Network.setCookie({
value: "bar",
domain: "example.org",
});
} catch (e) {
errorThrown = true;
}
ok(errorThrown, "Fails without name specified");
errorThrown = false;
try {
await Network.setCookie({
name: "foo",
domain: "example.org",
});
} catch (e) {
errorThrown = true;
}
ok(errorThrown, "Fails without value specified");
});
add_task(async function failureWithMissingDomainAndURL({ client }) {
const { Network } = client;
let errorThrown = false;
try {
await Network.setCookie({ name: "foo", value: "bar" });
} catch (e) {
errorThrown = true;
}
ok(errorThrown, "Fails without domain and URL specified");
});
add_task(async function setCookieWithDomain({ client }) {
const { Network } = client;
const cookie = {
name: "foo",
value: "bar",
domain: ALT_HOST,
};
try {
const { success } = await Network.setCookie(cookie);
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithEmptyDomain({ client }) {
const { Network } = client;
try {
const { success } = await Network.setCookie({
name: "foo",
value: "bar",
url: "",
});
ok(!success, "Cookie has not been set");
const cookies = getCookies();
is(cookies.length, 0, "No cookie has been found");
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithURL({ client }) {
const { Network } = client;
const cookie = {
name: "foo",
value: "bar",
domain: ALT_HOST,
};
try {
const { success } = await Network.setCookie({
name: cookie.name,
value: cookie.value,
url: `http://${ALT_HOST}`,
});
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithEmptyURL({ client }) {
const { Network } = client;
try {
const { success } = await Network.setCookie({
name: "foo",
value: "bar",
url: "",
});
ok(!success, "No cookie has been set");
const cookies = getCookies();
is(cookies.length, 0, "No cookie has been found");
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithDomainAndURL({ client }) {
const { Network } = client;
const cookie = {
name: "foo",
value: "bar",
domain: ALT_HOST,
};
try {
const { success } = await Network.setCookie({
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
url: `http://${DEFAULT_HOST}`,
});
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithHttpOnly({ client }) {
const { Network } = client;
const cookie = {
name: "foo",
value: "bar",
domain: DEFAULT_HOST,
httpOnly: true,
};
try {
const { success } = await Network.setCookie(cookie);
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithExpiry({ client }) {
const { Network } = client;
const tomorrow = Math.floor(Date.now() / 1000) + 60 * 60 * 24;
const cookie = {
name: "foo",
value: "bar",
domain: DEFAULT_HOST,
expires: tomorrow,
session: false,
};
try {
const { success } = await Network.setCookie(cookie);
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookieWithPath({ client }) {
const { Network } = client;
const cookie = {
name: "foo",
value: "bar",
domain: ALT_HOST,
path: SJS_PATH,
};
try {
const { success } = await Network.setCookie(cookie);
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function testAddSameSiteCookie({ client }) {
const { Network } = client;
for (const sameSite of ["None", "Lax", "Strict"]) {
console.log(`Check same site value: ${sameSite}`);
const cookie = {
name: "foo",
value: "bar",
domain: DEFAULT_HOST,
};
if (sameSite != "None") {
cookie.sameSite = sameSite;
}
try {
const { success } = await Network.setCookie({
name: cookie.name,
value: cookie.value,
domain: cookie.domain,
sameSite,
});
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
}
});
add_task(async function testAddSecureCookie({ client }) {
const { Network } = client;
const cookie = {
name: "foo",
value: "bar",
domain: "example.com",
secure: true,
};
try {
const { success } = await Network.setCookie({
name: cookie.name,
value: cookie.value,
url: `https://${SECURE_HOST}`,
});
ok(success, "Cookie has been set");
const cookies = getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
ok(cookies[0].secure, `Cookie for HTTPS is secure`);
} finally {
Services.cookies.removeAll();
}
});

Просмотреть файл

@ -0,0 +1,70 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
const ALT_HOST = "foo.example.org";
const DEFAULT_HOST = "example.org";
add_task(async function failureWithoutArguments({ client }) {
const { Network } = client;
let errorThrown = false;
try {
await Network.setCookies();
} catch (e) {
errorThrown = true;
}
ok(errorThrown, "Fails without any arguments");
});
add_task(async function setCookies({ client }) {
const { Network } = client;
const expected_cookies = [
{
name: "foo",
value: "bar",
domain: DEFAULT_HOST,
},
{
name: "user",
value: "password",
domain: ALT_HOST,
},
];
try {
await Network.setCookies({ cookies: expected_cookies });
const cookies = getCookies();
cookies.sort((a, b) => a.name.localeCompare(b.name));
is(cookies.length, expected_cookies.length, "Correct number of cookies");
assertCookie(cookies[0], expected_cookies[0]);
assertCookie(cookies[1], expected_cookies[1]);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function setCookiesWithInvalidField({ client }) {
const { Network } = client;
const cookies = [
{
name: "foo",
value: "bar",
domain: "",
},
];
let errorThrown = false;
try {
await Network.setCookies({ cookies });
} catch (e) {
errorThrown = true;
} finally {
Services.cookies.removeAll();
}
ok(errorThrown, "Fails with an invalid field");
});

Просмотреть файл

@ -29,8 +29,7 @@ function assertCookie(cookie, expected = {}) {
value, value,
domain, domain,
path, path,
// If expires is set, convert from milliseconds to seconds expires,
expires: expires > 0 ? Math.floor(expires.getTime() / 1000) : -1,
size, size,
httpOnly, httpOnly,
secure, secure,
@ -43,3 +42,31 @@ function assertCookie(cookie, expected = {}) {
Assert.deepEqual(cookie, expectedCookie); Assert.deepEqual(cookie, expectedCookie);
} }
function getCookies() {
return Services.cookies.cookies.map(cookie => {
const data = {
name: cookie.name,
value: cookie.value,
domain: cookie.host,
path: cookie.path,
expires: cookie.isSession ? -1 : cookie.expiry,
// The size is the combined length of both the cookie name and value
size: cookie.name.length + cookie.value.length,
httpOnly: cookie.isHttpOnly,
secure: cookie.isSecure,
session: cookie.isSession,
};
if (cookie.sameSite) {
const sameSiteMap = new Map([
[Ci.nsICookie.SAMESITE_LAX, "Lax"],
[Ci.nsICookie.SAMESITE_STRICT, "Strict"],
]);
data.sameSite = sameSiteMap.get(cookie.sameSite);
}
return data;
});
}