From 73efc480a8290bf71e6cee38d6334a0e47305d72 Mon Sep 17 00:00:00 2001 From: Kris Maglione Date: Fri, 9 Mar 2018 14:38:41 -0800 Subject: [PATCH] Bug 1444539: Disconnect StreamFilters on redirect. r=mixedpuppy MozReview-Commit-ID: AuCjXTlsFSC --HG-- extra : rebase_source : 40b0836c3efd739020dc59f1ed3f63020e187b47 --- .../test_ext_webRequest_filterResponseData.js | 131 ++++++++++++++++++ .../test/xpcshell/xpcshell-common.ini | 1 + .../extensions/webrequest/PStreamFilter.ipdl | 2 + .../webrequest/StreamFilterChild.cpp | 12 ++ .../extensions/webrequest/StreamFilterChild.h | 1 + .../webrequest/StreamFilterParent.cpp | 30 +++- .../webrequest/StreamFilterParent.h | 2 + 7 files changed, 175 insertions(+), 4 deletions(-) create mode 100644 toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js new file mode 100644 index 000000000000..c2031f293d3e --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_webRequest_filterResponseData.js @@ -0,0 +1,131 @@ +"use strict"; + +XPCOMUtils.defineLazyServiceGetter(this, "proxyService", + "@mozilla.org/network/protocol-proxy-service;1", + "nsIProtocolProxyService"); + +const server = createHttpServer(); +const gHost = "localhost"; +const gPort = server.identity.primaryPort; + +const HOSTS = new Set([ + "example.com", + "example.org", + "example.net", +]); + +for (let host of HOSTS) { + server.identity.add("http", host, 80); +} + +const proxyFilter = { + proxyInfo: proxyService.newProxyInfo("http", gHost, gPort, 0, 4096, null), + + applyFilter(service, channel, defaultProxyInfo, callback) { + if (HOSTS.has(channel.URI.host)) { + callback.onProxyFilterResult(this.proxyInfo); + } else { + callback.onProxyFilterResult(defaultProxyInfo); + } + }, +}; + +proxyService.registerChannelFilter(proxyFilter, 0); +registerCleanupFunction(() => { + proxyService.unregisterChannelFilter(proxyFilter); +}); + +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.setHeader("Access-Control-Allow-Origin", "*"); +}); + +server.registerPathHandler("/dummy", (request, response) => { + response.setStatusLine(request.httpVersion, 200, "OK"); + response.setHeader("Access-Control-Allow-Origin", "*"); + response.write("ok"); +}); + +Cu.importGlobalProperties(["fetch"]); + +add_task(async function() { + const {fetch} = Cu.Sandbox("http://example.com/", {wantGlobalProperties: ["fetch"]}); + + let extension = ExtensionTestUtils.loadExtension({ + background() { + let pending = []; + + browser.webRequest.onBeforeRequest.addListener( + data => { + let filter = browser.webRequest.filterResponseData(data.requestId); + + let url = new URL(data.url); + + if (url.searchParams.get("redirect_uri")) { + pending.push( + new Promise(resolve => { filter.onerror = resolve; }).then(() => { + browser.test.assertEq("Channel redirected", filter.error, + "Got correct error for redirected filter"); + })); + } + + filter.onstart = () => { + filter.write(new TextEncoder().encode(data.url)); + }; + filter.ondata = event => { + let str = new TextDecoder().decode(event.data); + browser.test.assertEq("ok", str, `Got unfiltered data for ${data.url}`); + }; + filter.onstop = () => { + filter.close(); + }; + }, { + urls: [""], + }, + ["blocking"]); + + browser.test.onMessage.addListener(async msg => { + if (msg === "done") { + await Promise.all(pending); + browser.test.notifyPass("stream-filter"); + } + }); + }, + + manifest: { + permissions: [ + "webRequest", + "webRequestBlocking", + "http://example.com/", + "http://example.org/", + ], + }, + }); + + await extension.startup(); + + let results = [ + ["http://example.com/dummy", "http://example.com/dummy"], + ["http://example.org/dummy", "http://example.org/dummy"], + ["http://example.net/dummy", "ok"], + ["http://example.com/redirect?redirect_uri=http://example.com/dummy", "http://example.com/dummy"], + ["http://example.com/redirect?redirect_uri=http://example.org/dummy", "http://example.org/dummy"], + ["http://example.com/redirect?redirect_uri=http://example.net/dummy", "ok"], + ["http://example.net/redirect?redirect_uri=http://example.com/dummy", "http://example.com/dummy"], + ].map(async ([url, expectedResponse]) => { + let resp = await fetch(url); + let text = await resp.text(); + equal(text, expectedResponse, `Expected response for ${url}`); + }); + + await Promise.all(results); + + extension.sendMessage("done"); + await extension.awaitFinish("stream-filter"); + await extension.unload(); + + ChromeUtils.import("resource://gre/modules/Timer.jsm"); + await new Promise(resolve => setTimeout(resolve, 100)); +}); diff --git a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini index 64500348ca75..edfac6b1417e 100644 --- a/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini +++ b/toolkit/components/extensions/test/xpcshell/xpcshell-common.ini @@ -81,6 +81,7 @@ skip-if = os == "android" # checking for telemetry needs to be updated: 1384923 [test_ext_trustworthy_origin.js] [test_ext_topSites.js] skip-if = os == "android" +[test_ext_webRequest_filterResponseData.js] [test_native_manifests.js] subprocess = true skip-if = os == "android" diff --git a/toolkit/components/extensions/webrequest/PStreamFilter.ipdl b/toolkit/components/extensions/webrequest/PStreamFilter.ipdl index 14f6d8d2f8a9..80e03cc8cfd5 100644 --- a/toolkit/components/extensions/webrequest/PStreamFilter.ipdl +++ b/toolkit/components/extensions/webrequest/PStreamFilter.ipdl @@ -18,11 +18,13 @@ parent: async Resume(); async Close(); async Disconnect(); + async Destroy(); child: async Resumed(); async Suspended(); async Closed(); + async Error(nsCString error); async FlushData(); diff --git a/toolkit/components/extensions/webrequest/StreamFilterChild.cpp b/toolkit/components/extensions/webrequest/StreamFilterChild.cpp index 1c00ee20f633..56eed3e0a14d 100644 --- a/toolkit/components/extensions/webrequest/StreamFilterChild.cpp +++ b/toolkit/components/extensions/webrequest/StreamFilterChild.cpp @@ -304,6 +304,18 @@ StreamFilterChild::RecvInitialized(bool aSuccess) } } +IPCResult +StreamFilterChild::RecvError(const nsCString& aError) +{ + mState = State::Error; + if (mStreamFilter) { + mStreamFilter->FireErrorEvent(NS_ConvertUTF8toUTF16(aError)); + mStreamFilter = nullptr; + } + SendDestroy(); + return IPC_OK(); +} + IPCResult StreamFilterChild::RecvClosed() { MOZ_DIAGNOSTIC_ASSERT(mState == State::Closing); diff --git a/toolkit/components/extensions/webrequest/StreamFilterChild.h b/toolkit/components/extensions/webrequest/StreamFilterChild.h index c18976068eb1..2cd6b3842648 100644 --- a/toolkit/components/extensions/webrequest/StreamFilterChild.h +++ b/toolkit/components/extensions/webrequest/StreamFilterChild.h @@ -99,6 +99,7 @@ protected: virtual IPCResult RecvStartRequest() override; virtual IPCResult RecvData(Data&& data) override; virtual IPCResult RecvStopRequest(const nsresult& aStatus) override; + virtual IPCResult RecvError(const nsCString& aError) override; virtual IPCResult RecvClosed() override; virtual IPCResult RecvSuspended() override; diff --git a/toolkit/components/extensions/webrequest/StreamFilterParent.cpp b/toolkit/components/extensions/webrequest/StreamFilterParent.cpp index cf51127358c3..f6e7e73978f8 100644 --- a/toolkit/components/extensions/webrequest/StreamFilterParent.cpp +++ b/toolkit/components/extensions/webrequest/StreamFilterParent.cpp @@ -225,6 +225,7 @@ StreamFilterParent::Broken() RunOnActorThread(FUNC, [=] { if (self->IPCActive()) { + self->mDisconnected = true; self->mState = State::Disconnected; } }); @@ -267,6 +268,15 @@ StreamFilterParent::Destroy() NS_DISPATCH_NORMAL); } +IPCResult +StreamFilterParent::RecvDestroy() +{ + AssertIsActorThread(); + Destroy(); + return IPC_OK(); +} + + IPCResult StreamFilterParent::RecvSuspend() { @@ -311,7 +321,6 @@ StreamFilterParent::RecvResume() } return IPC_OK(); } - IPCResult StreamFilterParent::RecvDisconnect() { @@ -346,6 +355,7 @@ StreamFilterParent::RecvFlushedData() RunOnActorThread(FUNC, [=] { self->mState = State::Disconnected; + self->mDisconnected = true; }); }); return IPC_OK(); @@ -406,7 +416,19 @@ StreamFilterParent::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) mContext = aContext; - if (mState != State::Disconnected) { + if (aRequest != mChannel) { + mDisconnected = true; + + RefPtr self(this); + RunOnActorThread(FUNC, [=] { + if (self->IPCActive()) { + self->mState = State::Disconnected; + CheckResult(self->SendError(NS_LITERAL_CSTRING("Channel redirected"))); + } + }); + } + + if (!mDisconnected) { RefPtr self(this); RunOnActorThread(FUNC, [=] { if (self->IPCActive()) { @@ -439,7 +461,7 @@ StreamFilterParent::OnStopRequest(nsIRequest* aRequest, AssertIsMainThread(); mReceivedStop = true; - if (mState == State::Disconnected) { + if (mDisconnected) { return EmitStopRequest(aStatusCode); } @@ -485,7 +507,7 @@ StreamFilterParent::OnDataAvailable(nsIRequest* aRequest, { AssertIsIOThread(); - if (mState == State::Disconnected) { + if (mDisconnected) { // If we're offloading data in a thread pool, it's possible that we'll // have buffered some additional data while waiting for the buffer to // flush. So, if there's any buffered data left, flush that before we diff --git a/toolkit/components/extensions/webrequest/StreamFilterParent.h b/toolkit/components/extensions/webrequest/StreamFilterParent.h index 791487b96c9b..f098663b9c60 100644 --- a/toolkit/components/extensions/webrequest/StreamFilterParent.h +++ b/toolkit/components/extensions/webrequest/StreamFilterParent.h @@ -93,6 +93,7 @@ protected: virtual IPCResult RecvResume() override; virtual IPCResult RecvClose() override; virtual IPCResult RecvDisconnect() override; + virtual IPCResult RecvDestroy() override; virtual void DeallocPStreamFilterParent() override; @@ -175,6 +176,7 @@ private: bool mReceivedStop; bool mSentStop; + bool mDisconnected = false; nsCOMPtr mContext; uint64_t mOffset;