test_plaintext_sniff.js (5700B)
1 // Test the plaintext-or-binary sniffer 2 3 "use strict"; 4 5 const { HttpServer } = ChromeUtils.importESModule( 6 "resource://testing-common/httpd.sys.mjs" 7 ); 8 9 // List of Content-Type headers to test. For each header we have an array. 10 // The first element in the array is the Content-Type header string. The 11 // second element in the array is a boolean indicating whether we allow 12 // sniffing for that type. 13 var contentTypeHeaderList = [ 14 ["text/plain", true], 15 ["text/plain; charset=ISO-8859-1", true], 16 ["text/plain; charset=iso-8859-1", true], 17 ["text/plain; charset=UTF-8", true], 18 ["text/plain; charset=unknown", false], 19 ["text/plain; param", false], 20 ["text/plain; charset=ISO-8859-1; param", false], 21 ["text/plain; charset=iso-8859-1; param", false], 22 ["text/plain; charset=UTF-8; param", false], 23 ["text/plain; charset=utf-8", false], 24 ["text/plain; charset=utf8", false], 25 ["text/plain; charset=UTF8", false], 26 ["text/plain; charset=iSo-8859-1", false], 27 ]; 28 29 // List of response bodies to test. For each response we have an array. The 30 // first element in the array is the body string. The second element in the 31 // array is a boolean indicating whether that string should sniff as binary. 32 var bodyList = [["Plaintext", false]]; 33 34 // List of possible BOMs 35 var BOMList = [ 36 "\xFE\xFF", // UTF-16BE 37 "\xFF\xFE", // UTF-16LE 38 "\xEF\xBB\xBF", // UTF-8 39 "\x00\x00\xFE\xFF", // UCS-4BE 40 "\x00\x00\xFF\xFE", // UCS-4LE 41 ]; 42 43 // Build up bodyList. The things we treat as binary are ASCII codes 0-8, 44 // 14-26, 28-31. That is, the control char range, except for tab, newline, 45 // vertical tab, form feed, carriage return, and ESC (this last being used by 46 // Shift_JIS, apparently). 47 function isBinaryChar(ch) { 48 return ( 49 (0 <= ch && ch <= 8) || (14 <= ch && ch <= 26) || (28 <= ch && ch <= 31) 50 ); 51 } 52 53 // Test chars on their own 54 var i; 55 for (i = 0; i <= 127; ++i) { 56 bodyList.push([String.fromCharCode(i), isBinaryChar(i)]); 57 } 58 59 // Test that having a BOM prevents plaintext sniffing 60 var j; 61 for (i = 0; i <= 127; ++i) { 62 for (j = 0; j < BOMList.length; ++j) { 63 bodyList.push([BOMList[j] + String.fromCharCode(i, i), false]); 64 } 65 } 66 67 // Test that having a BOM requires at least 4 chars to kick in 68 for (i = 0; i <= 127; ++i) { 69 for (j = 0; j < BOMList.length; ++j) { 70 bodyList.push([ 71 BOMList[j] + String.fromCharCode(i), 72 BOMList[j].length == 2 && isBinaryChar(i), 73 ]); 74 } 75 } 76 77 function makeChan(headerIdx, bodyIdx) { 78 var chan = NetUtil.newChannel({ 79 uri: 80 "http://localhost:" + 81 httpserv.identity.primaryPort + 82 "/" + 83 headerIdx + 84 "/" + 85 bodyIdx, 86 loadUsingSystemPrincipal: true, 87 }).QueryInterface(Ci.nsIHttpChannel); 88 89 chan.loadFlags |= Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS; 90 91 return chan; 92 } 93 94 function makeListener(headerIdx, bodyIdx) { 95 var listener = { 96 onStartRequest: function test_onStartR(request) { 97 try { 98 var chan = request.QueryInterface(Ci.nsIChannel); 99 100 Assert.equal(chan.status, Cr.NS_OK); 101 102 var type = chan.contentType; 103 104 var expectedType = 105 contentTypeHeaderList[headerIdx][1] && bodyList[bodyIdx][1] 106 ? "application/x-vnd.mozilla.guess-from-ext" 107 : "text/plain"; 108 if (expectedType != type) { 109 do_throw( 110 "Unexpected sniffed type '" + 111 type + 112 "'. " + 113 "Should be '" + 114 expectedType + 115 "'. " + 116 "Header is ['" + 117 contentTypeHeaderList[headerIdx][0] + 118 "', " + 119 contentTypeHeaderList[headerIdx][1] + 120 "]. " + 121 "Body is ['" + 122 bodyList[bodyIdx][0].toSource() + 123 "', " + 124 bodyList[bodyIdx][1] + 125 "]." 126 ); 127 } 128 Assert.equal(expectedType, type); 129 } catch (e) { 130 do_throw("Unexpected exception: " + e); 131 } 132 133 throw Components.Exception("", Cr.NS_ERROR_ABORT); 134 }, 135 136 onDataAvailable: function test_ODA() { 137 do_throw("Should not get any data!"); 138 }, 139 140 onStopRequest: function test_onStopR() { 141 // Advance to next test 142 ++headerIdx; 143 if (headerIdx == contentTypeHeaderList.length) { 144 headerIdx = 0; 145 ++bodyIdx; 146 } 147 148 if (bodyIdx == bodyList.length) { 149 do_test_pending(); 150 httpserv.stop(do_test_finished); 151 } else { 152 doTest(headerIdx, bodyIdx); 153 } 154 155 do_test_finished(); 156 }, 157 }; 158 159 return listener; 160 } 161 162 function doTest(headerIdx, bodyIdx) { 163 var chan = makeChan(headerIdx, bodyIdx); 164 165 var listener = makeListener(headerIdx, bodyIdx); 166 167 chan.asyncOpen(listener); 168 169 do_test_pending(); 170 } 171 172 function createResponse(headerIdx, bodyIdx, metadata, response) { 173 response.setHeader( 174 "Content-Type", 175 contentTypeHeaderList[headerIdx][0], 176 false 177 ); 178 response.bodyOutputStream.write( 179 bodyList[bodyIdx][0], 180 bodyList[bodyIdx][0].length 181 ); 182 } 183 184 function makeHandler(headerIdx, bodyIdx) { 185 var f = function handlerClosure(metadata, response) { 186 return createResponse(headerIdx, bodyIdx, metadata, response); 187 }; 188 return f; 189 } 190 191 var httpserv; 192 function run_test() { 193 // disable on Windows for now, because it seems to leak sockets and die. 194 // Silly operating system! 195 // This is a really nasty way to detect Windows. I wish we could do better. 196 if (mozinfo.os == "win") { 197 //failing eslint no-empty test 198 } 199 200 httpserv = new HttpServer(); 201 202 for (i = 0; i < contentTypeHeaderList.length; ++i) { 203 for (j = 0; j < bodyList.length; ++j) { 204 httpserv.registerPathHandler("/" + i + "/" + j, makeHandler(i, j)); 205 } 206 } 207 208 httpserv.start(-1); 209 210 doTest(0, 0); 211 }