Bug 1475391 - Add mapping for CORS error types to MDN pages; r=bgrins.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Nicolas Chevobbe 2018-08-03 06:40:36 +00:00
parent 497a22d1d5
commit 4f34541b99
4 changed files with 352 additions and 11 deletions

View File

@ -270,6 +270,7 @@ subsuite = clipboard
[browser_webconsole_context_menu_object_in_sidebar.js]
[browser_webconsole_context_menu_open_url.js]
[browser_webconsole_context_menu_store_as_global.js]
[browser_webconsole_cors_errors.js]
[browser_webconsole_csp_ignore_reflected_xss_message.js]
[browser_webconsole_csp_violation.js]
[browser_webconsole_cspro.js]

View File

@ -0,0 +1,180 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
// Ensure that the different CORS error are logged to the console with the appropriate
// "Learn more" link.
"use strict";
const TEST_URI = "http://example.com/browser/devtools/client/webconsole/test/mochitest/test-network-request.html";
const BASE_CORS_ERROR_URL = "https://developer.mozilla.org/docs/Web/HTTP/CORS/Errors/";
const BASE_CORS_ERROR_URL_PARAMS = new URLSearchParams({
utm_source: "devtools",
utm_medium: "firefox-cors-errors",
utm_campaign: "default",
});
add_task(async function() {
await pushPref("devtools.webconsole.filter.netxhr", true);
const hud = await openNewTabAndConsole(TEST_URI);
let onCorsMessage;
let message;
info(`Setting "content.cors.disable" to true to test CORSDisabled message`);
await pushPref("content.cors.disable", true);
onCorsMessage = waitForMessage(hud, "Reason: CORS disabled");
makeFaultyCorsCall("CORSDisabled");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSDisabled");
await pushPref("content.cors.disable", false);
info("Test CORSPreflightDidNotSucceed");
onCorsMessage = waitForMessage(hud, `CORS preflight channel did not succeed`);
makeFaultyCorsCall("CORSPreflightDidNotSucceed");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSPreflightDidNotSucceed");
info("Test CORS did not succeed");
onCorsMessage = waitForMessage(hud, "Reason: CORS request did not succeed");
makeFaultyCorsCall("CORSDidNotSucceed");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSDidNotSucceed");
info("Test CORSExternalRedirectNotAllowed");
onCorsMessage = waitForMessage(hud,
"Reason: CORS request external redirect not allowed");
makeFaultyCorsCall("CORSExternalRedirectNotAllowed");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSExternalRedirectNotAllowed");
info("Test CORSMissingAllowOrigin");
onCorsMessage = waitForMessage(hud,
`Reason: CORS header ${quote("Access-Control-Allow-Origin")} missing`);
makeFaultyCorsCall("CORSMissingAllowOrigin");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSMissingAllowOrigin");
info("Test CORSMultipleAllowOriginNotAllowed");
onCorsMessage = waitForMessage(hud,
`Reason: Multiple CORS header ${quote("Access-Control-Allow-Origin")} not allowed`);
makeFaultyCorsCall("CORSMultipleAllowOriginNotAllowed");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSMultipleAllowOriginNotAllowed");
info("Test CORSAllowOriginNotMatchingOrigin");
onCorsMessage = waitForMessage(hud, `Reason: CORS header ` +
`${quote("Access-Control-Allow-Origin")} does not match ${quote("mochi.test")}`);
makeFaultyCorsCall("CORSAllowOriginNotMatchingOrigin");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSAllowOriginNotMatchingOrigin");
info("Test CORSNotSupportingCredentials");
onCorsMessage = waitForMessage(hud, `Reason: Credential is not supported if the CORS ` +
`header ${quote("Access-Control-Allow-Origin")} is ${quote("*")}`);
makeFaultyCorsCall("CORSNotSupportingCredentials");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSNotSupportingCredentials");
info("Test CORSMethodNotFound");
onCorsMessage = waitForMessage(hud, `Reason: Did not find method in CORS header ` +
`${quote("Access-Control-Allow-Methods")}`);
makeFaultyCorsCall("CORSMethodNotFound");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSMethodNotFound");
info("Test CORSMissingAllowCredentials");
onCorsMessage = waitForMessage(hud, `Reason: expected ${quote("true")} in CORS ` +
`header ${quote("Access-Control-Allow-Credentials")}`);
makeFaultyCorsCall("CORSMissingAllowCredentials");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSMissingAllowCredentials");
info("Test CORSInvalidAllowMethod");
onCorsMessage = waitForMessage(hud, `Reason: invalid token ${quote("xyz;")} in CORS ` +
`header ${quote("Access-Control-Allow-Methods")}`);
makeFaultyCorsCall("CORSInvalidAllowMethod");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSInvalidAllowMethod");
info("Test CORSInvalidAllowHeader");
onCorsMessage = waitForMessage(hud, `Reason: invalid token ${quote("xyz;")} in CORS ` +
`header ${quote("Access-Control-Allow-Headers")}`);
makeFaultyCorsCall("CORSInvalidAllowHeader");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSInvalidAllowHeader");
info("Test CORSMissingAllowHeaderFromPreflight");
onCorsMessage = waitForMessage(hud, `Reason: missing token ${quote("xyz")} in CORS ` +
`header ${quote("Access-Control-Allow-Headers")} from CORS preflight channel`);
makeFaultyCorsCall("CORSMissingAllowHeaderFromPreflight");
message = await onCorsMessage;
await checkCorsMessage(message, "CORSMissingAllowHeaderFromPreflight");
// See Bug 1480671.
// XXX: how to make Origin to not be included in the request ?
// onCorsMessage = waitForMessage(hud,
// `Reason: CORS header ${quote("Origin")} cannot be added`);
// makeFaultyCorsCall("CORSOriginHeaderNotAdded");
// message = await onCorsMessage;
// await checkCorsMessage(message, "CORSOriginHeaderNotAdded");
// See Bug 1480672.
// XXX: Failing with another error: Console message: Security Error: Content at
// http://example.com/browser/devtools/client/webconsole/test/mochitest/test-network-request.html
// may not load or link to file:///Users/nchevobbe/Projects/mozilla-central/devtools/client/webconsole/test/mochitest/sjs_cors-test-server.sjs.
// info("Test CORSRequestNotHttp");
// onCorsMessage = waitForMessage(hud, "Reason: CORS request not http");
// const dir = getChromeDir(getResolvedURI(gTestPath));
// dir.append("sjs_cors-test-server.sjs");
// makeFaultyCorsCall("CORSRequestNotHttp", Services.io.newFileURI(dir).spec);
// message = await onCorsMessage;
// await checkCorsMessage(message, "CORSRequestNotHttp");
});
async function checkCorsMessage(message, category) {
const node = message.node;
ok(node.classList.contains("warn"), "The cors message has the expected classname");
const learnMoreLink = node.querySelector(".learn-more-link");
ok(learnMoreLink, "There is a Learn more link displayed");
const linkSimulation = await simulateLinkClick(learnMoreLink);
is(linkSimulation.link, getCategoryUrl(category),
"Click on the link opens the expected page");
}
function makeFaultyCorsCall(errorCategory, corsUrl) {
ContentTask.spawn(gBrowser.selectedBrowser, [errorCategory, corsUrl],
([category, url]) => {
if (!url) {
const baseUrl =
"http://mochi.test:8888/browser/devtools/client/webconsole/test/mochitest";
url = `${baseUrl}/sjs_cors-test-server.sjs?corsErrorCategory=${category}`;
}
// Preflight request are not made for GET requests, so let's do a PUT.
const method = "PUT";
const options = { method };
if (category === "CORSNotSupportingCredentials"
|| category === "CORSMissingAllowCredentials"
) {
options.credentials = "include";
}
if (category === "CORSMissingAllowHeaderFromPreflight") {
options.headers = new content.Headers({"xyz": true});
}
content.fetch(url, options);
});
}
function quote(str) {
const openingQuote = String.fromCharCode(8216);
const closingQuote = String.fromCharCode(8217);
return `${openingQuote}${str}${closingQuote}`;
}
function getCategoryUrl(category) {
return `${BASE_CORS_ERROR_URL}${category}?${BASE_CORS_ERROR_URL_PARAMS}`;
}

View File

@ -1,17 +1,150 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
function handleRequest(request, response) {
response.setStatusLine(request.httpVersion, 200, "Och Aye");
"use strict";
function handleRequest(request, response) {
const params = new Map(
request.queryString
.replace("?", "")
.split("&")
.map(s => s.split("="))
);
if (!params.has("corsErrorCategory")) {
response.setStatusLine(request.httpVersion, 200, "Och Aye");
setCacheHeaders(response);
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.setHeader("Access-Control-Allow-Headers", "content-type", false);
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
response.write("Access-Control-Allow-Origin: *");
return;
}
const category = params.get("corsErrorCategory");
switch (category) {
case "CORSDidNotSucceed":
corsDidNotSucceed(request, response);
break;
case "CORSExternalRedirectNotAllowed":
corsExternalRedirectNotAllowed(request, response);
break;
case "CORSMissingAllowOrigin":
corsMissingAllowOrigin(request, response);
break;
case "CORSMultipleAllowOriginNotAllowed":
corsMultipleOriginNotAllowed(request, response);
break;
case "CORSAllowOriginNotMatchingOrigin":
corsAllowOriginNotMatchingOrigin(request, response);
break;
case "CORSNotSupportingCredentials":
corsNotSupportingCredentials(request, response);
break;
case "CORSMethodNotFound":
corsMethodNotFound(request, response);
break;
case "CORSMissingAllowCredentials":
corsMissingAllowCredentials(request, response);
break;
case "CORSPreflightDidNotSucceed":
corsPreflightDidNotSucceed(request, response);
break;
case "CORSInvalidAllowMethod":
corsInvalidAllowMethod(request, response);
break;
case "CORSInvalidAllowHeader":
corsInvalidAllowHeader(request, response);
break;
case "CORSMissingAllowHeaderFromPreflight":
corsMissingAllowHeaderFromPreflight(request, response);
break;
}
}
function corsDidNotSucceed(request, response) {
setCacheHeaders(response);
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
response.setHeader("Location", "http://example.com");
}
function corsExternalRedirectNotAllowed(request, response) {
response.setStatusLine(request.httpVersion, 301, "Moved Permanently");
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.setHeader("Access-Control-Allow-Headers", "content-type", false);
response.setHeader("Location", "http://redirect.test/");
}
function corsMissingAllowOrigin(request, response) {
setCacheHeaders(response);
response.setStatusLine(request.httpVersion, 200, "corsMissingAllowOrigin");
}
function corsMultipleOriginNotAllowed(request, response) {
// We can't set the same header twice with response.setHeader, so we need to seizePower
// and write the response manually.
response.seizePower();
response.write("HTTP/1.0 200 OK\r\n");
response.write("Content-Type: text/plain\r\n");
response.write("Access-Control-Allow-Origin: *\r\n");
response.write("Access-Control-Allow-Origin: mochi.test\r\n");
response.write("\r\n");
response.finish();
setCacheHeaders(response);
}
function corsAllowOriginNotMatchingOrigin(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsAllowOriginNotMatchingOrigin");
response.setHeader("Access-Control-Allow-Origin", "mochi.test");
}
function corsNotSupportingCredentials(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsNotSupportingCredentials");
response.setHeader("Access-Control-Allow-Origin", "*");
}
function corsMethodNotFound(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsMethodNotFound");
response.setHeader("Access-Control-Allow-Origin", "*");
// Will make the request fail since it is a "PUT".
response.setHeader("Access-Control-Allow-Methods", "POST");
}
function corsMissingAllowCredentials(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsMissingAllowCredentials");
// Need to set an explicit origin (i.e. not "*") to make the request fail.
response.setHeader("Access-Control-Allow-Origin", "http://example.com");
}
function corsPreflightDidNotSucceed(request, response) {
const isPreflight = request.method == "OPTIONS";
if (isPreflight) {
response.setStatusLine(request.httpVersion, 500, "Preflight fail");
response.setHeader("Access-Control-Allow-Origin", "*");
}
}
function corsInvalidAllowMethod(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsInvalidAllowMethod");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "xyz;");
}
function corsInvalidAllowHeader(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsInvalidAllowHeader");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "PUT");
response.setHeader("Access-Control-Allow-Headers", "xyz;");
}
function corsMissingAllowHeaderFromPreflight(request, response) {
response.setStatusLine(request.httpVersion, 200, "corsMissingAllowHeaderFromPreflight");
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "PUT");
}
function setCacheHeaders(response) {
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setHeader("Expires", "0");
response.setHeader("Access-Control-Allow-Origin", "*", false);
response.setHeader("Access-Control-Allow-Headers", "content-type", false);
response.setHeader("Content-Type", "text/plain; charset=utf-8", false);
response.write("Access-Control-Allow-Origin: *");
}

View File

@ -9,9 +9,10 @@
"use strict";
const baseURL = "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/";
const baseErrorURL = "https://developer.mozilla.org/docs/Web/JavaScript/Reference/Errors/";
const params =
"?utm_source=mozilla&utm_medium=firefox-console-errors&utm_campaign=default";
const ErrorDocs = {
JSMSG_READ_ONLY: "Read-only",
JSMSG_BAD_ARRAY_LENGTH: "Invalid_array_length",
@ -108,6 +109,27 @@ const ErrorCategories = {
"source map": SOURCE_MAP_LEARN_MORE,
};
const baseCorsErrorUrl = "https://developer.mozilla.org/docs/Web/HTTP/CORS/Errors/";
const corsParams =
"?utm_source=devtools&utm_medium=firefox-cors-errors&utm_campaign=default";
const CorsErrorDocs = {
CORSDisabled: "CORSDisabled",
CORSDidNotSucceed: "CORSDidNotSucceed",
CORSOriginHeaderNotAdded: "CORSOriginHeaderNotAdded",
CORSExternalRedirectNotAllowed: "CORSExternalRedirectNotAllowed",
CORSRequestNotHttp: "CORSRequestNotHttp",
CORSMissingAllowOrigin: "CORSMissingAllowOrigin",
CORSMultipleAllowOriginNotAllowed: "CORSMultipleAllowOriginNotAllowed",
CORSAllowOriginNotMatchingOrigin: "CORSAllowOriginNotMatchingOrigin",
CORSNotSupportingCredentials: "CORSNotSupportingCredentials",
CORSMethodNotFound: "CORSMethodNotFound",
CORSMissingAllowCredentials: "CORSMissingAllowCredentials",
CORSPreflightDidNotSucceed: "CORSPreflightDidNotSucceed",
CORSInvalidAllowMethod: "CORSInvalidAllowMethod",
CORSInvalidAllowHeader: "CORSInvalidAllowHeader",
CORSMissingAllowHeaderFromPreflight: "CORSMissingAllowHeaderFromPreflight",
};
exports.GetURL = (error) => {
if (!error) {
return undefined;
@ -115,7 +137,12 @@ exports.GetURL = (error) => {
const doc = ErrorDocs[error.errorMessageName];
if (doc) {
return baseURL + doc + params;
return baseErrorURL + doc + params;
}
const corsDoc = CorsErrorDocs[error.category];
if (corsDoc) {
return baseCorsErrorUrl + corsDoc + corsParams;
}
const categoryURL = ErrorCategories[error.category];