// Test the plaintext-or-binary sniffer "use strict"; const { HttpServer } = ChromeUtils.importESModule( "resource://testing-common/httpd.sys.mjs" ); // List of Content-Type headers to test. For each header we have an array. // The first element in the array is the Content-Type header string. The // second element in the array is a boolean indicating whether we allow // sniffing for that type. var contentTypeHeaderList = [ ["text/plain", true], ["text/plain; charset=ISO-8859-1", true], ["text/plain; charset=iso-8859-1", true], ["text/plain; charset=UTF-8", true], ["text/plain; charset=unknown", false], ["text/plain; param", false], ["text/plain; charset=ISO-8859-1; param", false], ["text/plain; charset=iso-8859-1; param", false], ["text/plain; charset=UTF-8; param", false], ["text/plain; charset=utf-8", false], ["text/plain; charset=utf8", false], ["text/plain; charset=UTF8", false], ["text/plain; charset=iSo-8859-1", false], ]; // List of response bodies to test. For each response we have an array. The // first element in the array is the body string. The second element in the // array is a boolean indicating whether that string should sniff as binary. var bodyList = [["Plaintext", false]]; // List of possible BOMs var BOMList = [ "\xFE\xFF", // UTF-16BE "\xFF\xFE", // UTF-16LE "\xEF\xBB\xBF", // UTF-8 "\x00\x00\xFE\xFF", // UCS-4BE "\x00\x00\xFF\xFE", // UCS-4LE ]; // Build up bodyList. The things we treat as binary are ASCII codes 0-8, // 14-26, 28-31. That is, the control char range, except for tab, newline, // vertical tab, form feed, carriage return, and ESC (this last being used by // Shift_JIS, apparently). function isBinaryChar(ch) { return ( (0 <= ch && ch <= 8) || (14 <= ch && ch <= 26) || (28 <= ch && ch <= 31) ); } // Test chars on their own var i; for (i = 0; i <= 127; ++i) { bodyList.push([String.fromCharCode(i), isBinaryChar(i)]); } // Test that having a BOM prevents plaintext sniffing var j; for (i = 0; i <= 127; ++i) { for (j = 0; j < BOMList.length; ++j) { bodyList.push([BOMList[j] + String.fromCharCode(i, i), false]); } } // Test that having a BOM requires at least 4 chars to kick in for (i = 0; i <= 127; ++i) { for (j = 0; j < BOMList.length; ++j) { bodyList.push([ BOMList[j] + String.fromCharCode(i), BOMList[j].length == 2 && isBinaryChar(i), ]); } } function makeChan(headerIdx, bodyIdx) { var chan = NetUtil.newChannel({ uri: "http://localhost:" + httpserv.identity.primaryPort + "/" + headerIdx + "/" + bodyIdx, loadUsingSystemPrincipal: true, }).QueryInterface(Ci.nsIHttpChannel); chan.loadFlags |= Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; return chan; } function makeListener(headerIdx, bodyIdx) { var listener = { onStartRequest: function test_onStartR(request) { try { var chan = request.QueryInterface(Ci.nsIChannel); Assert.equal(chan.status, Cr.NS_OK); var type = chan.contentType; var expectedType = contentTypeHeaderList[headerIdx][1] && bodyList[bodyIdx][1] ? "application/x-vnd.mozilla.guess-from-ext" : "text/plain"; if (expectedType != type) { do_throw( "Unexpected sniffed type '" + type + "'. " + "Should be '" + expectedType + "'. " + "Header is ['" + contentTypeHeaderList[headerIdx][0] + "', " + contentTypeHeaderList[headerIdx][1] + "]. " + "Body is ['" + bodyList[bodyIdx][0].toSource() + "', " + bodyList[bodyIdx][1] + "]." ); } Assert.equal(expectedType, type); } catch (e) { do_throw("Unexpected exception: " + e); } throw Components.Exception("", Cr.NS_ERROR_ABORT); }, onDataAvailable: function test_ODA() { do_throw("Should not get any data!"); }, onStopRequest: function test_onStopR() { // Advance to next test ++headerIdx; if (headerIdx == contentTypeHeaderList.length) { headerIdx = 0; ++bodyIdx; } if (bodyIdx == bodyList.length) { do_test_pending(); httpserv.stop(do_test_finished); } else { doTest(headerIdx, bodyIdx); } do_test_finished(); }, }; return listener; } function doTest(headerIdx, bodyIdx) { var chan = makeChan(headerIdx, bodyIdx); var listener = makeListener(headerIdx, bodyIdx); chan.asyncOpen(listener); do_test_pending(); } function createResponse(headerIdx, bodyIdx, metadata, response) { response.setHeader( "Content-Type", contentTypeHeaderList[headerIdx][0], false ); response.bodyOutputStream.write( bodyList[bodyIdx][0], bodyList[bodyIdx][0].length ); } function makeHandler(headerIdx, bodyIdx) { var f = function handlerClosure(metadata, response) { return createResponse(headerIdx, bodyIdx, metadata, response); }; return f; } var httpserv; function run_test() { // disable on Windows for now, because it seems to leak sockets and die. // Silly operating system! // This is a really nasty way to detect Windows. I wish we could do better. if (mozinfo.os == "win") { //failing eslint no-empty test } httpserv = new HttpServer(); for (i = 0; i < contentTypeHeaderList.length; ++i) { for (j = 0; j < bodyList.length; ++j) { httpserv.registerPathHandler("/" + i + "/" + j, makeHandler(i, j)); } } httpserv.start(-1); doTest(0, 0); }