1 /* Any copyright is dedicated to the Public Domain.
2 http://creativecommons.org/publicdomain/zero/1.0/ */
7 * Tests if Copy as cURL works.
10 const POST_PAYLOAD
= "Plaintext value as a payload";
12 add_task(async
function () {
13 const { tab
, monitor
} = await
initNetMonitor(HTTPS_CURL_URL
, {
16 // disable sending idempotency header for POST request
17 Services
.prefs
.setBoolPref("network.http.idempotencyKey.enabled", false);
18 info("Starting test... ");
20 // Different quote chars are used for Windows and POSIX
21 const QUOTE_WIN
= '"';
22 const QUOTE_POSIX
= "'";
24 const isWin
= Services
.appinfo
.OS
=== "WINNT";
25 const testData
= isWin
28 menuItemId
: "request-list-context-copy-as-curl-win",
29 data
: buildTestData(QUOTE_WIN
),
32 menuItemId
: "request-list-context-copy-as-curl-posix",
33 data
: buildTestData(QUOTE_POSIX
),
38 menuItemId
: "request-list-context-copy-as-curl",
39 data
: buildTestData(QUOTE_POSIX
),
43 await
testForPlatform(tab
, monitor
, testData
);
45 await
teardown(monitor
);
48 function buildTestData(QUOTE
) {
49 // Quote a string, escape the quotes inside the string
51 return QUOTE
+ str
.replace(new RegExp(QUOTE
, "g"), `\\${QUOTE}`) + QUOTE
;
54 // Header param is formatted as -H "Header: value" or -H 'Header: value'
56 return "-H " + quote(h
);
59 // Construct the expected command
60 const SIMPLE_BASE
= ["curl " + quote(HTTPS_SIMPLE_SJS
)];
61 const SLOW_BASE
= ["curl " + quote(HTTPS_SLOW_SJS
)];
64 header("User-Agent: " + navigator
.userAgent
),
65 header("Accept: */*"),
66 header("Accept-Language: " + navigator
.language
),
67 header("X-Custom-Header-1: Custom value"),
68 header("X-Custom-Header-2: 8.8.8.8"),
69 header("X-Custom-Header-3: Mon, 3 Mar 2014 11:11:11 GMT"),
70 header("Referer: " + HTTPS_CURL_URL
),
71 header("Connection: keep-alive"),
72 header("Pragma: no-cache"),
73 header("Cache-Control: no-cache"),
74 header("Sec-Fetch-Dest: empty"),
75 header("Sec-Fetch-Mode: cors"),
76 header("Sec-Fetch-Site: same-origin"),
79 const COOKIE_PARTIAL_RESULT
= [header("Cookie: bob=true; tom=cool")];
81 const POST_PARTIAL_RESULT
= [
84 "--data-raw " + quote(POST_PAYLOAD
),
85 header("Content-Type: text/plain;charset=UTF-8"),
87 const ORIGIN_RESULT
= [header("Origin: https://example.com")];
89 const HEAD_PARTIAL_RESULT
= ["-I"];
95 COOKIE_PARTIAL_RESULT
,
103 async
function testForPlatform(tab
, monitor
, testData
) {
104 // GET request, no cookies (first request)
105 await
performRequest("GET");
106 for (const test
of testData
) {
107 await
testClipboardContent(test
.menuItemId
, [
108 ...test
.data
.SIMPLE_BASE
,
109 ...test
.data
.BASE_RESULT
,
112 // Check to make sure it is still OK after we view the response (bug#1452442)
113 await
selectIndexAndWaitForSourceEditor(monitor
, 0);
114 for (const test
of testData
) {
115 await
testClipboardContent(test
.menuItemId
, [
116 ...test
.data
.SIMPLE_BASE
,
117 ...test
.data
.BASE_RESULT
,
121 // GET request, cookies set by previous response
122 await
performRequest("GET");
123 for (const test
of testData
) {
124 await
testClipboardContent(test
.menuItemId
, [
125 ...test
.data
.SIMPLE_BASE
,
126 ...test
.data
.BASE_RESULT
,
127 ...test
.data
.COOKIE_PARTIAL_RESULT
,
131 // Unfinished request (bug#1378464, bug#1420513)
132 const waitSlow
= waitForNetworkEvents(monitor
, 0);
133 await SpecialPowers
.spawn(
136 async
function (url
) {
137 content
.wrappedJSObject
.performRequest(url
, "GET", null);
141 for (const test
of testData
) {
142 await
testClipboardContent(test
.menuItemId
, [
143 ...test
.data
.SLOW_BASE
,
144 ...test
.data
.BASE_RESULT
,
145 ...test
.data
.COOKIE_PARTIAL_RESULT
,
150 await
performRequest("POST", POST_PAYLOAD
);
151 for (const test
of testData
) {
152 await
testClipboardContent(test
.menuItemId
, [
153 ...test
.data
.SIMPLE_BASE
,
154 ...test
.data
.BASE_RESULT
,
155 ...test
.data
.COOKIE_PARTIAL_RESULT
,
156 ...test
.data
.POST_PARTIAL_RESULT
,
157 ...test
.data
.ORIGIN_RESULT
,
162 await
performRequest("HEAD");
163 for (const test
of testData
) {
164 await
testClipboardContent(test
.menuItemId
, [
165 ...test
.data
.SIMPLE_BASE
,
166 ...test
.data
.BASE_RESULT
,
167 ...test
.data
.COOKIE_PARTIAL_RESULT
,
168 ...test
.data
.HEAD_PARTIAL_RESULT
,
172 async
function performRequest(method
, payload
) {
173 const waitRequest
= waitForNetworkEvents(monitor
, 1);
174 await SpecialPowers
.spawn(
178 url
: HTTPS_SIMPLE_SJS
,
183 async
function ({ url
, method_
, payload_
}) {
184 content
.wrappedJSObject
.performRequest(url
, method_
, payload_
);
190 async
function testClipboardContent(menuItemId
, expectedResult
) {
191 const { document
} = monitor
.panelWin
;
193 const items
= document
.querySelectorAll(".request-list-item");
194 const itemIndex
= items
.length
- 1;
195 EventUtils
.sendMouseEvent({ type
: "mousedown" }, items
[itemIndex
]);
196 EventUtils
.sendMouseEvent(
197 { type
: "contextmenu" },
198 document
.querySelectorAll(".request-list-item")[0]
201 /* Ensure that the copy as cURL option is always visible */
203 !!getContextMenuItem(monitor
, menuItemId
),
205 `The "Copy as cURL" context menu item "${menuItemId}" should not be hidden.`
208 await
waitForClipboardPromise(
209 async
function setup() {
210 await
selectContextMenuItem(monitor
, menuItemId
);
212 function validate(result
) {
213 if (typeof result
!== "string") {
217 // Different setups may produce the same command, but with the
218 // parameters in a different order in the commandline (which is fine).
219 // Here we confirm that the commands are the same even in that case.
221 // This monster regexp parses the command line into an array of arguments,
222 // recognizing quoted args with matching quotes and escaped quotes inside:
223 // [ "curl 'url'", "--standalone-arg", "-arg-with-quoted-string 'value\'s'" ]
224 const matchRe
= /[-A-Za-z1-9]+(?: ([\"'])(?:\\\1|.)*?\1)?/g;
226 const actual
= result
.match(matchRe
);
227 // Must begin with the same "curl 'URL'" segment
228 if (!actual
|| expectedResult
[0] != actual
[0]) {
232 // Must match each of the params in the middle (headers)
234 expectedResult
.length
=== actual
.length
&&
235 expectedResult
.some(param
=> actual
.includes(param
))
241 `Clipboard contains a cURL command for item ${itemIndex} by "${menuItemId}"`