Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Errors

/* Any copyright is dedicated to the Public Domain.
"use strict";
/**
* Tests if Copy as cURL works.
*/
const POST_PAYLOAD = "Plaintext value as a payload";
add_task(async function () {
const { tab, monitor } = await initNetMonitor(HTTPS_CURL_URL, {
requestCount: 1,
});
info("Starting test... ");
// Different quote chars are used for Windows and POSIX
const QUOTE_WIN = '"';
const QUOTE_POSIX = "'";
const isWin = Services.appinfo.OS === "WINNT";
const testData = isWin
? [
{
menuItemId: "request-list-context-copy-as-curl-win",
data: buildTestData(QUOTE_WIN),
},
{
menuItemId: "request-list-context-copy-as-curl-posix",
data: buildTestData(QUOTE_POSIX),
},
]
: [
{
menuItemId: "request-list-context-copy-as-curl",
data: buildTestData(QUOTE_POSIX),
},
];
await testForPlatform(tab, monitor, testData);
await teardown(monitor);
});
function buildTestData(QUOTE) {
// Quote a string, escape the quotes inside the string
function quote(str) {
return QUOTE + str.replace(new RegExp(QUOTE, "g"), `\\${QUOTE}`) + QUOTE;
}
// Header param is formatted as -H "Header: value" or -H 'Header: value'
function header(h) {
return "-H " + quote(h);
}
// Construct the expected command
const SIMPLE_BASE = ["curl " + quote(HTTPS_SIMPLE_SJS)];
const SLOW_BASE = ["curl " + quote(HTTPS_SLOW_SJS)];
const BASE_RESULT = [
"--compressed",
header("User-Agent: " + navigator.userAgent),
header("Accept: */*"),
header("Accept-Language: " + navigator.language),
header("X-Custom-Header-1: Custom value"),
header("X-Custom-Header-2: 8.8.8.8"),
header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
header("Referer: " + HTTPS_CURL_URL),
header("Connection: keep-alive"),
header("Pragma: no-cache"),
header("Cache-Control: no-cache"),
header("Sec-Fetch-Dest: empty"),
header("Sec-Fetch-Mode: cors"),
header("Sec-Fetch-Site: same-origin"),
];
const COOKIE_PARTIAL_RESULT = [header("Cookie: bob=true; tom=cool")];
const POST_PARTIAL_RESULT = [
"-X",
"POST",
"--data-raw " + quote(POST_PAYLOAD),
header("Content-Type: text/plain;charset=UTF-8"),
];
const ORIGIN_RESULT = [header("Origin: https://example.com")];
const HEAD_PARTIAL_RESULT = ["-I"];
return {
SIMPLE_BASE,
SLOW_BASE,
BASE_RESULT,
COOKIE_PARTIAL_RESULT,
POST_PAYLOAD,
POST_PARTIAL_RESULT,
ORIGIN_RESULT,
HEAD_PARTIAL_RESULT,
};
}
async function testForPlatform(tab, monitor, testData) {
// GET request, no cookies (first request)
await performRequest("GET");
for (const test of testData) {
await testClipboardContent(test.menuItemId, [
...test.data.SIMPLE_BASE,
...test.data.BASE_RESULT,
]);
}
// Check to make sure it is still OK after we view the response (bug#1452442)
await selectIndexAndWaitForSourceEditor(monitor, 0);
for (const test of testData) {
await testClipboardContent(test.menuItemId, [
...test.data.SIMPLE_BASE,
...test.data.BASE_RESULT,
]);
}
// GET request, cookies set by previous response
await performRequest("GET");
for (const test of testData) {
await testClipboardContent(test.menuItemId, [
...test.data.SIMPLE_BASE,
...test.data.BASE_RESULT,
...test.data.COOKIE_PARTIAL_RESULT,
]);
}
// Unfinished request (bug#1378464, bug#1420513)
const waitSlow = waitForNetworkEvents(monitor, 0);
await SpecialPowers.spawn(
tab.linkedBrowser,
[HTTPS_SLOW_SJS],
async function (url) {
content.wrappedJSObject.performRequest(url, "GET", null);
}
);
await waitSlow;
for (const test of testData) {
await testClipboardContent(test.menuItemId, [
...test.data.SLOW_BASE,
...test.data.BASE_RESULT,
...test.data.COOKIE_PARTIAL_RESULT,
]);
}
// POST request
await performRequest("POST", POST_PAYLOAD);
for (const test of testData) {
await testClipboardContent(test.menuItemId, [
...test.data.SIMPLE_BASE,
...test.data.BASE_RESULT,
...test.data.COOKIE_PARTIAL_RESULT,
...test.data.POST_PARTIAL_RESULT,
...test.data.ORIGIN_RESULT,
]);
}
// HEAD request
await performRequest("HEAD");
for (const test of testData) {
await testClipboardContent(test.menuItemId, [
...test.data.SIMPLE_BASE,
...test.data.BASE_RESULT,
...test.data.COOKIE_PARTIAL_RESULT,
...test.data.HEAD_PARTIAL_RESULT,
]);
}
async function performRequest(method, payload) {
const waitRequest = waitForNetworkEvents(monitor, 1);
await SpecialPowers.spawn(
tab.linkedBrowser,
[
{
url: HTTPS_SIMPLE_SJS,
method_: method,
payload_: payload,
},
],
async function ({ url, method_, payload_ }) {
content.wrappedJSObject.performRequest(url, method_, payload_);
}
);
await waitRequest;
}
async function testClipboardContent(menuItemId, expectedResult) {
const { document } = monitor.panelWin;
const items = document.querySelectorAll(".request-list-item");
const itemIndex = items.length - 1;
EventUtils.sendMouseEvent({ type: "mousedown" }, items[itemIndex]);
EventUtils.sendMouseEvent(
{ type: "contextmenu" },
document.querySelectorAll(".request-list-item")[0]
);
/* Ensure that the copy as cURL option is always visible */
is(
!!getContextMenuItem(monitor, menuItemId),
true,
`The "Copy as cURL" context menu item "${menuItemId}" should not be hidden.`
);
await waitForClipboardPromise(
async function setup() {
await selectContextMenuItem(monitor, menuItemId);
},
function validate(result) {
if (typeof result !== "string") {
return false;
}
// Different setups may produce the same command, but with the
// parameters in a different order in the commandline (which is fine).
// Here we confirm that the commands are the same even in that case.
// This monster regexp parses the command line into an array of arguments,
// recognizing quoted args with matching quotes and escaped quotes inside:
// [ "curl 'url'", "--standalone-arg", "-arg-with-quoted-string 'value\'s'" ]
const matchRe = /[-A-Za-z1-9]+(?: ([\"'])(?:\\\1|.)*?\1)?/g;
const actual = result.match(matchRe);
// Must begin with the same "curl 'URL'" segment
if (!actual || expectedResult[0] != actual[0]) {
return false;
}
// Must match each of the params in the middle (headers)
return (
expectedResult.length === actual.length &&
expectedResult.some(param => actual.includes(param))
);
}
);
info(
`Clipboard contains a cURL command for item ${itemIndex} by "${menuItemId}"`
);
}
}