"use strict"; // Tests whether we can redirect to a moz-extension: url. ChromeUtils.defineESModuleGetters(this, { TestUtils: "resource://testing-common/TestUtils.sys.mjs", }); const server = createHttpServer(); const gServerUrl = `http://localhost:${server.identity.primaryPort}`; server.registerPathHandler("/redirect", (request, response) => { let params = new URLSearchParams(request.queryString); response.setStatusLine(request.httpVersion, 302, "Moved Temporarily"); response.setHeader("Location", params.get("redirect_uri")); response.write("redirecting"); }); server.registerPathHandler("/dummy", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write("ok"); }); server.registerPathHandler("/dummy-2", (request, response) => { response.setStatusLine(request.httpVersion, 200, "OK"); response.write("ok"); }); function onStopListener(channel) { return new Promise(resolve => { let orig = channel.QueryInterface(Ci.nsITraceableChannel).setNewListener({ QueryInterface: ChromeUtils.generateQI([ "nsIRequestObserver", "nsIStreamListener", ]), getFinalURI(request) { let { loadInfo } = request; return (loadInfo && loadInfo.resultPrincipalURI) || request.originalURI; }, onDataAvailable(...args) { orig.onDataAvailable(...args); }, onStartRequest(request) { orig.onStartRequest(request); }, onStopRequest(request, statusCode) { orig.onStopRequest(request, statusCode); let URI = this.getFinalURI(request.QueryInterface(Ci.nsIChannel)); resolve(URI && URI.spec); }, }); }); } async function onModifyListener(originUrl, redirectToUrl) { return TestUtils.topicObserved("http-on-modify-request", (subject, data) => { let channel = subject.QueryInterface(Ci.nsIHttpChannel); return channel.URI && channel.URI.spec == originUrl; }).then(([subject, data]) => { let channel = subject.QueryInterface(Ci.nsIHttpChannel); if (redirectToUrl) { channel.redirectTo(Services.io.newURI(redirectToUrl)); } return channel; }); } function getExtension( accessible = false, background = undefined, blocking = true ) { let manifest = { permissions: ["webRequest", ""], }; if (blocking) { manifest.permissions.push("webRequestBlocking"); } if (accessible) { manifest.web_accessible_resources = ["finished.html"]; } if (!background) { background = () => { // send the extensions public uri to the test. let exturi = browser.runtime.getURL("finished.html"); browser.test.sendMessage("redirectURI", exturi); }; } return ExtensionTestUtils.loadExtension({ manifest, files: { "finished.html": `

redirected!

`.trim(), }, background, }); } async function redirection_test(url, channelRedirectUrl) { // setup our observer let watcher = onModifyListener(url, channelRedirectUrl).then(channel => { return onStopListener(channel); }); let xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.send(); return watcher; } // This test verifies failure without web_accessible_resources. add_task(async function test_redirect_to_non_accessible_resource() { let extension = getExtension(); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`; let result = await redirection_test(url); equal(result, url, `expected no redirect`); await extension.unload(); }); // This test makes a request against a server that redirects with a 302. add_task(async function test_302_redirect_to_extension() { let extension = getExtension(true); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`; let result = await redirection_test(url); equal(result, redirectUrl, "redirect request is finished"); await extension.unload(); }); // This test uses channel.redirectTo during http-on-modify to redirect to the // moz-extension url. add_task(async function test_channel_redirect_to_extension() { let extension = getExtension(true); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let url = `${gServerUrl}/dummy?r=${Math.random()}`; let result = await redirection_test(url, redirectUrl); equal(result, redirectUrl, "redirect request is finished"); await extension.unload(); }); // This test verifies failure without web_accessible_resources. add_task(async function test_content_redirect_to_non_accessible_resource() { let extension = getExtension(); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`; let watcher = onModifyListener(url).then(channel => { return onStopListener(channel); }); let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl: "about:blank", }); equal( contentPage.browser.documentURI.spec, "about:blank", `expected no redirect` ); equal(await watcher, url, "expected no redirect"); await contentPage.close(); await extension.unload(); }); // This test makes a request against a server that redirects with a 302. add_task(async function test_content_302_redirect_to_extension() { let extension = getExtension(true); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let url = `${gServerUrl}/redirect?redirect_uri=${redirectUrl}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`); await contentPage.close(); await extension.unload(); }); // This test uses channel.redirectTo during http-on-modify to redirect to the // moz-extension url. add_task(async function test_content_channel_redirect_to_extension() { let extension = getExtension(true); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let url = `${gServerUrl}/dummy?r=${Math.random()}`; onModifyListener(url, redirectUrl); let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`); await contentPage.close(); await extension.unload(); }); // This test makes a request against a server and tests redirect to another server page. add_task(async function test_extension_302_redirect_web() { function background(serverUrl) { let expectedUrls = ["/redirect", "/dummy"]; let expected = [ "onBeforeRequest", "onHeadersReceived", "onBeforeRedirect", "onBeforeRequest", "onHeadersReceived", "onResponseStarted", "onCompleted", ]; browser.webRequest.onBeforeRequest.addListener( details => { browser.test.assertTrue( details.url.includes(expectedUrls.shift()), "onBeforeRequest url matches" ); browser.test.assertEq( expected.shift(), "onBeforeRequest", "onBeforeRequest matches" ); }, { urls: [serverUrl] } ); browser.webRequest.onHeadersReceived.addListener( details => { browser.test.assertEq( expected.shift(), "onHeadersReceived", "onHeadersReceived matches" ); }, { urls: [serverUrl] } ); browser.webRequest.onResponseStarted.addListener( details => { browser.test.assertEq( expected.shift(), "onResponseStarted", "onResponseStarted matches" ); }, { urls: [serverUrl] } ); browser.webRequest.onBeforeRedirect.addListener( details => { browser.test.assertTrue( details.redirectUrl.includes("/dummy"), "onBeforeRedirect matches redirectUrl" ); browser.test.assertEq( expected.shift(), "onBeforeRedirect", "onBeforeRedirect matches" ); }, { urls: [serverUrl] } ); browser.webRequest.onCompleted.addListener( details => { browser.test.assertTrue( details.url.includes("/dummy"), "onCompleted expected url received" ); browser.test.assertEq( expected.shift(), "onCompleted", "onCompleted matches" ); browser.test.notifyPass("requestCompleted"); }, { urls: [serverUrl] } ); browser.webRequest.onErrorOccurred.addListener( details => { browser.test.log(`onErrorOccurred ${JSON.stringify(details)}`); browser.test.notifyFail("requestCompleted"); }, { urls: [serverUrl] } ); } let extension = getExtension( false, `(${background})("*://${server.identity.primaryHost}/*")`, false ); await extension.startup(); let redirectUrl = `${gServerUrl}/dummy`; let completed = extension.awaitFinish("requestCompleted"); let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal( contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect` ); await completed; await contentPage.close(); await extension.unload(); }); // This test makes a request against a server and tests redirect to another server page, without // onBeforeRedirect. Bug 1448599 add_task(async function test_extension_302_redirect_opening() { let redirectUrl = `${gServerUrl}/dummy`; let expectData = [ { event: "onBeforeRequest", url: `${gServerUrl}/redirect`, }, { event: "onBeforeRequest", url: redirectUrl, }, ]; function background(serverUrl, expected) { browser.webRequest.onBeforeRequest.addListener( details => { let expect = expected.shift(); browser.test.assertEq( expect.event, "onBeforeRequest", "onBeforeRequest event matches" ); browser.test.assertTrue( details.url.startsWith(expect.url), "onBeforeRequest url matches" ); if (expected.length === 0) { browser.test.notifyPass("requestCompleted"); } }, { urls: [serverUrl] } ); } let extension = getExtension( false, `(${background})("*://${server.identity.primaryHost}/*", ${JSON.stringify( expectData )})`, false ); await extension.startup(); let completed = extension.awaitFinish("requestCompleted"); let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal( contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect` ); await completed; await contentPage.close(); await extension.unload(); }); // This test makes a request against a server and tests redirect to another server page, without // onBeforeRedirect. Bug 1448599 add_task(async function test_extension_302_redirect_modify() { let redirectUrl = `${gServerUrl}/dummy`; let expectData = [ { event: "onHeadersReceived", url: `${gServerUrl}/redirect`, }, { event: "onHeadersReceived", url: redirectUrl, }, ]; function background(serverUrl, expected) { browser.webRequest.onHeadersReceived.addListener( details => { let expect = expected.shift(); browser.test.assertEq( expect.event, "onHeadersReceived", "onHeadersReceived event matches" ); browser.test.assertTrue( details.url.startsWith(expect.url), "onHeadersReceived url matches" ); if (expected.length === 0) { browser.test.notifyPass("requestCompleted"); } }, { urls: [""] } ); } let extension = getExtension( false, `(${background})("*://${server.identity.primaryHost}/*", ${JSON.stringify( expectData )})`, false ); await extension.startup(); let completed = extension.awaitFinish("requestCompleted"); let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal( contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect` ); await completed; await contentPage.close(); await extension.unload(); }); // This test makes a request against a server and tests redirect to another server page, without // onBeforeRedirect. Bug 1448599 add_task(async function test_extension_302_redirect_tracing() { let redirectUrl = `${gServerUrl}/dummy`; let expectData = [ { event: "onCompleted", url: redirectUrl, }, ]; function background(serverUrl, expected) { browser.webRequest.onCompleted.addListener( details => { let expect = expected.shift(); browser.test.assertEq( expect.event, "onCompleted", "onCompleted event matches" ); browser.test.assertTrue( details.url.startsWith(expect.url), "onCompleted url matches" ); if (expected.length === 0) { browser.test.notifyPass("requestCompleted"); } }, { urls: [serverUrl] } ); } let extension = getExtension( false, `(${background})("*://${server.identity.primaryHost}/*", ${JSON.stringify( expectData )})`, false ); await extension.startup(); let completed = extension.awaitFinish("requestCompleted"); let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal( contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect` ); await completed; await contentPage.close(); await extension.unload(); }); // This test makes a request against a server and tests webrequest. Currently // disabled due to NS_BINDING_ABORTED happening. add_task(async function test_extension_302_redirect() { let extension = getExtension(true, () => { let myuri = browser.runtime.getURL("*"); let exturi = browser.runtime.getURL("finished.html"); browser.webRequest.onBeforeRedirect.addListener( details => { browser.test.assertEq(details.redirectUrl, exturi, "redirect matches"); }, { urls: ["", myuri] } ); browser.webRequest.onCompleted.addListener( details => { browser.test.assertEq(details.url, exturi, "expected url received"); browser.test.notifyPass("requestCompleted"); }, { urls: ["", myuri] } ); browser.webRequest.onErrorOccurred.addListener( details => { browser.test.log(`onErrorOccurred ${JSON.stringify(details)}`); browser.test.notifyFail("requestCompleted"); }, { urls: ["", myuri] } ); // send the extensions public uri to the test. browser.test.sendMessage("redirectURI", exturi); }); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let completed = extension.awaitFinish("requestCompleted"); let url = `${gServerUrl}/redirect?r=${Math.random()}&redirect_uri=${redirectUrl}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal( contentPage.browser.documentURI.spec, redirectUrl, `expected content redirect` ); await completed; await contentPage.close(); await extension.unload(); }).skip(); // This test makes a request and uses onBeforeRequet to redirect to moz-ext. // Currently disabled due to NS_BINDING_ABORTED happening. add_task(async function test_extension_redirect() { let extension = getExtension(true, () => { let myuri = browser.runtime.getURL("*"); let exturi = browser.runtime.getURL("finished.html"); browser.webRequest.onBeforeRequest.addListener( details => { return { redirectUrl: exturi }; }, { urls: ["", myuri] }, ["blocking"] ); browser.webRequest.onBeforeRedirect.addListener( details => { browser.test.assertEq(details.redirectUrl, exturi, "redirect matches"); }, { urls: ["", myuri] } ); browser.webRequest.onCompleted.addListener( details => { browser.test.assertEq(details.url, exturi, "expected url received"); browser.test.notifyPass("requestCompleted"); }, { urls: ["", myuri] } ); browser.webRequest.onErrorOccurred.addListener( details => { browser.test.log(`onErrorOccurred ${JSON.stringify(details)}`); browser.test.notifyFail("requestCompleted"); }, { urls: ["", myuri] } ); // send the extensions public uri to the test. browser.test.sendMessage("redirectURI", exturi); }); await extension.startup(); let redirectUrl = await extension.awaitMessage("redirectURI"); let completed = extension.awaitFinish("requestCompleted"); let url = `${gServerUrl}/dummy?r=${Math.random()}`; let contentPage = await ExtensionTestUtils.loadContentPage(url, { redirectUrl, }); equal(contentPage.browser.documentURI.spec, redirectUrl, `expected redirect`); await completed; await contentPage.close(); await extension.unload(); }).skip(); add_task(async function test_redirect_with_onHeadersReceived() { let redirectUrl = `${gServerUrl}/dummy-2`; function background(initialUrl, redirectUrl) { browser.webRequest.onCompleted.addListener( () => { browser.test.notifyPass("requestCompleted"); }, { urls: [""] } ); browser.webRequest.onHeadersReceived.addListener( () => { // Redirect to a different URL when we receive the headers of the // initial request. return { redirectUrl }; }, { urls: [initialUrl] }, ["blocking"] ); } let extension = getExtension( false, `(${background})("*://${server.identity.primaryHost}/dummy", "${redirectUrl}")` ); await extension.startup(); let contentPage = await ExtensionTestUtils.loadContentPage( `${gServerUrl}/dummy` ); await extension.awaitFinish("requestCompleted"); equal(contentPage.browser.documentURI.spec, redirectUrl, "expected redirect"); await contentPage.close(); await extension.unload(); }); add_task(async function test_no_redirect_with_location_in_onHeadersReceived() { function background(initialUrl, redirectUrl) { browser.webRequest.onCompleted.addListener( ({ responseHeaders }) => { // Make sure that the `Location` header is set by `onHeadersReceived`. browser.test.assertTrue( responseHeaders.some(({ name, value }) => { return name.toLowerCase() === "location" && value === redirectUrl; }), "Location header is set" ); browser.test.notifyPass("requestCompleted"); }, { urls: [""] }, ["responseHeaders"] ); browser.webRequest.onHeadersReceived.addListener( ({ responseHeaders }) => { return { responseHeaders: [ ...responseHeaders, // Although we set a Location header here, the request shouldn't be // redirected to `redirectUrl` because the status code hasn't been // change (and cannot be changed from there). { name: "Location", value: redirectUrl }, ], }; }, { urls: [initialUrl] }, ["blocking", "responseHeaders"] ); } let extension = getExtension( false, `(${background})("*://${server.identity.primaryHost}/dummy", "${gServerUrl}/dummy-2")` ); await extension.startup(); let initialUrl = `${gServerUrl}/dummy`; let contentPage = await ExtensionTestUtils.loadContentPage(initialUrl); await extension.awaitFinish("requestCompleted"); equal( contentPage.browser.documentURI.spec, initialUrl, "expected no redirect" ); await contentPage.close(); await extension.unload(); });