336 lines
11 KiB
HTML
336 lines
11 KiB
HTML
<!DOCTYPE HTML>
|
|
<html>
|
|
<!--
|
|
Tests for Mixed Content Blocker
|
|
https://bugzilla.mozilla.org/show_bug.cgi?id=62178
|
|
-->
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<title>Tests for Bug 62178</title>
|
|
<script src="/tests/SimpleTest/EventUtils.js"></script>
|
|
</head>
|
|
<body>
|
|
<div id="testContent"></div>
|
|
|
|
<!-- types the Mixed Content Blocker can block
|
|
/*
|
|
switch (aContentType) {
|
|
case nsIContentPolicy::TYPE_OBJECT:
|
|
case nsIContentPolicy::TYPE_SCRIPT:
|
|
case nsIContentPolicy::TYPE_STYLESHEET:
|
|
case nsIContentPolicy::TYPE_SUBDOCUMENT:
|
|
case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
|
|
|
|
case nsIContentPolicy::TYPE_FONT: - NO TEST:
|
|
Load events for external fonts are not detectable by javascript.
|
|
case nsIContentPolicy::TYPE_WEBSOCKET: - NO TEST:
|
|
websocket connections over https require an encrypted websocket protocol (wss:)
|
|
|
|
case nsIContentPolicy::TYPE_IMAGE:
|
|
case nsIContentPolicy::TYPE_IMAGESET:
|
|
case nsIContentPolicy::TYPE_MEDIA:
|
|
case nsIContentPolicy::TYPE_PING:
|
|
our ping implementation is off by default and does not comply with the current spec (bug 786347)
|
|
case nsIContentPolicy::TYPE_BEACON:
|
|
|
|
}
|
|
*/
|
|
-->
|
|
|
|
<script>
|
|
function ok(a, msg) {
|
|
parent.postMessage({msg, check: true, status: !!a}, "http://mochi.test:8888");
|
|
}
|
|
|
|
function is(a, b, msg) {
|
|
ok(a == b, msg);
|
|
}
|
|
|
|
function report(type, msg) {
|
|
parent.postMessage({test: type, msg}, "http://mochi.test:8888");
|
|
}
|
|
|
|
function uniqueID() {
|
|
return Math.floor(Math.random() * 100000)
|
|
}
|
|
function uniqueIDParam(id) {
|
|
return "&uniqueID=" + id;
|
|
}
|
|
|
|
async function init() {
|
|
var baseUrl = "http://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs";
|
|
var checkLastRequestUrl = "https://example.com/tests/dom/security/test/mixedcontentblocker/file_server.sjs?lastRequest=true";
|
|
|
|
//For tests that require setTimeout, set the maximum polling time to 100 x 100ms = 10 seconds.
|
|
var MAX_COUNT = 100;
|
|
var TIMEOUT_INTERVAL = 100;
|
|
|
|
var testContent = document.getElementById("testContent");
|
|
|
|
async function checkLastRequest() {
|
|
const response = await fetch(checkLastRequestUrl);
|
|
return response.json();
|
|
}
|
|
|
|
/* Part 1: Mixed Script tests */
|
|
|
|
// Test 1a: insecure object
|
|
var object = document.createElement("object");
|
|
var objectId = uniqueID();
|
|
object.data = baseUrl + "?type=object" + uniqueIDParam(objectId);
|
|
object.type = "image/png";
|
|
object.width = "200";
|
|
object.height = "200";
|
|
|
|
testContent.appendChild(object);
|
|
|
|
var objectCount = 0;
|
|
|
|
function objectStatus(object) {
|
|
// Expose our privileged bits on the object. We will match the MIME type to the one
|
|
// provided by file_server.sjs
|
|
object = SpecialPowers.wrap(object);
|
|
var typeIsSet = object.actualType && (object.actualType !== '');
|
|
var isLoaded = typeIsSet && object.actualType === 'application/x-test-match';
|
|
if (isLoaded) {
|
|
//object loaded
|
|
report("object", "insecure object loaded");
|
|
}
|
|
else {
|
|
if(!typeIsSet && objectCount < MAX_COUNT) {
|
|
objectCount++;
|
|
setTimeout(objectStatus, TIMEOUT_INTERVAL, object);
|
|
}
|
|
else {
|
|
//After we have called setTimeout the maximum number of times, assume object is blocked
|
|
report("object", "insecure object blocked");
|
|
}
|
|
}
|
|
}
|
|
|
|
// object does not have onload and onerror events. Hence we need a setTimeout to check the object's status
|
|
setTimeout(objectStatus, TIMEOUT_INTERVAL, object);
|
|
|
|
// Test 1b: insecure script
|
|
var script = document.createElement("script");
|
|
var scriptId = uniqueID();
|
|
var scriptLoad = false;
|
|
var scriptCount = 0;
|
|
script.src = baseUrl + "?type=script" + uniqueIDParam(scriptId);
|
|
script.onload = function(e) {
|
|
report("script", "insecure script loaded");
|
|
scriptLoad = true;
|
|
}
|
|
testContent.appendChild(script);
|
|
|
|
function scriptStatus(script)
|
|
{
|
|
if(scriptLoad) {
|
|
return;
|
|
}
|
|
if (scriptCount < MAX_COUNT) {
|
|
scriptCount++;
|
|
setTimeout(scriptStatus, TIMEOUT_INTERVAL, script);
|
|
}
|
|
else {
|
|
//After we have called setTimeout the maximum number of times, assume script is blocked
|
|
report("script", "insecure script blocked");
|
|
}
|
|
}
|
|
|
|
// scripts blocked by Content Policy's do not have onerror events (see bug 789856). Hence we need a setTimeout to check the script's status
|
|
setTimeout(scriptStatus, TIMEOUT_INTERVAL, script);
|
|
|
|
|
|
// Test 1c: insecure stylesheet
|
|
var cssStyleSheet = document.createElement("link");
|
|
var cssStyleSheetId = uniqueID();
|
|
cssStyleSheet.rel = "stylesheet";
|
|
cssStyleSheet.href = baseUrl + "?type=stylesheet" + uniqueIDParam(cssStyleSheetId);
|
|
cssStyleSheet.type = "text/css";
|
|
testContent.appendChild(cssStyleSheet);
|
|
|
|
var styleCount = 0;
|
|
|
|
function styleStatus(cssStyleSheet) {
|
|
if( cssStyleSheet.sheet || cssStyleSheet.styleSheet || cssStyleSheet.innerHTML ) {
|
|
report("stylesheet", "insecure stylesheet loaded");
|
|
}
|
|
else {
|
|
if(styleCount < MAX_COUNT) {
|
|
styleCount++;
|
|
setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet);
|
|
}
|
|
else {
|
|
//After we have called setTimeout the maximum number of times, assume stylesheet is blocked
|
|
report("stylesheet", "insecure stylesheet blocked");
|
|
}
|
|
}
|
|
}
|
|
|
|
// link does not have onload and onerror events. Hence we need a setTimeout to check the link's status
|
|
window.setTimeout(styleStatus, TIMEOUT_INTERVAL, cssStyleSheet);
|
|
|
|
// Test 1d: insecure iframe
|
|
var iframe = document.createElement("iframe");
|
|
var iframeId = uniqueID();
|
|
iframe.src = baseUrl + "?type=iframe" + uniqueIDParam(iframeId);
|
|
iframe.onload = function() {
|
|
report("iframe", "insecure iframe loaded");
|
|
}
|
|
iframe.onerror = function() {
|
|
report("iframe", "insecure iframe blocked");
|
|
};
|
|
testContent.appendChild(iframe);
|
|
|
|
|
|
// Test 1e: insecure xhr
|
|
await new Promise((resolve) => {
|
|
var xhr = new XMLHttpRequest;
|
|
var xhrId = uniqueID();
|
|
try {
|
|
xhr.open("GET", baseUrl + "?type=xhr" + uniqueIDParam(xhrId), true);
|
|
xhr.send();
|
|
xhr.onloadend = function (oEvent) {
|
|
if (xhr.status == 200) {
|
|
report("xhr", "insecure xhr loaded");
|
|
resolve();
|
|
}
|
|
else {
|
|
report("xhr", "insecure xhr blocked");
|
|
resolve();
|
|
}
|
|
}
|
|
} catch(ex) {
|
|
report("xhr", "insecure xhr blocked");
|
|
resolve();
|
|
}
|
|
});
|
|
|
|
/* Part 2: Mixed Display tests */
|
|
|
|
// Shorthand for all image test variants
|
|
async function imgHandlers(img, test, uniqueID) {
|
|
await new Promise((resolve) => {
|
|
img.onload = async () => {
|
|
const lastRequest = await checkLastRequest();
|
|
is(lastRequest.uniqueID, uniqueID, "UniqueID for the last request matches");
|
|
let message = "insecure image loaded";
|
|
if (lastRequest.scheme == "https") {
|
|
message = "secure image loaded after upgrade";
|
|
}
|
|
report(test, message);
|
|
resolve();
|
|
}
|
|
img.onerror = async () => {
|
|
let message = "insecure image blocked";
|
|
report(test, message);
|
|
resolve();
|
|
}
|
|
});
|
|
}
|
|
|
|
// Test 2a: insecure image
|
|
var img = document.createElement("img");
|
|
var imgId = uniqueID();
|
|
img.src = baseUrl + "?type=img" + uniqueIDParam(imgId);
|
|
await imgHandlers(img, "image", imgId);
|
|
// We don't need to append the image to the document. Doing so causes the image test to run twice.
|
|
|
|
// Test 2b: insecure media
|
|
var media = document.createElement("video");
|
|
var mediaId = uniqueID();
|
|
media.src = baseUrl + "?type=media" + uniqueIDParam(mediaId);
|
|
media.width = "320";
|
|
media.height = "200";
|
|
media.type = "video/ogg";
|
|
await new Promise(res => {
|
|
media.onloadeddata = async () => {
|
|
const lastRequest = await checkLastRequest();
|
|
is(lastRequest.uniqueID, mediaId, "UniqueID for the last request matches");
|
|
let message = "insecure media loaded";
|
|
if (lastRequest.scheme == "https") {
|
|
message = "secure media loaded after upgrade";
|
|
}
|
|
report("media", message);
|
|
res();
|
|
}
|
|
media.onerror = function() {
|
|
report("media", "insecure media blocked");
|
|
res();
|
|
}
|
|
});
|
|
// We don't need to append the video to the document. Doing so causes the image test to run twice.
|
|
|
|
/* Part 3: Mixed Active Tests for Image srcset */
|
|
|
|
// Test 3a: image with srcset
|
|
var imgA = document.createElement("img");
|
|
var imgAId = uniqueID();
|
|
imgA.srcset = baseUrl + "?type=img&subtype=imageSrcset" + uniqueIDParam(imgAId);
|
|
await imgHandlers(imgA, "imageSrcset", imgAId);
|
|
|
|
// Test 3b: image with srcset, using fallback from src, should still use imageset policy
|
|
var imgB = document.createElement("img");
|
|
var imgBId = uniqueID();
|
|
imgB.srcset = " ";
|
|
imgB.src = baseUrl + "?type=img&subtype=imageSrcSetFallback" + uniqueIDParam(imgBId);
|
|
await imgHandlers(imgB, "imageSrcsetFallback", imgBId);
|
|
|
|
// Test 3c: image in <picture>
|
|
var imgC = document.createElement("img");
|
|
var pictureC = document.createElement("picture");
|
|
var sourceC = document.createElement("source");
|
|
var sourceCId = uniqueID();
|
|
sourceC.srcset = baseUrl + "?type=img&subtype=imagePicture" + uniqueIDParam(sourceCId);
|
|
pictureC.appendChild(sourceC);
|
|
pictureC.appendChild(imgC);
|
|
await imgHandlers(imgC, "imagePicture", sourceCId);
|
|
|
|
// Test 3d: Loaded basic image switching to a <picture>, loading
|
|
// same source, should still redo the request with new
|
|
// policy.
|
|
var imgD = document.createElement("img");
|
|
var imgDId = uniqueID();
|
|
var sourceDId = uniqueID();
|
|
imgD.src = baseUrl + "?type=img&subtype=imageJoinPicture" + uniqueIDParam(imgDId);
|
|
await new Promise(res => {
|
|
imgD.onload = imgD.onerror = function() {
|
|
// Whether or not it loads, we want to now append it to a picture and observe
|
|
var pictureD = document.createElement("picture");
|
|
var sourceD = document.createElement("source");
|
|
sourceD.srcset = baseUrl + "?type=img&subtype=imageJoinPicture2" + uniqueIDParam(sourceDId);
|
|
pictureD.appendChild(sourceD);
|
|
pictureD.appendChild(imgD);
|
|
res();
|
|
}
|
|
});
|
|
await imgHandlers(imgD, "imageJoinPicture", sourceDId);
|
|
|
|
// Test 3e: img load from <picture> source reverts to img.src as it
|
|
// is removed -- the new request should revert to mixed
|
|
// display policy
|
|
var imgE = document.createElement("img");
|
|
var pictureE = document.createElement("picture");
|
|
var pictureEId = uniqueID();
|
|
var sourceE = document.createElement("source");
|
|
var sourceEId = uniqueID();
|
|
sourceE.srcset = baseUrl + "?type=img&subtype=imageLeavePicture" + uniqueIDParam(sourceEId);
|
|
pictureE.appendChild(sourceE);
|
|
pictureE.appendChild(imgE);
|
|
imgE.src = baseUrl + "?type=img&subtype=imageLeavePicture2" + uniqueIDParam(pictureEId);
|
|
await new Promise(res => {
|
|
imgE.onload = imgE.onerror = function() {
|
|
// Whether or not it loads, remove it from the picture and observe
|
|
pictureE.removeChild(imgE)
|
|
res();
|
|
}
|
|
});
|
|
await imgHandlers(imgE, "imageLeavePicture", pictureEId);
|
|
}
|
|
|
|
init();
|
|
|
|
</script>
|
|
</body>
|
|
</html>
|