// This file tests async handling of a channel suspended in DoAuthRetry // notifying http-on-modify-request and http-on-before-connect observers. "use strict"; const { HttpServer } = ChromeUtils.importESModule( "resource://testing-common/httpd.sys.mjs" ); ChromeUtils.defineLazyGetter(this, "URL", function () { return "http://localhost:" + httpserv.identity.primaryPort; }); var obs = Services.obs; var requestObserver = null; function AuthPrompt() {} AuthPrompt.prototype = { user: "guest", pass: "guest", QueryInterface: ChromeUtils.generateQI(["nsIAuthPrompt"]), prompt: function ap1_prompt() { do_throw("unexpected prompt call"); }, promptUsernameAndPassword: function promptUP( title, text, realm, savePW, user, pw ) { user.value = this.user; pw.value = this.pass; obs.addObserver(requestObserver, "http-on-before-connect"); obs.addObserver(requestObserver, "http-on-modify-request"); return true; }, promptPassword: function promptPW() { do_throw("unexpected promptPassword call"); }, }; function requestListenerObserver( suspendOnBeforeConnect, suspendOnModifyRequest ) { this.suspendOnModifyRequest = suspendOnModifyRequest; this.suspendOnBeforeConnect = suspendOnBeforeConnect; } requestListenerObserver.prototype = { suspendOnModifyRequest: false, suspendOnBeforeConnect: false, gotOnBeforeConnect: false, resumeOnBeforeConnect: false, gotOnModifyRequest: false, resumeOnModifyRequest: false, QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), observe(subject, topic) { if ( topic === "http-on-before-connect" && subject instanceof Ci.nsIHttpChannel ) { if (this.suspendOnBeforeConnect) { let chan = subject.QueryInterface(Ci.nsIHttpChannel); executeSoon(() => { this.resumeOnBeforeConnect = true; chan.resume(); }); this.gotOnBeforeConnect = true; chan.suspend(); } } else if ( topic === "http-on-modify-request" && subject instanceof Ci.nsIHttpChannel ) { if (this.suspendOnModifyRequest) { let chan = subject.QueryInterface(Ci.nsIHttpChannel); executeSoon(() => { this.resumeOnModifyRequest = true; chan.resume(); }); this.gotOnModifyRequest = true; chan.suspend(); } } }, }; function Requestor() {} Requestor.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIInterfaceRequestor"]), getInterface: function requestor_gi(iid) { if (iid.equals(Ci.nsIAuthPrompt)) { // Allow the prompt to store state by caching it here if (!this.prompt) { this.prompt = new AuthPrompt(); } return this.prompt; } throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE); }, prompt: null, }; var listener = { expectedCode: -1, // Uninitialized onStartRequest: function test_onStartR(request) { try { if (!Components.isSuccessCode(request.status)) { do_throw("Channel should have a success code!"); } if (!(request instanceof Ci.nsIHttpChannel)) { do_throw("Expecting an HTTP channel"); } Assert.equal(request.responseStatus, this.expectedCode); // The request should be succeeded iff we expect 200 Assert.equal(request.requestSucceeded, this.expectedCode == 200); } 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(request, status) { Assert.equal(status, Cr.NS_ERROR_ABORT); if (requestObserver.suspendOnBeforeConnect) { Assert.ok( requestObserver.gotOnBeforeConnect && requestObserver.resumeOnBeforeConnect ); } if (requestObserver.suspendOnModifyRequest) { Assert.ok( requestObserver.gotOnModifyRequest && requestObserver.resumeOnModifyRequest ); } obs.removeObserver(requestObserver, "http-on-before-connect"); obs.removeObserver(requestObserver, "http-on-modify-request"); moveToNextTest(); }, }; function makeChan(url, loadingUrl) { var principal = Services.scriptSecurityManager.createContentPrincipal( Services.io.newURI(loadingUrl), {} ); return NetUtil.newChannel({ uri: url, loadingPrincipal: principal, securityFlags: Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL, contentPolicyType: Ci.nsIContentPolicy.TYPE_OTHER, }); } var tests = [ test_suspend_on_before_connect, test_suspend_on_modify_request, test_suspend_all, ]; var current_test = 0; var httpserv = null; function moveToNextTest() { if (current_test < tests.length - 1) { // First, gotta clear the auth cache Cc["@mozilla.org/network/http-auth-manager;1"] .getService(Ci.nsIHttpAuthManager) .clearAll(); current_test++; tests[current_test](); } else { do_test_pending(); httpserv.stop(do_test_finished); } do_test_finished(); } function run_test() { httpserv = new HttpServer(); httpserv.registerPathHandler("/auth", authHandler); httpserv.start(-1); tests[0](); } function test_suspend_on_auth(suspendOnBeforeConnect, suspendOnModifyRequest) { var chan = makeChan(URL + "/auth", URL); requestObserver = new requestListenerObserver( suspendOnBeforeConnect, suspendOnModifyRequest ); chan.notificationCallbacks = new Requestor(); listener.expectedCode = 200; // OK chan.asyncOpen(listener); do_test_pending(); } function test_suspend_on_before_connect() { test_suspend_on_auth(true, false); } function test_suspend_on_modify_request() { test_suspend_on_auth(false, true); } function test_suspend_all() { test_suspend_on_auth(true, true); } // PATH HANDLERS // /auth function authHandler(metadata, response) { // btoa("guest:guest"), but that function is not available here var expectedHeader = "Basic Z3Vlc3Q6Z3Vlc3Q="; var body; if ( metadata.hasHeader("Authorization") && metadata.getHeader("Authorization") == expectedHeader ) { response.setStatusLine(metadata.httpVersion, 200, "OK, authorized"); response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); body = "success"; } else { // didn't know guest:guest, failure response.setStatusLine(metadata.httpVersion, 401, "Unauthorized"); response.setHeader("WWW-Authenticate", 'Basic realm="secret"', false); body = "failed"; } response.bodyOutputStream.write(body, body.length); }