mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-27 14:52:16 +00:00
Bug 1900375 - Add setResponseOverride to nsIHttpChannelInternal.idl r=necko-reviewers,kershaw
Depends on D213399 Differential Revision: https://phabricator.services.mozilla.com/D213400
This commit is contained in:
parent
c3bdfb1fa4
commit
073990e32f
@ -105,6 +105,11 @@ class HttpChannelChild final : public PHttpChannelChild,
|
||||
NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
|
||||
NS_IMETHOD SetWebTransportSessionEventListener(
|
||||
WebTransportSessionEventListener* aListener) override;
|
||||
|
||||
NS_IMETHOD SetResponseOverride(
|
||||
nsIReplacedHttpResponse* aReplacedHttpResponse) override {
|
||||
return NS_OK;
|
||||
}
|
||||
// nsISupportsPriority
|
||||
NS_IMETHOD SetPriority(int32_t value) override;
|
||||
// nsIClassOfService
|
||||
|
@ -294,6 +294,11 @@ class InterceptedHttpChannel final
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD SetResponseOverride(
|
||||
nsIReplacedHttpResponse* aReplacedHttpResponse) override {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) override;
|
||||
NS_IMETHOD GetLaunchServiceWorkerStart(TimeStamp* aRetVal) override;
|
||||
|
||||
|
@ -98,6 +98,11 @@ class TRRServiceChannel : public HttpBaseChannel,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD SetResponseOverride(
|
||||
nsIReplacedHttpResponse* aReplacedHttpResponse) override {
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
[[nodiscard]] nsresult OnPush(uint32_t aPushedStreamId,
|
||||
const nsACString& aUrl,
|
||||
const nsACString& aRequestString,
|
||||
|
@ -89,6 +89,7 @@
|
||||
#include "nsIStreamConverterService.h"
|
||||
#include "nsISiteSecurityService.h"
|
||||
#include "nsString.h"
|
||||
#include "nsStringStream.h"
|
||||
#include "mozilla/dom/PerformanceStorage.h"
|
||||
#include "mozilla/dom/ReferrerInfo.h"
|
||||
#include "mozilla/Telemetry.h"
|
||||
@ -880,9 +881,123 @@ nsresult nsHttpChannel::ContinueOnBeforeConnect(bool aShouldUpgrade,
|
||||
return CallOrWaitForResume([](auto* self) { return self->Connect(); });
|
||||
}
|
||||
|
||||
class MOZ_STACK_CLASS AddResponseHeadersToResponseHead final
|
||||
: public nsIHttpHeaderVisitor {
|
||||
public:
|
||||
explicit AddResponseHeadersToResponseHead(nsHttpResponseHead* aResponseHead)
|
||||
: mResponseHead(aResponseHead) {}
|
||||
|
||||
NS_IMETHOD VisitHeader(const nsACString& aHeader,
|
||||
const nsACString& aValue) override {
|
||||
nsAutoCString headerLine = aHeader + ": "_ns + aValue;
|
||||
DebugOnly<nsresult> rv = mResponseHead->ParseHeaderLine(headerLine);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
|
||||
|
||||
// Stub AddRef/Release since this is a stack class.
|
||||
NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override {
|
||||
return ++mRefCnt;
|
||||
}
|
||||
|
||||
NS_IMETHOD_(MozExternalRefCountType) Release(void) override {
|
||||
return --mRefCnt;
|
||||
}
|
||||
|
||||
virtual ~AddResponseHeadersToResponseHead() {
|
||||
MOZ_DIAGNOSTIC_ASSERT(mRefCnt == 0);
|
||||
}
|
||||
|
||||
private:
|
||||
nsHttpResponseHead* mResponseHead;
|
||||
|
||||
nsrefcnt mRefCnt = 0;
|
||||
};
|
||||
|
||||
NS_IMPL_QUERY_INTERFACE(AddResponseHeadersToResponseHead, nsIHttpHeaderVisitor)
|
||||
|
||||
nsresult nsHttpChannel::HandleOverrideResponse() {
|
||||
// Start building a response with the data from mOverrideResponse.
|
||||
mResponseHead = MakeUnique<nsHttpResponseHead>();
|
||||
|
||||
// Apply override response status code and status text.
|
||||
uint32_t statusCode;
|
||||
nsresult rv = mOverrideResponse->GetResponseStatus(&statusCode);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsAutoCString statusText;
|
||||
rv = mOverrideResponse->GetResponseStatusText(statusText);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Hardcoding protocol HTTP/1.1
|
||||
nsPrintfCString line("HTTP/1.1 %u %s", statusCode, statusText.get());
|
||||
rv = mResponseHead->ParseStatusLine(line);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
// Apply override response headers.
|
||||
AddResponseHeadersToResponseHead visitor(mResponseHead.get());
|
||||
rv = mOverrideResponse->VisitResponseHeaders(&visitor);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
if (WillRedirect(*mResponseHead)) {
|
||||
// TODO: Bug 759040 - We should call HandleAsyncRedirect directly here,
|
||||
// to avoid event dispatching latency.
|
||||
LOG(("Skipping read of overridden response redirect entity\n"));
|
||||
return AsyncCall(&nsHttpChannel::HandleAsyncRedirect);
|
||||
}
|
||||
|
||||
// Handle Set-Cookie headers as if the response was from networking.
|
||||
if (nsAutoCString cookie;
|
||||
NS_SUCCEEDED(mResponseHead->GetHeader(nsHttp::Set_Cookie, cookie))) {
|
||||
SetCookie(cookie);
|
||||
nsCOMPtr<nsIParentChannel> parentChannel;
|
||||
NS_QueryNotificationCallbacks(this, parentChannel);
|
||||
if (RefPtr<HttpChannelParent> httpParent = do_QueryObject(parentChannel)) {
|
||||
httpParent->SetCookie(std::move(cookie));
|
||||
}
|
||||
}
|
||||
|
||||
rv = ProcessSecurityHeaders();
|
||||
if (NS_FAILED(rv)) {
|
||||
NS_WARNING("ProcessSecurityHeaders failed, continuing load.");
|
||||
}
|
||||
|
||||
if ((statusCode < 500) && (statusCode != 421)) {
|
||||
ProcessAltService();
|
||||
}
|
||||
|
||||
nsAutoCString body;
|
||||
rv = mOverrideResponse->GetResponseBody(body);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
|
||||
nsCOMPtr<nsIInputStream> stringStream;
|
||||
rv = NS_NewCStringInputStream(getter_AddRefs(stringStream), body);
|
||||
NS_ENSURE_SUCCESS(rv, rv);
|
||||
rv = nsInputStreamPump::Create(getter_AddRefs(mCachePump), stringStream, 0, 0,
|
||||
true);
|
||||
if (NS_FAILED(rv)) {
|
||||
stringStream->Close();
|
||||
return rv;
|
||||
}
|
||||
|
||||
rv = mCachePump->AsyncRead(this);
|
||||
if (NS_FAILED(rv)) return rv;
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult nsHttpChannel::Connect() {
|
||||
LOG(("nsHttpChannel::Connect [this=%p]\n", this));
|
||||
|
||||
// If mOverrideResponse is set, bypass the rest of the connection and reply
|
||||
// immediately with a response built using the data from mOverrideResponse.
|
||||
if (mOverrideResponse) {
|
||||
return HandleOverrideResponse();
|
||||
}
|
||||
|
||||
// Don't allow resuming when cache must be used
|
||||
if (LoadResuming() && (mLoadFlags & LOAD_ONLY_FROM_CACHE)) {
|
||||
LOG(("Resuming from cache is not supported yet"));
|
||||
@ -10734,6 +10849,13 @@ nsHttpChannel::EarlyHint(const nsACString& aLinkHeader,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpChannel::SetResponseOverride(
|
||||
nsIReplacedHttpResponse* aReplacedHttpResponse) {
|
||||
mOverrideResponse = new nsMainThreadPtrHolder<nsIReplacedHttpResponse>(
|
||||
"nsIReplacedHttpResponse", aReplacedHttpResponse);
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP nsHttpChannel::SetWebTransportSessionEventListener(
|
||||
WebTransportSessionEventListener* aListener) {
|
||||
mWebTransportSessionEventListener = aListener;
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include "AlternateServices.h"
|
||||
#include "AutoClose.h"
|
||||
#include "HttpBaseChannel.h"
|
||||
#include "nsIReplacedHttpResponse.h"
|
||||
#include "TimingStruct.h"
|
||||
#include "mozilla/AtomicBitfields.h"
|
||||
#include "mozilla/Atomics.h"
|
||||
@ -206,6 +207,8 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||
NS_IMETHOD SetEarlyHintObserver(nsIEarlyHintObserver* aObserver) override;
|
||||
NS_IMETHOD SetWebTransportSessionEventListener(
|
||||
WebTransportSessionEventListener* aListener) override;
|
||||
NS_IMETHOD SetResponseOverride(
|
||||
nsIReplacedHttpResponse* aReplacedHttpResponse) override;
|
||||
|
||||
void SetWarningReporter(HttpChannelSecurityWarningReporter* aReporter);
|
||||
HttpChannelSecurityWarningReporter* GetWarningReporter();
|
||||
@ -344,6 +347,7 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||
void AsyncContinueProcessResponse();
|
||||
[[nodiscard]] nsresult ContinueProcessResponse1();
|
||||
[[nodiscard]] nsresult ContinueProcessResponse2(nsresult);
|
||||
nsresult HandleOverrideResponse();
|
||||
|
||||
public:
|
||||
void UpdateCacheDisposition(bool aSuccessfulReval, bool aPartialContentUsed);
|
||||
@ -861,6 +865,7 @@ class nsHttpChannel final : public HttpBaseChannel,
|
||||
RefPtr<nsIEarlyHintObserver> mEarlyHintObserver;
|
||||
Maybe<nsCString> mOpenerCallingScriptLocation;
|
||||
RefPtr<WebTransportSessionEventListener> mWebTransportSessionEventListener;
|
||||
nsMainThreadPtrHandle<nsIReplacedHttpResponse> mOverrideResponse;
|
||||
};
|
||||
|
||||
NS_DEFINE_STATIC_IID_ACCESSOR(nsHttpChannel, NS_HTTPCHANNEL_IID)
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
#include "nsISupports.idl"
|
||||
#include "nsILoadInfo.idl"
|
||||
#include "nsIReplacedHttpResponse.idl"
|
||||
#include "nsIRequest.idl"
|
||||
#include "nsITRRSkipReason.idl"
|
||||
|
||||
@ -84,7 +85,7 @@ interface nsIHttpChannelInternal : nsISupports
|
||||
[must_use]
|
||||
void getResponseVersion(out unsigned long major, out unsigned long minor);
|
||||
|
||||
/*
|
||||
/**
|
||||
* Retrieves all security messages from the security message queue
|
||||
* and empties the queue after retrieval
|
||||
*/
|
||||
@ -425,7 +426,7 @@ interface nsIHttpChannelInternal : nsISupports
|
||||
[noscript, infallible]
|
||||
attribute unsigned long lastRedirectFlags;
|
||||
|
||||
// This is use to determine the duration since navigation started.
|
||||
// This is used to determine the duration since navigation started.
|
||||
[noscript] attribute TimeStamp navigationStartTimeStamp;
|
||||
|
||||
/**
|
||||
@ -485,7 +486,7 @@ interface nsIHttpChannelInternal : nsISupports
|
||||
*/
|
||||
[must_use] void setEarlyHintObserver(in nsIEarlyHintObserver aObserver);
|
||||
|
||||
/*
|
||||
/**
|
||||
* id of the EarlyHintPreloader to connect back from PreloadService to
|
||||
* EarlyHintPreloader.
|
||||
*/
|
||||
@ -493,12 +494,12 @@ interface nsIHttpChannelInternal : nsISupports
|
||||
|
||||
[notxpcom, nostdcall] void setConnectionInfo(in nsHttpConnectionInfo aInfo);
|
||||
|
||||
/*
|
||||
* This attribute indicates if the channel was loaded via Proxy.
|
||||
*/
|
||||
/**
|
||||
* This attribute indicates if the channel was loaded via Proxy.
|
||||
*/
|
||||
[must_use] readonly attribute boolean isProxyUsed;
|
||||
|
||||
/**
|
||||
/**
|
||||
* Set mWebTransportSessionEventListener.
|
||||
*/
|
||||
[must_use] void setWebTransportSessionEventListener(
|
||||
@ -518,4 +519,14 @@ interface nsIHttpChannelInternal : nsISupports
|
||||
* overridden header value.
|
||||
*/
|
||||
[must_use] attribute boolean isUserAgentHeaderModified;
|
||||
|
||||
/**
|
||||
* The nsIReplacedHttpResponse will be used to override the response of the
|
||||
* channel. Should be called before connect (http-on-before-connect).
|
||||
* When setResponseOverride is called, the request will bypass the network
|
||||
* and a mocked response based on the nsIReplacedHttpResponse content will
|
||||
* be used instead.
|
||||
*/
|
||||
[must_use] void setResponseOverride(
|
||||
in nsIReplacedHttpResponse aReplacedHttpResponse);
|
||||
};
|
||||
|
@ -192,6 +192,8 @@ skip-if = ["socketprocess_networking"] # Bug 1772209
|
||||
|
||||
["browser_resource_navigation.js"]
|
||||
|
||||
["browser_set_response_override.js"]
|
||||
|
||||
["browser_speculative_connection_link_header.js"]
|
||||
|
||||
["browser_test_data_channel_observer.js"]
|
||||
|
135
netwerk/test/browser/browser_set_response_override.js
Normal file
135
netwerk/test/browser/browser_set_response_override.js
Normal file
@ -0,0 +1,135 @@
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
// Test for the nsIHttpChannelInternal helper setResponseOverride.
|
||||
// which allows to bypass the network for a request before connect, and
|
||||
// reply with a mocked response instead.
|
||||
add_task(async function test_set_response_override() {
|
||||
let observer = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
|
||||
observe(aSubject, aTopic) {
|
||||
aSubject = aSubject.QueryInterface(Ci.nsIHttpChannelInternal);
|
||||
if (
|
||||
aTopic == "http-on-before-connect" &&
|
||||
aSubject.URI.spec ==
|
||||
"https://example.com/browser/netwerk/test/browser/dummy.html"
|
||||
) {
|
||||
const replacedHttpResponse = Cc[
|
||||
"@mozilla.org/network/replaced-http-response;1"
|
||||
].createInstance(Ci.nsIReplacedHttpResponse);
|
||||
replacedHttpResponse.responseStatus = 200;
|
||||
replacedHttpResponse.responseStatusText = "Och Aye";
|
||||
replacedHttpResponse.responseBody =
|
||||
"<div id=from-response-override>From setResponseOverride";
|
||||
replacedHttpResponse.setResponseHeader(
|
||||
"some-header",
|
||||
"some-value",
|
||||
false
|
||||
);
|
||||
replacedHttpResponse.setResponseHeader(
|
||||
"Set-Cookie",
|
||||
"foo=bar;Path=/",
|
||||
false
|
||||
);
|
||||
aSubject.setResponseOverride(replacedHttpResponse);
|
||||
}
|
||||
},
|
||||
};
|
||||
Services.obs.addObserver(observer, "http-on-before-connect");
|
||||
|
||||
const onTabLoaded = BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "https://example.com/browser/netwerk/test/browser/dummy.html",
|
||||
waitForLoad: true,
|
||||
},
|
||||
async function (browser) {
|
||||
await ContentTask.spawn(browser, [], async function () {
|
||||
Assert.ok(
|
||||
!!content.document.getElementById("from-response-override"),
|
||||
"Page was loaded using the response override"
|
||||
);
|
||||
Assert.equal(
|
||||
content.document.cookie,
|
||||
"foo=bar",
|
||||
"Cookie was set from the response override headers"
|
||||
);
|
||||
|
||||
// Perform another request to the same URL to check status and headers override.
|
||||
const response = await content.fetch(
|
||||
"https://example.com/browser/netwerk/test/browser/dummy.html"
|
||||
);
|
||||
Assert.equal(
|
||||
response.status,
|
||||
200,
|
||||
"Status was set from the response override"
|
||||
);
|
||||
Assert.equal(
|
||||
response.statusText,
|
||||
"Och Aye",
|
||||
"Status text was set from the response override"
|
||||
);
|
||||
Assert.equal(
|
||||
response.headers.get("some-header"),
|
||||
"some-value",
|
||||
"same-header header was set from the response override"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
await onTabLoaded;
|
||||
|
||||
Services.obs.removeObserver(observer, "http-on-before-connect");
|
||||
});
|
||||
|
||||
// Test that a response override with 302 Found status + Location header
|
||||
// redirects to the URL specified in the Location header.
|
||||
add_task(async function test_set_response_override_redirects() {
|
||||
let observer = {
|
||||
QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
|
||||
observe(aSubject, aTopic) {
|
||||
aSubject = aSubject.QueryInterface(Ci.nsIHttpChannelInternal);
|
||||
if (
|
||||
aTopic == "http-on-before-connect" &&
|
||||
aSubject.URI.spec ==
|
||||
"https://example.com/browser/netwerk/test/browser/dummy.html"
|
||||
) {
|
||||
const replacedHttpResponse = Cc[
|
||||
"@mozilla.org/network/replaced-http-response;1"
|
||||
].createInstance(Ci.nsIReplacedHttpResponse);
|
||||
replacedHttpResponse.responseStatus = 302;
|
||||
replacedHttpResponse.responseStatusText = "Found";
|
||||
replacedHttpResponse.setResponseHeader(
|
||||
"Location",
|
||||
"https://example.com/browser/netwerk/test/browser/dummy.html?redirected=true",
|
||||
false
|
||||
);
|
||||
aSubject.setResponseOverride(replacedHttpResponse);
|
||||
}
|
||||
},
|
||||
};
|
||||
Services.obs.addObserver(observer, "http-on-before-connect");
|
||||
|
||||
const onTabLoaded = BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "https://example.com/browser/netwerk/test/browser/dummy.html",
|
||||
waitForLoad: true,
|
||||
},
|
||||
async function (browser) {
|
||||
await ContentTask.spawn(browser, [], async function () {
|
||||
Assert.equal(
|
||||
content.location.href,
|
||||
"https://example.com/browser/netwerk/test/browser/dummy.html?redirected=true",
|
||||
"Navigation was redirected based on the overridden response"
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
await onTabLoaded;
|
||||
|
||||
Services.obs.removeObserver(observer, "http-on-before-connect");
|
||||
});
|
Loading…
Reference in New Issue
Block a user