Source code
Revision control
Copy as Markdown
Other Tools
Test Info: Errors
- This test gets skipped with pattern: http3 OR http2
- This test failed 1 times in the preceding 30 days. quicksearch this test
- Manifest: devtools/client/netmonitor/test/browser.toml
/* Any copyright is dedicated to the Public Domain.
"use strict";
/**
* Tests Curl Utils functionality.
*/
const {
Curl,
CurlUtils,
} = require("resource://devtools/client/shared/curl.js");
add_task(async function () {
const { tab, monitor } = await initNetMonitor(HTTPS_CURL_UTILS_URL, {
requestCount: 1,
});
info("Starting test... ");
const { store, windowRequire, connector } = monitor.panelWin;
const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
const { getSortedRequests } = windowRequire(
"devtools/client/netmonitor/src/selectors/index"
);
const { getLongString, requestData } = connector;
store.dispatch(Actions.batchEnable(false));
const wait = waitForNetworkEvents(monitor, 6);
await SpecialPowers.spawn(
tab.linkedBrowser,
[HTTPS_SIMPLE_SJS],
async function (url) {
content.wrappedJSObject.performRequests(url);
}
);
await wait;
const requests = {
get: getSortedRequests(store.getState())[0],
post: getSortedRequests(store.getState())[1],
postJson: getSortedRequests(store.getState())[2],
patch: getSortedRequests(store.getState())[3],
multipart: getSortedRequests(store.getState())[4],
multipartForm: getSortedRequests(store.getState())[5],
};
let data = await createCurlData(requests.get, getLongString, requestData);
testFindHeader(data);
data = await createCurlData(requests.post, getLongString, requestData);
testIsUrlEncodedRequest(data);
testWritePostDataTextParams(data);
testWriteEmptyPostDataTextParams(data);
testDataArgumentOnGeneratedCommand(data);
data = await createCurlData(requests.patch, getLongString, requestData);
testWritePostDataTextParams(data);
testDataArgumentOnGeneratedCommand(data);
data = await createCurlData(requests.postJson, getLongString, requestData);
testDataEscapeOnGeneratedCommand(data);
data = await createCurlData(requests.multipart, getLongString, requestData);
testIsMultipartRequest(data);
testGetMultipartBoundary(data);
testMultiPartHeaders(data);
testRemoveBinaryDataFromMultipartText(data);
data = await createCurlData(
requests.multipartForm,
getLongString,
requestData
);
testMultiPartHeaders(data);
testGetHeadersFromMultipartText({
postDataText: "Content-Type: text/plain\r\n\r\n",
});
if (Services.appinfo.OS != "WINNT") {
testEscapeStringPosix();
} else {
testEscapeStringWin();
}
await teardown(monitor);
});
function testIsUrlEncodedRequest(data) {
const isUrlEncoded = CurlUtils.isUrlEncodedRequest(data);
ok(isUrlEncoded, "Should return true for url encoded requests.");
}
function testIsMultipartRequest(data) {
const isMultipart = CurlUtils.isMultipartRequest(data);
ok(isMultipart, "Should return true for multipart/form-data requests.");
}
function testFindHeader(data) {
const { headers } = data;
const hostName = CurlUtils.findHeader(headers, "Host");
const requestedWithLowerCased = CurlUtils.findHeader(
headers,
"x-requested-with"
);
const doesNotExist = CurlUtils.findHeader(headers, "X-Does-Not-Exist");
is(
hostName,
"example.com",
"Header with name 'Host' should be found in the request array."
);
is(
requestedWithLowerCased,
"XMLHttpRequest",
"The search should be case insensitive."
);
is(doesNotExist, null, "Should return null when a header is not found.");
}
function testMultiPartHeaders(data) {
const { headers } = data;
const contentType = CurlUtils.findHeader(headers, "Content-Type");
ok(
contentType.startsWith("multipart/form-data; boundary="),
"Multi-part content type header is present in headers array"
);
}
function testWritePostDataTextParams(data) {
const params = CurlUtils.writePostDataTextParams(data.postDataText);
is(
params,
"param1=value1¶m2=value2¶m3=value3",
"Should return a serialized representation of the request parameters"
);
}
function testWriteEmptyPostDataTextParams() {
const params = CurlUtils.writePostDataTextParams(null);
is(params, "", "Should return a empty string when no parameters provided");
}
function testDataArgumentOnGeneratedCommand(data) {
const curlCommand = Curl.generateCommand(data);
ok(
curlCommand.includes("--data-raw"),
"Should return a curl command with --data-raw"
);
}
function testDataEscapeOnGeneratedCommand(data) {
const paramsWin = `--data-raw "{""param1"":""value1"",""param2"":""value2""}"`;
const paramsPosix = `--data-raw '{"param1":"value1","param2":"value2"}'`;
let curlCommand = Curl.generateCommand(data, "WINNT");
ok(
curlCommand.includes(paramsWin),
"Should return a curl command with --data-raw escaped for Windows systems"
);
curlCommand = Curl.generateCommand(data, "Linux");
ok(
curlCommand.includes(paramsPosix),
"Should return a curl command with --data-raw escaped for Posix systems"
);
}
function testGetMultipartBoundary(data) {
const boundary = CurlUtils.getMultipartBoundary(data);
ok(
/-{3,}\w+/.test(boundary),
"A boundary string should be found in a multipart request."
);
}
function testRemoveBinaryDataFromMultipartText(data) {
const generatedBoundary = CurlUtils.getMultipartBoundary(data);
const text = data.postDataText;
const binaryRemoved = CurlUtils.removeBinaryDataFromMultipartText(
text,
generatedBoundary
);
const boundary = "--" + generatedBoundary;
const EXPECTED_POSIX_RESULT = [
"$'",
boundary,
"\\r\\n",
'Content-Disposition: form-data; name="param1"',
"\\r\\n\\r\\n",
"value1",
"\\r\\n",
boundary,
"\\r\\n",
'Content-Disposition: form-data; name="file"; filename="filename.png"',
"\\r\\n",
"Content-Type: image/png",
"\\r\\n\\r\\n",
boundary + "--",
"\\r\\n",
"'",
].join("");
const EXPECTED_WIN_RESULT = [
'"',
boundary,
'"^\u000d\u000A\u000d\u000A"',
'Content-Disposition: form-data; name=""param1""',
'"^\u000d\u000A\u000d\u000A""^\u000d\u000A\u000d\u000A"',
"value1",
'"^\u000d\u000A\u000d\u000A"',
boundary,
'"^\u000d\u000A\u000d\u000A"',
'Content-Disposition: form-data; name=""file""; filename=""filename.png""',
'"^\u000d\u000A\u000d\u000A"',
"Content-Type: image/png",
'"^\u000d\u000A\u000d\u000A""^\u000d\u000A\u000d\u000A"',
boundary + "--",
'"^\u000d\u000A\u000d\u000A"',
'"',
].join("");
if (Services.appinfo.OS != "WINNT") {
is(
CurlUtils.escapeStringPosix(binaryRemoved),
EXPECTED_POSIX_RESULT,
"The mulitpart request payload should not contain binary data."
);
} else {
is(
CurlUtils.escapeStringWin(binaryRemoved),
EXPECTED_WIN_RESULT,
"WinNT: The mulitpart request payload should not contain binary data."
);
}
}
function testGetHeadersFromMultipartText(data) {
const headers = CurlUtils.getHeadersFromMultipartText(data.postDataText);
ok(Array.isArray(headers), "Should return an array.");
ok(!!headers.length, "There should exist at least one request header.");
is(
headers[0].name,
"Content-Type",
"The first header name should be 'Content-Type'."
);
}
function testEscapeStringPosix() {
const surroundedWithQuotes = "A simple string";
is(
CurlUtils.escapeStringPosix(surroundedWithQuotes),
"'A simple string'",
"The string should be surrounded with single quotes."
);
const singleQuotes = "It's unusual to put crickets in your coffee.";
is(
CurlUtils.escapeStringPosix(singleQuotes),
"$'It\\'s unusual to put crickets in your coffee.'",
"Single quotes should be escaped."
);
const escapeChar = "'!ls:q:gs|ls|;ping 8.8.8.8;|";
is(
CurlUtils.escapeStringPosix(escapeChar),
"$'\\'\\041ls:q:gs|ls|;ping 8.8.8.8;|'",
"'!' should be escaped."
);
const newLines = "Line 1\r\nLine 2\u000d\u000ALine3";
is(
CurlUtils.escapeStringPosix(newLines),
"$'Line 1\\r\\nLine 2\\r\\nLine3'",
"Newlines should be escaped."
);
const controlChars = "\u0007 \u0009 \u000C \u001B";
is(
CurlUtils.escapeStringPosix(controlChars),
"$'\\x07 \\x09 \\x0c \\x1b'",
"Control characters should be escaped."
);
// æ ø ü ß ö é
const extendedAsciiChars =
"\xc3\xa6 \xc3\xb8 \xc3\xbc \xc3\x9f \xc3\xb6 \xc3\xa9";
is(
CurlUtils.escapeStringPosix(extendedAsciiChars),
"$'\\xc3\\xa6 \\xc3\\xb8 \\xc3\\xbc \\xc3\\x9f \\xc3\\xb6 \\xc3\\xa9'",
"Character codes outside of the decimal range 32 - 126 should be escaped."
);
}
function testEscapeStringWin() {
const surroundedWithDoubleQuotes = "A simple string";
is(
CurlUtils.escapeStringWin(surroundedWithDoubleQuotes),
'"A simple string"',
"The string should be surrounded with double quotes."
);
const doubleQuotes = 'Quote: "Time is an illusion. Lunchtime doubly so."';
is(
CurlUtils.escapeStringWin(doubleQuotes),
'"Quote: ""Time is an illusion. Lunchtime doubly so."""',
"Double quotes should be escaped."
);
const percentSigns = "%TEMP% %@foo% %2XX% %_XX% %?XX%";
is(
CurlUtils.escapeStringWin(percentSigns),
'"^%^TEMP^% ^%^@foo^% ^%^2XX^% ^%^_XX^% ^%?XX^%"',
"Percent signs should be escaped."
);
const backslashes = "\\A simple string\\";
is(
CurlUtils.escapeStringWin(backslashes),
'"\\\\A simple string\\\\"',
"Backslashes should be escaped."
);
const newLines = "line1\r\nline2\r\rline3\n\nline4";
is(
CurlUtils.escapeStringWin(newLines),
'"line1"^\r\n\r\n"line2"^\r\n\r\n""^\r\n\r\n"line3"^\r\n\r\n""^\r\n\r\n"line4"',
"Newlines should be escaped."
);
const dollarSignCommand = "$(calc.exe)";
is(
CurlUtils.escapeStringWin(dollarSignCommand),
'"\\$(calc.exe)"',
"Dollar sign should be escaped."
);
const tickSignCommand = "`$(calc.exe)";
is(
CurlUtils.escapeStringWin(tickSignCommand),
'"\\`\\$(calc.exe)"',
"Both the tick and dollar signs should be escaped."
);
const evilCommand = `query=evil\r\rcmd" /c timeout /t 3 & calc.exe\r\r`;
is(
CurlUtils.escapeStringWin(evilCommand),
'"query=evil"^\r\n\r\n""^\r\n\r\n"cmd"" /c timeout /t 3 & calc.exe"^\r\n\r\n""^\r\n\r\n""',
"The evil command is escaped properly"
);
}
async function createCurlData(selected, getLongString, requestData) {
const { id, url, method, httpVersion } = selected;
// Create a sanitized object for the Curl command generator.
const data = {
url,
method,
headers: [],
httpVersion,
postDataText: null,
};
const requestHeaders = await requestData(id, "requestHeaders");
// Fetch header values.
for (const { name, value } of requestHeaders.headers) {
const text = await getLongString(value);
data.headers.push({ name, value: text });
}
const requestPostData = await requestData(id, "requestPostData");
// Fetch the request payload.
if (requestPostData) {
const postData = requestPostData.postData.text;
data.postDataText = await getLongString(postData);
}
return data;
}