2 * Read count bytes from stream and return as a String object
5 /* import-globals-from head_cache.js */
6 /* import-globals-from head_cookies.js */
8 function read_stream(stream, count) {
9 /* assume stream has non-ASCII data */
10 var wrapper = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
11 Ci.nsIBinaryInputStream
13 wrapper.setInputStream(stream);
14 /* JS methods can be called with a maximum of 65535 arguments, and input
15 streams don't have to return all the data they make .available() when
16 asked to .read() that number of bytes. */
19 var bytes = wrapper.readByteArray(Math.min(65535, count));
20 data.push(String.fromCharCode.apply(null, bytes));
21 count -= bytes.length;
23 do_throw("Nothing read from input stream!");
29 const CL_EXPECT_FAILURE = 0x1;
30 const CL_EXPECT_GZIP = 0x2;
31 const CL_EXPECT_3S_DELAY = 0x4;
32 const CL_SUSPEND = 0x8;
33 const CL_ALLOW_UNKNOWN_CL = 0x10;
34 const CL_EXPECT_LATE_FAILURE = 0x20;
35 const CL_FROM_CACHE = 0x40; // Response must be from the cache
36 const CL_NOT_FROM_CACHE = 0x80; // Response must NOT be from the cache
37 const CL_IGNORE_CL = 0x100; // don't bother to verify the content-length
39 const SUSPEND_DELAY = 3000;
42 * A stream listener that calls a callback function with a specified
43 * context and the received data when the channel is loaded.
45 * Signature of the closure:
46 * void closure(in nsIRequest request, in ACString data, in JSObject context);
48 * This listener makes sure that various parts of the channel API are
49 * implemented correctly and that the channel's status is a success code
50 * (you can pass CL_EXPECT_FAILURE or CL_EXPECT_LATE_FAILURE as flags
51 * to allow a failure code)
53 * Note that it also requires a valid content length on the channel and
54 * is thus not fully generic.
56 function ChannelListener(closure, ctx, flags) {
57 this._closure = closure;
58 this._closurectx = ctx;
60 this._isFromCache = false;
61 this._cacheEntryId = undefined;
63 ChannelListener.prototype = {
67 _got_onstartrequest: false,
68 _got_onstoprequest: false,
72 QueryInterface: ChromeUtils.generateQI([
77 onStartRequest(request) {
79 if (this._got_onstartrequest) {
80 do_throw("Got second onStartRequest event!");
82 this._got_onstartrequest = true;
83 this._lastEvent = Date.now();
86 this._isFromCache = request
87 .QueryInterface(Ci.nsICacheInfoChannel)
93 this._cacheEntryId = request
94 .QueryInterface(Ci.nsICacheInfoChannel)
99 if (this._isFromCache && thrown) {
100 do_throw("Should get a CacheEntryId");
101 } else if (!this._isFromCache && !thrown) {
102 do_throw("Shouldn't get a CacheEntryId");
105 request.QueryInterface(Ci.nsIChannel);
107 this._contentLen = request.contentLength;
109 if (!(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))) {
110 do_throw("Could not get contentLength");
113 if (!request.isPending()) {
114 do_throw("request reports itself as not pending from onStartRequest!");
117 this._contentLen == -1 &&
118 !(this._flags & (CL_EXPECT_FAILURE | CL_ALLOW_UNKNOWN_CL))
120 do_throw("Content length is unknown in onStartRequest!");
123 if (this._flags & CL_FROM_CACHE) {
124 request.QueryInterface(Ci.nsICachingChannel);
125 if (!request.isFromCache()) {
126 do_throw("Response is not from the cache (CL_FROM_CACHE)");
129 if (this._flags & CL_NOT_FROM_CACHE) {
130 request.QueryInterface(Ci.nsICachingChannel);
131 if (request.isFromCache()) {
132 do_throw("Response is from the cache (CL_NOT_FROM_CACHE)");
136 if (this._flags & CL_SUSPEND) {
138 do_timeout(SUSPEND_DELAY, function() {
143 do_throw("Error in onStartRequest: " + ex);
147 onDataAvailable(request, stream, offset, count) {
149 let current = Date.now();
151 if (!this._got_onstartrequest) {
152 do_throw("onDataAvailable without onStartRequest event!");
154 if (this._got_onstoprequest) {
155 do_throw("onDataAvailable after onStopRequest event!");
157 if (!request.isPending()) {
158 do_throw("request reports itself as not pending from onDataAvailable!");
160 if (this._flags & CL_EXPECT_FAILURE) {
161 do_throw("Got data despite expecting a failure");
165 current - this._lastEvent >= SUSPEND_DELAY &&
166 !(this._flags & CL_EXPECT_3S_DELAY)
168 do_throw("Data received after significant unexpected delay");
170 current - this._lastEvent < SUSPEND_DELAY &&
171 this._flags & CL_EXPECT_3S_DELAY
173 do_throw("Data received sooner than expected");
175 current - this._lastEvent >= SUSPEND_DELAY &&
176 this._flags & CL_EXPECT_3S_DELAY
178 this._flags &= ~CL_EXPECT_3S_DELAY;
179 } // No more delays expected
181 this._buffer = this._buffer.concat(read_stream(stream, count));
182 this._lastEvent = current;
184 do_throw("Error in onDataAvailable: " + ex);
188 onStopRequest(request, status) {
190 var success = Components.isSuccessCode(status);
191 if (!this._got_onstartrequest) {
192 do_throw("onStopRequest without onStartRequest event!");
194 if (this._got_onstoprequest) {
195 do_throw("Got second onStopRequest event!");
197 this._got_onstoprequest = true;
199 this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE) &&
203 "Should have failed to load URL (status is " +
204 status.toString(16) +
208 !(this._flags & (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE)) &&
211 do_throw("Failed to load URL: " + status.toString(16));
213 if (status != request.status) {
214 do_throw("request.status does not match status arg to onStopRequest!");
216 if (request.isPending()) {
217 do_throw("request reports itself as pending from onStopRequest!");
222 (CL_EXPECT_FAILURE | CL_EXPECT_LATE_FAILURE | CL_IGNORE_CL)
224 !(this._flags & CL_EXPECT_GZIP) &&
225 this._contentLen != -1
227 Assert.equal(this._buffer.length, this._contentLen);
230 do_throw("Error in onStopRequest: " + ex);
240 this._closurectx = null;
242 do_throw("Error in closure function: " + ex);
247 var ES_ABORT_REDIRECT = 0x01;
249 function ChannelEventSink(flags) {
253 ChannelEventSink.prototype = {
254 QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]),
257 if (iid.equals(Ci.nsIChannelEventSink)) {
260 throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
263 asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
264 if (this._flags & ES_ABORT_REDIRECT) {
265 throw Components.Exception("", Cr.NS_BINDING_ABORTED);
268 callback.onRedirectVerifyCallback(Cr.NS_OK);
273 * A helper class to construct origin attributes.
275 function OriginAttributes(inIsolatedMozBrowser, privateId) {
276 this.inIsolatedMozBrowser = inIsolatedMozBrowser;
277 this.privateBrowsingId = privateId;
279 OriginAttributes.prototype = {
280 inIsolatedMozBrowser: false,
281 privateBrowsingId: 0,
284 function readFile(file) {
285 let fstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(
286 Ci.nsIFileInputStream
288 fstream.init(file, -1, 0, 0);
289 let data = NetUtil.readInputStreamToString(fstream, fstream.available());
294 function addCertFromFile(certdb, filename, trustString) {
295 let certFile = do_get_file(filename, false);
296 let pem = readFile(certFile)
297 .replace(/-----BEGIN CERTIFICATE-----/, "")
298 .replace(/-----END CERTIFICATE-----/, "")
299 .replace(/[\r\n]/g, "");
300 certdb.addCertFromBase64(pem, trustString);
303 // Helper code to test nsISerializable
304 function serialize_to_escaped_string(obj) {
305 let objectOutStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
306 Ci.nsIObjectOutputStream
308 let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
309 pipe.init(false, false, 0, 0xffffffff, null);
310 objectOutStream.setOutputStream(pipe.outputStream);
311 objectOutStream.writeCompoundObject(obj, Ci.nsISupports, true);
312 objectOutStream.close();
314 let objectInStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
315 Ci.nsIObjectInputStream
317 objectInStream.setInputStream(pipe.inputStream);
319 // This reads all the data from the stream until an error occurs.
322 let bytes = objectInStream.readByteArray(1);
323 data.push(String.fromCharCode.apply(null, bytes));
328 return escape(data.join(""));
331 function deserialize_from_escaped_string(str) {
332 let payload = unescape(str);
335 while (i < payload.length) {
336 data.push(payload.charCodeAt(i++));
339 let objectOutStream = Cc["@mozilla.org/binaryoutputstream;1"].createInstance(
340 Ci.nsIObjectOutputStream
342 let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
343 pipe.init(false, false, 0, 0xffffffff, null);
344 objectOutStream.setOutputStream(pipe.outputStream);
345 objectOutStream.writeByteArray(data);
346 objectOutStream.close();
348 let objectInStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(
349 Ci.nsIObjectInputStream
351 objectInStream.setInputStream(pipe.inputStream);
352 return objectInStream.readObject(true);
355 // Copied from head_psm.js.
356 function add_tls_server_setup(serverBinName, certsPath, addDefaultRoot = true) {
357 add_test(function() {
358 _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot);
362 // Do not call this directly; use add_tls_server_setup
363 function _setupTLSServerTest(serverBinName, certsPath, addDefaultRoot) {
364 asyncStartTLSTestServer(serverBinName, certsPath, addDefaultRoot).then(
369 async function asyncStartTLSTestServer(
374 const { HttpServer } = ChromeUtils.import(
375 "resource://testing-common/httpd.js"
377 let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
380 // The trusted CA that is typically used for "good" certificates.
381 if (addDefaultRoot) {
382 addCertFromFile(certdb, `${certsPath}/test-ca.pem`, "CTu,u,u");
385 const CALLBACK_PORT = 8444;
387 let envSvc = Cc["@mozilla.org/process/environment;1"].getService(
390 let greBinDir = Services.dirsvc.get("GreBinD", Ci.nsIFile);
391 envSvc.set("DYLD_LIBRARY_PATH", greBinDir.path);
392 // TODO(bug 1107794): Android libraries are in /data/local/xpcb, but "GreBinD"
393 // does not return this path on Android, so hard code it here.
394 envSvc.set("LD_LIBRARY_PATH", greBinDir.path + ":/data/local/xpcb");
395 envSvc.set("MOZ_TLS_SERVER_DEBUG_LEVEL", "3");
396 envSvc.set("MOZ_TLS_SERVER_CALLBACK_PORT", CALLBACK_PORT);
398 let httpServer = new HttpServer();
399 let serverReady = new Promise(resolve => {
400 httpServer.registerPathHandler("/", function handleServerCallback(
404 aResponse.setStatusLine(aRequest.httpVersion, 200, "OK");
405 aResponse.setHeader("Content-Type", "text/plain");
406 let responseBody = "OK!";
407 aResponse.bodyOutputStream.write(responseBody, responseBody.length);
408 executeSoon(function() {
409 httpServer.stop(resolve);
412 httpServer.start(CALLBACK_PORT);
415 let serverBin = _getBinaryUtil(serverBinName);
416 let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
417 process.init(serverBin);
418 let certDir = do_get_file(certsPath, false);
419 Assert.ok(certDir.exists(), `certificate folder (${certsPath}) should exist`);
420 // Using "sql:" causes the SQL DB to be used so we can run tests on Android.
421 process.run(false, ["sql:" + certDir.path, Services.appinfo.processID], 2);
423 registerCleanupFunction(function() {
430 function _getBinaryUtil(binaryUtilName) {
431 let utilBin = Services.dirsvc.get("GreD", Ci.nsIFile);
432 // On macOS, GreD is .../Contents/Resources, and most binary utilities
433 // are located there, but certutil is in GreBinD (or .../Contents/MacOS),
434 // so we have to change the path accordingly.
435 if (binaryUtilName === "certutil") {
436 utilBin = Services.dirsvc.get("GreBinD", Ci.nsIFile);
438 utilBin.append(binaryUtilName + mozinfo.bin_suffix);
439 // If we're testing locally, the above works. If not, the server executable
440 // is in another location.
441 if (!utilBin.exists()) {
442 utilBin = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
443 while (utilBin.path.includes("xpcshell")) {
444 utilBin = utilBin.parent;
446 utilBin.append("bin");
447 utilBin.append(binaryUtilName + mozinfo.bin_suffix);
449 // But maybe we're on Android, where binaries are in /data/local/xpcb.
450 if (!utilBin.exists()) {
451 utilBin.initWithPath("/data/local/xpcb/");
452 utilBin.append(binaryUtilName);
454 Assert.ok(utilBin.exists(), `Binary util ${binaryUtilName} should exist`);
458 function promiseAsyncOpen(chan) {
459 return new Promise(resolve => {
461 new ChannelListener((req, buf, ctx, isCache, cacheId) => {
462 resolve({ req, buf, ctx, isCache, cacheId });