Bug 1590098 - [remote] Implement basic support for Network.getCookies. r=remote-protocol-reviewers,ato,maja_zf

This patch adds basic support for retrieving cookies,
which means that it returns the cookies for the currently
active target.

Hereby it has the following limitations:

1. It does not walk the frame tree, and as such only returns
the cookies from the top-level frame. Support for that will
be added once frames can correctly be handled, which means
once support for the JSWindowActor API has been landed.

2. The "urls" parameter is not supported because it is
unclear right now what it actually does. More investigation
is necessary before any implementation can happen.

3. There is no support for the file:// protocol yet.

4. Dot domains aren't taken care of yet.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Henrik Skupin 2019-12-20 19:38:05 +00:00
Родитель 3036d033c3
Коммит 2dd64e92cd
4 изменённых файлов: 368 добавлений и 1 удалений

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

@ -6,6 +6,14 @@
var EXPORTED_SYMBOLS = ["Network"];
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
XPCOMUtils.defineLazyGlobalGetters(this, ["URL"]);
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { Domain } = ChromeUtils.import(
"chrome://remote/content/domains/Domain.jsm"
);
@ -75,6 +83,73 @@ class Network extends Domain {
this.enabled = false;
}
/**
* Returns all browser cookies for the current URL.
*
* @param {Object} options
* @param {Array<string>=} urls
* The list of URLs for which applicable cookies will be fetched.
* Defaults to the currently open URL.
*
* @return {Array<Cookie>}
* Array of cookie objects.
*/
// https://cs.chromium.org/chromium/src/content/browser/devtools/protocol/network_handler.cc?type=cs&q=+ComputeCookieURLs&sq=package:chromium&g=0&l=1115
async getCookies(options = {}) {
// Bug 1605354 - Add support for options.urls
const urls = [this.session.target.url];
const cookies = [];
for (let url of urls) {
url = new URL(url);
const secureProtocol = ["https:", "wss:"].includes(url.protocol);
const cookiesFound = Services.cookies.getCookiesWithOriginAttributes(
JSON.stringify({}),
url.hostname
);
for (const cookie of cookiesFound) {
// Reject secure cookies for non-secure protocols
if (cookie.isSecure && !secureProtocol) {
continue;
}
// Reject cookies which do not match the given path
if (!url.pathname.startsWith(cookie.path)) {
continue;
}
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);
}
cookies.push(data);
}
}
return { cookies };
}
/**
* Allows overriding user agent with the given string.
*

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

@ -3,8 +3,12 @@ tags = remote
subsuite = remote
prefs = remote.enabled=true
support-files =
!/remote/test/browser/chrome-remote-interface.js
!/remote/test/browser/head.js
head.js
doc_requestWillBeSent.html
file_requestWillBeSent.js
head.js
sjs-cookies.sjs
[browser_getCookies.js]
[browser_requestWillBeSent.js]

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

@ -0,0 +1,244 @@
/* 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 = "http://example.org";
const ALT_HOST = "http://example.net";
const SECURE_HOST = "https://example.com";
const DEFAULT_URL = `${DEFAULT_HOST}${SJS_PATH}`;
add_task(async function noCookiesWhenNoneAreSet({ Network }) {
const { cookies } = await Network.getCookies({ urls: [DEFAULT_HOST] });
is(cookies.length, 0, "No cookies have been found");
});
add_task(async function noCookiesForPristineContext({ Network }) {
await loadURL(DEFAULT_URL);
try {
const { cookies } = await Network.getCookies();
is(cookies.length, 0, "No cookies have been found");
} finally {
Services.cookies.removeAll();
}
});
add_task(async function allCookiesFromHostWithPort({ Network }) {
const PORT_URL = `${DEFAULT_HOST}:8000${SJS_PATH}?name=id&value=1`;
await loadURL(PORT_URL);
const cookie = {
name: "id",
value: "1",
};
try {
const { cookies } = await Network.getCookies();
is(cookies.length, 1, "All cookies have been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function allCookiesFromCurrentURL({ Network }) {
await loadURL(`${ALT_HOST}${SJS_PATH}?name=user&value=password`);
await loadURL(`${DEFAULT_URL}?name=foo&value=bar`);
await loadURL(`${DEFAULT_URL}?name=user&value=password`);
const cookie1 = { name: "foo", value: "bar", domain: "example.org" };
const cookie2 = { name: "user", value: "password", domain: "example.org" };
try {
const { cookies } = await Network.getCookies();
cookies.sort((a, b) => a.name.localeCompare(b.name));
is(cookies.length, 2, "All cookies have been found");
assertCookie(cookies[0], cookie1);
assertCookie(cookies[1], cookie2);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function secure({ Network }) {
await loadURL(`${SECURE_HOST}${SJS_PATH}?name=foo&value=bar&secure`);
const cookie = {
name: "foo",
value: "bar",
domain: "example.com",
secure: true,
};
try {
// Cookie returned for secure protocols
let result = await Network.getCookies();
is(result.cookies.length, 1, "The secure cookie has been found");
assertCookie(result.cookies[0], cookie);
// For unsecure protocols no secure cookies are returned
await loadURL(DEFAULT_URL);
result = await Network.getCookies();
is(result.cookies.length, 0, "No secure cookies have been found");
} finally {
Services.cookies.removeAll();
}
});
add_task(async function expiry({ Network }) {
const date = new Date();
date.setDate(date.getDate() + 3);
const encodedDate = encodeURI(date.toUTCString());
await loadURL(`${DEFAULT_URL}?name=foo&value=bar&expiry=${encodedDate}`);
const cookie = {
name: "foo",
value: "bar",
expires: date,
session: false,
};
try {
const { cookies } = await Network.getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function session({ Network }) {
await loadURL(`${DEFAULT_URL}?name=foo&value=bar`);
const cookie = {
name: "foo",
value: "bar",
expiry: -1,
session: true,
};
try {
const { cookies } = await Network.getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function path({ Network }) {
const PATH = "/browser/remote/test/browser/";
const PARENT_PATH = "/browser/remote/test/";
await loadURL(`${DEFAULT_URL}?name=foo&value=bar&path=${PATH}`);
const cookie = {
name: "foo",
value: "bar",
path: PATH,
};
try {
console.log("Check exact path");
await loadURL(`${DEFAULT_HOST}${PATH}`);
let result = await Network.getCookies();
is(result.cookies.length, 1, "A single cookie has been found");
assertCookie(result.cookies[0], cookie);
console.log("Check sub path");
await loadURL(`${DEFAULT_HOST}${SJS_PATH}`);
result = await Network.getCookies();
is(result.cookies.length, 1, "A single cookie has been found");
assertCookie(result.cookies[0], cookie);
console.log("Check parent path");
await loadURL(`${DEFAULT_HOST}${PARENT_PATH}`);
result = await Network.getCookies();
is(result.cookies.length, 0, "No cookies have been found");
console.log("Check non matching path");
await loadURL(`${DEFAULT_HOST}/foo/bar`);
result = await Network.getCookies();
is(result.cookies.length, 0, "No cookies have been found");
} finally {
Services.cookies.removeAll();
}
});
add_task(async function httpOnly({ Network }) {
await loadURL(`${DEFAULT_URL}?name=foo&value=bar&httpOnly`);
const cookie = {
name: "foo",
value: "bar",
httpOnly: true,
};
try {
const { cookies } = await Network.getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
});
add_task(async function sameSite({ Network }) {
for (const value of ["Lax", "Strict"]) {
console.log(`Test cookie with sameSite=${value}`);
await loadURL(`${DEFAULT_URL}?name=foo&value=bar&sameSite=${value}`);
const cookie = {
name: "foo",
value: "bar",
sameSite: value,
};
try {
const { cookies } = await Network.getCookies();
is(cookies.length, 1, "A single cookie has been found");
assertCookie(cookies[0], cookie);
} finally {
Services.cookies.removeAll();
}
}
});
function assertCookie(cookie, expected = {}) {
const {
name = "",
value = "",
domain = "example.org",
path = "/",
expires = -1,
size = name.length + value.length,
httpOnly = false,
secure = false,
session = true,
sameSite,
} = expected;
const expectedCookie = {
name,
value,
domain,
path,
// If expires is set, convert from milliseconds to seconds
expires: expires > 0 ? Math.floor(expires.getTime() / 1000) : -1,
size,
httpOnly,
secure,
session,
};
if (sameSite) {
expectedCookie.sameSite = sameSite;
}
Assert.deepEqual(cookie, expectedCookie);
}

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

@ -0,0 +1,44 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
// eslint-disable-next-line mozilla/reject-importGlobalProperties
Cu.importGlobalProperties(["URLSearchParams"]);
function handleRequest(request, response) {
const queryString = new URLSearchParams(request.queryString);
response.setStatusLine(request.httpVersion, 200, "OK");
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
if (queryString.has("name") && queryString.has("value")) {
const name = queryString.get("name");
const value = queryString.get("value");
const path = queryString.get("path") || "/";
const expiry = queryString.get("expiry");
const httpOnly = queryString.has("httpOnly");
const secure = queryString.has("secure");
const sameSite = queryString.get("sameSite");
let cookie = `${name}=${value}; Path=${path}`;
if (expiry) {
cookie += `; Expires=${expiry}`;
}
if (httpOnly) {
cookie += "; HttpOnly";
}
if (sameSite != undefined) {
cookie += `; sameSite=${sameSite}`;
}
if (secure) {
cookie += "; Secure";
}
response.setHeader("Set-Cookie", cookie, true);
response.write(`Set cookie: ${cookie}`);
}
}