Bug 1270096 - Netmonitor request replay: disable CORS checks, set request headers accurately r?Honza
MozReview-Commit-ID: Ew7zPkrhOHd
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -109,16 +109,18 @@ skip-if = (os == 'linux' && e10s && debu
[browser_net_post-data-02.js]
[browser_net_post-data-03.js]
[browser_net_prefs-and-l10n.js]
[browser_net_prefs-reload.js]
[browser_net_raw_headers.js]
[browser_net_reload-button.js]
[browser_net_reload-markers.js]
[browser_net_req-resp-bodies.js]
+[browser_net_resend_cors.js]
+[browser_net_resend_headers.js]
[browser_net_resend.js]
[browser_net_security-details.js]
[browser_net_security-error.js]
[browser_net_security-icon-click.js]
[browser_net_security-redirect.js]
[browser_net_security-state.js]
[browser_net_security-tab-deselect.js]
[browser_net_security-tab-visibility.js]
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -4,16 +4,17 @@
"use strict";
/**
* Tests if resending a request works.
*/
const ADD_QUERY = "t1=t2";
const ADD_HEADER = "Test-header: true";
+const ADD_UA_HEADER = "User-Agent: Custom-Agent";
const ADD_POSTDATA = "&t3=t4";
add_task(function* () {
let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
info("Starting test... ");
let { panelWin } = monitor;
let { document, EVENTS, NetMonitorView } = panelWin;
@@ -124,16 +125,21 @@ add_task(function* () {
let headersFocus = once(headers, "focus", false);
headers.focus();
yield headersFocus;
// add a header
type(["VK_RETURN"]);
type(ADD_HEADER);
+ // add a User-Agent header, to check if default headers can be modified
+ // (there will be two of them, first gets overwritten by the second)
+ type(["VK_RETURN"]);
+ type(ADD_UA_HEADER);
+
let postData = document.getElementById("custom-postdata-value");
let postFocus = once(postData, "focus", false);
postData.focus();
yield postFocus;
// add to POST data
type(ADD_POSTDATA);
}
@@ -144,16 +150,19 @@ add_task(function* () {
function testSentRequest(data, origData) {
is(data.method, origData.method, "correct method in sent request");
is(data.url, origData.url + "&" + ADD_QUERY, "correct url in sent request");
let { headers } = data.requestHeaders;
let hasHeader = headers.some(h => `${h.name}: ${h.value}` == ADD_HEADER);
ok(hasHeader, "new header added to sent request");
+ let hasUAHeader = headers.some(h => `${h.name}: ${h.value}` == ADD_UA_HEADER);
+ ok(hasUAHeader, "User-Agent header added to sent request");
+
is(data.requestPostData.postData.text,
origData.requestPostData.postData.text + ADD_POSTDATA,
"post data added to sent request");
}
function type(string) {
for (let ch of string) {
EventUtils.synthesizeKey(ch, {}, panelWin);
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if resending a CORS request avoids the security checks and doesn't send
+ * a preflight OPTIONS request (bug 1270096 and friends)
+ */
+
+add_task(function* () {
+ let { tab, monitor } = yield initNetMonitor(CORS_URL);
+ info("Starting test... ");
+
+ let { EVENTS, NetMonitorView } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let requestUrl = "http://test1.example.com" + CORS_SJS_PATH;
+
+ info("Waiting for OPTIONS, then POST");
+ let wait = waitForNetworkEvents(monitor, 1, 1);
+ yield ContentTask.spawn(tab.linkedBrowser, requestUrl, function* (url) {
+ content.wrappedJSObject.performRequests(url, "triggering/preflight", "post-data");
+ });
+ yield wait;
+
+ const METHODS = ["OPTIONS", "POST"];
+
+ // Check the requests that were sent
+ for (let [i, method] of METHODS.entries()) {
+ let { attachment } = RequestsMenu.getItemAtIndex(i);
+ is(attachment.method, method, `The ${method} request has the right method`);
+ is(attachment.url, requestUrl, `The ${method} request has the right URL`);
+ }
+
+ // Resend both requests without modification. Wait for resent OPTIONS, then POST.
+ // POST is supposed to have no preflight OPTIONS request this time (CORS is disabled)
+ let onRequests = waitForNetworkEvents(monitor, 1, 1);
+ for (let [i, method] of METHODS.entries()) {
+ let item = RequestsMenu.getItemAtIndex(i);
+
+ info(`Selecting the ${method} request (at index ${i})`);
+ let onUpdate = monitor.panelWin.once(EVENTS.TAB_UPDATED);
+ RequestsMenu.selectedItem = item;
+ yield onUpdate;
+
+ info("Cloning the selected request into a custom clone");
+ let onPopulate = monitor.panelWin.once(EVENTS.CUSTOMREQUESTVIEW_POPULATED);
+ RequestsMenu.cloneSelectedRequest();
+ yield onPopulate;
+
+ info("Sending the cloned request (without change)");
+ RequestsMenu.sendCustomRequest();
+ }
+
+ info("Waiting for both resent requests");
+ yield onRequests;
+
+ // Check the resent requests
+ for (let [i, method] of METHODS.entries()) {
+ let index = i + 2;
+ let item = RequestsMenu.getItemAtIndex(index).attachment;
+ is(item.method, method, `The ${method} request has the right method`);
+ is(item.url, requestUrl, `The ${method} request has the right URL`);
+ is(item.status, 200, `The ${method} response has the right status`);
+
+ if (method === "POST") {
+ is(item.requestPostData.postData.text, "post-data",
+ "The POST request has the right POST data");
+ // eslint-disable-next-line mozilla/no-cpows-in-tests
+ is(item.responseContent.content.text, "Access-Control-Allow-Origin: *",
+ "The POST response has the right content");
+ }
+ }
+
+ info("Finishing the test");
+ return teardown(monitor);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test if custom request headers are not ignored (bug 1270096 and friends)
+ */
+
+add_task(function* () {
+ let { monitor } = yield initNetMonitor(SIMPLE_SJS);
+ info("Starting test... ");
+
+ let { NetMonitorView, NetMonitorController } = monitor.panelWin;
+ let { RequestsMenu } = NetMonitorView;
+
+ RequestsMenu.lazyUpdate = false;
+
+ let requestUrl = SIMPLE_SJS;
+ let requestHeaders = [
+ { name: "Host", value: "fakehost.example.com" },
+ { name: "User-Agent", value: "Testzilla" },
+ { name: "Referer", value: "http://example.com/referrer" },
+ { name: "Accept", value: "application/jarda"},
+ { name: "Accept-Encoding", value: "compress, identity, funcoding" },
+ { name: "Accept-Language", value: "cs-CZ" }
+ ];
+
+ let wait = waitForNetworkEvents(monitor, 0, 1);
+ NetMonitorController.webConsoleClient.sendHTTPRequest({
+ url: requestUrl,
+ method: "POST",
+ headers: requestHeaders,
+ body: "Hello"
+ });
+ yield wait;
+
+ let { attachment } = RequestsMenu.getItemAtIndex(0);
+ is(attachment.method, "POST", "The request has the right method");
+ is(attachment.url, requestUrl, "The request has the right URL");
+
+ for (let { name, value } of attachment.requestHeaders.headers) {
+ info(`Request header: ${name}: ${value}`);
+ }
+
+ function hasRequestHeader(name, value) {
+ let { headers } = attachment.requestHeaders;
+ return headers.some(h => h.name === name && h.value === value);
+ }
+
+ function hasNotRequestHeader(name) {
+ let { headers } = attachment.requestHeaders;
+ return headers.every(h => h.name !== name);
+ }
+
+ for (let { name, value } of requestHeaders) {
+ ok(hasRequestHeader(name, value), `The ${name} header has the right value`);
+ }
+
+ // Check that the Cookie header was not added silently (i.e., that the request is
+ // anonymous.
+ for (let name of ["Cookie"]) {
+ ok(hasNotRequestHeader(name), `The ${name} header is not present`);
+ }
+
+ return teardown(monitor);
+});
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -18,16 +18,17 @@ const ErrorDocs = require("devtools/serv
loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "StackTraceCollector", "devtools/shared/webconsole/network-monitor", true);
loader.lazyRequireGetter(this, "events", "sdk/event/core");
loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
loader.lazyRequireGetter(this, "JSPropertyProvider", "devtools/shared/webconsole/js-property-provider", true);
loader.lazyRequireGetter(this, "Parser", "resource://devtools/shared/Parser.jsm", true);
+loader.lazyRequireGetter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm", true);
for (let name of ["WebConsoleUtils", "ConsoleServiceListener",
"ConsoleAPIListener", "addWebConsoleCommands",
"ConsoleReflowListener", "CONSOLE_WORKER_IDS"]) {
Object.defineProperty(this, name, {
get: function (prop) {
if (prop == "WebConsoleUtils") {
prop = "Utils";
@@ -1558,33 +1559,56 @@ WebConsoleActor.prototype =
actor = new NetworkEventActor(this);
this._actorPool.addActor(actor);
return actor;
},
/**
* Send a new HTTP request from the target's window.
*
- * @param object aMessage
+ * @param object message
* Object with 'request' - the HTTP request details.
*/
- onSendHTTPRequest: function WCA_onSendHTTPRequest(aMessage)
- {
- let details = aMessage.request;
+ onSendHTTPRequest(message) {
+ let { url, method, headers, body } = message.request;
+
+ // Set the loadingNode and loadGroup to the target document - otherwise the
+ // request won't show up in the opened netmonitor.
+ let doc = this.window.document;
+
+ let channel = NetUtil.newChannel({
+ uri: NetUtil.newURI(url),
+ loadingNode: doc,
+ securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER
+ });
+
+ channel.QueryInterface(Ci.nsIHttpChannel);
- // send request from target's window
- let request = new this.window.XMLHttpRequest();
- request.open(details.method, details.url, true);
+ channel.loadGroup = doc.documentLoadGroup;
+ channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE |
+ Ci.nsIRequest.INHIBIT_CACHING |
+ Ci.nsIRequest.LOAD_ANONYMOUS;
+
+ channel.requestMethod = method;
+
+ for (let {name, value} of headers) {
+ channel.setRequestHeader(name, value, false);
+ }
- for (let {name, value} of details.headers) {
- request.setRequestHeader(name, value);
+ if (body) {
+ channel.QueryInterface(Ci.nsIUploadChannel2);
+ let bodyStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ bodyStream.setData(body, body.length);
+ channel.explicitSetUploadStream(bodyStream, null, -1, method, false);
}
- request.send(details.body);
- let channel = request.channel.QueryInterface(Ci.nsIHttpChannel);
+ NetUtil.asyncFetch(channel, () => {});
+
let actor = this.getNetworkEventActor(channel.channelId);
// map channel to actor so we can associate future events with it
this._netEvents.set(channel.channelId, actor);
return {
from: this.actorID,
eventActor: actor.grip()