Bug 1823877 - Part 1: Filter opaque results from fetch() in the parent for ORB. r=sefeng,smaug,necko-reviewers,edenchuang,valentin

We make sure to not send any data to the content process in case of
fetching an opaque resource. This is way to remain more web
compatible, but is also in conflict with the ORB specification.

Differential Revision: https://phabricator.services.mozilla.com/D173454
This commit is contained in:
Andreas Farre 2023-05-10 14:35:52 +00:00
parent f317ffb43d
commit 476835e1c7
12 changed files with 421 additions and 45 deletions

View File

@ -1737,6 +1737,29 @@
value: @IS_EARLY_BETA_OR_EARLIER@
mirror: always
# This pref controls how filtering of opaque responses for calls to `Window.fetch`.
# (and similar) is performed in the parent process. This is intended to make sure
# that data that would be filtered in a content process never actually reaches that
# content process.
# See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
# 0) Don't filter in the parent process at all, and let content processes handle
# opaque filtering. Regardless of if ORB is enabled or not. N.B. that if ORB
# is enabled opaque responses will be blocked.
# 1) If ORB is enabled, in the parent process, filter the responses that ORB allows.
# N.B. any responses ORB doesn't allow will not send data to a content process
# since they will return a NetworkError. If the request is allowed by ORB, the
# internal response will be intact and sent to the content process as is.
# 2) If ORB is enabled, in the parent process, filter the responses that ORB blocks,
# if they were issued by `Window.fetch` (and similar).
# 3) Filter all responses in the parent, regardless of if ORB is enabled or not.
# This means that opaque responses coming from `Window.fetch` won't even be
# considered for being blocked by ORB.
- name: browser.opaqueResponseBlocking.filterFetchResponse
type: uint32_t
value: 2
mirror: always
do_not_use_directly: true
# When this pref is enabled, <object> and <embed> elements will create
# synthetic documents when the resource type they're loading is an image.
- name: browser.opaqueResponseBlocking.syntheticBrowsingContext

View File

@ -52,6 +52,7 @@
#include "nsContentSecurityManager.h"
#include "nsContentSecurityUtils.h"
#include "nsContentUtils.h"
#include "nsDebug.h"
#include "nsEscape.h"
#include "nsGlobalWindowOuter.h"
#include "nsHttpChannel.h"
@ -104,9 +105,9 @@
#include "nsQueryObject.h"
using mozilla::dom::RequestMode;
extern mozilla::LazyLogModule gORBLog;
#define LOGORB(msg, ...) \
MOZ_LOG(gORBLog, LogLevel::Debug, \
#define LOGORB(msg, ...) \
MOZ_LOG(GetORBLog(), LogLevel::Debug, \
("%s: %p " msg, __func__, this, ##__VA_ARGS__))
namespace mozilla {
@ -184,6 +185,17 @@ class AddHeadersToChannelVisitor final : public nsIHttpHeaderVisitor {
NS_IMPL_ISUPPORTS(AddHeadersToChannelVisitor, nsIHttpHeaderVisitor)
static OpaqueResponseFilterFetch ConfiguredFilterFetchResponseBehaviour() {
uint32_t pref = StaticPrefs::
browser_opaqueResponseBlocking_filterFetchResponse_DoNotUseDirectly();
if (NS_WARN_IF(pref >
static_cast<uint32_t>(OpaqueResponseFilterFetch::All))) {
return OpaqueResponseFilterFetch::All;
}
return static_cast<OpaqueResponseFilterFetch>(pref);
}
HttpBaseChannel::HttpBaseChannel()
: mReportCollector(new ConsoleReportCollector()),
mHttpHandler(gHttpHandler),
@ -3017,6 +3029,19 @@ nsresult HttpBaseChannel::ValidateMIMEType() {
return NS_OK;
}
bool HttpBaseChannel::ShouldFilterOpaqueResponse(
OpaqueResponseFilterFetch aFilterType) const {
MOZ_DIAGNOSTIC_ASSERT(ShouldBlockOpaqueResponse());
if (!mLoadInfo || ConfiguredFilterFetchResponseBehaviour() != aFilterType) {
return false;
}
// We should filter a response in the parent if it is opaque and is the result
// of a fetch() function from the Fetch specification.
return mLoadInfo->InternalContentPolicyType() == nsIContentPolicy::TYPE_FETCH;
}
bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
if (!mURI || !mResponseHead || !mLoadInfo) {
// if there is no uri, no response head or no loadInfo, then there is
@ -3036,6 +3061,7 @@ bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
// Check if the response is a opaque response, which means requestMode should
// be RequestMode::No_cors and responseType should be ResponseType::Opaque.
nsContentPolicyType contentPolicy = mLoadInfo->InternalContentPolicyType();
// Skip the RequestMode would be RequestMode::Navigate
if (contentPolicy == nsIContentPolicy::TYPE_DOCUMENT ||
contentPolicy == nsIContentPolicy::TYPE_SUBDOCUMENT ||
@ -3102,6 +3128,41 @@ bool HttpBaseChannel::ShouldBlockOpaqueResponse() const {
return true;
}
OpaqueResponse HttpBaseChannel::BlockOrFilterOpaqueResponse(
OpaqueResponseBlocker* aORB, const nsAString& aReason, const char* aFormat,
...) {
const bool shouldFilter =
ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::BlockedByORB);
if (MOZ_UNLIKELY(MOZ_LOG_TEST(GetORBLog(), LogLevel::Debug))) {
va_list ap;
va_start(ap, aFormat);
nsVprintfCString logString(aFormat, ap);
va_end(ap);
LOGORB("%s: %s", shouldFilter ? "Filtered" : "Blocked", logString.get());
}
if (shouldFilter) {
// The existence of `mORB` depends on `BlockOrFilterOpaqueResponse` being
// called before or after sniffing has completed.
// Another requirement is that `OpaqueResponseFilter` must come after
// `OpaqueResponseBlocker`, which is why in the case of having an
// `OpaqueResponseBlocker` we let it handle creating an
// `OpaqueResponseFilter`.
if (aORB) {
MOZ_DIAGNOSTIC_ASSERT(!mORB || aORB == mORB);
aORB->FilterResponse();
} else {
mListener = new OpaqueResponseFilter(mListener);
}
return OpaqueResponse::Allow;
}
LogORBError(aReason);
return OpaqueResponse::Block;
}
// The specification for ORB is currently being written:
// https://whatpr.org/fetch/1442.html#orb-algorithm
// The `opaque-response-safelist check` is implemented in:
@ -3113,13 +3174,40 @@ OpaqueResponse
HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
MOZ_ASSERT(XRE_IsParentProcess());
// https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
if (!ShouldBlockOpaqueResponse()) {
return OpaqueResponse::Allow;
}
// Regardless of if ORB is enabled or not, we check if we should filter the
// response in the parent. This way data won't reach a content process that
// will create a filtered `Response` object. This is enabled when
// 'browser.opaqueResponseBlocking.filterFetchResponse' is
// `OpaqueResponseFilterFetch::All`.
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::All)) {
mListener = new OpaqueResponseFilter(mListener);
// If we're filtering a response in the parent, there will be no data to
// determine if it should be blocked or not so the only option we have is to
// allow it.
return OpaqueResponse::Allow;
}
if (!mCachedOpaqueResponseBlockingPref) {
return OpaqueResponse::Allow;
}
// https://whatpr.org/fetch/1442.html#http-fetch, step 6.4
if (!ShouldBlockOpaqueResponse()) {
return OpaqueResponse::Allow;
// If ORB is enabled, we check if we should filter the response in the parent.
// This way data won't reach a content process that will create a filtered
// `Response` object. We allow ORB to determine if the response should be
// blocked or filtered, but regardless no data should reach the content
// process. This is enabled when
// 'browser.opaqueResponseBlocking.filterFetchResponse' is
// `OpaqueResponseFilterFetch::AllowedByORB`.
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque
if (ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch::AllowedByORB)) {
mListener = new OpaqueResponseFilter(mListener);
}
Telemetry::ScalarAdd(
@ -3147,24 +3235,22 @@ HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
// Step 3.1
return OpaqueResponse::Allow;
case OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED:
// Step 3.2
LOGORB("Blocked: BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
LogORBError(
u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB, u"mimeType is an opaque-blocklisted-never-sniffed MIME type"_ns,
"BLOCKED_BLOCKLISTED_NEVER_SNIFFED");
case OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED:
// Step 3.3
LOGORB("Blocked: BLOCKED_206_AND_BLOCKEDLISTED");
LogORBError(
u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB,
u"response's status is 206 and mimeType is an opaque-blocklisted MIME type"_ns,
"BLOCKED_206_AND_BLOCKEDLISTED");
case OpaqueResponseBlockedReason::
BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN:
// Step 3.4
LOGORB("Blocked: BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
LogORBError(
u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB,
u"nosniff is true and mimeType is an opaque-blocklisted MIME type or its essence is 'text/plain'"_ns,
"BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN");
default:
break;
}
@ -3185,9 +3271,9 @@ HttpBaseChannel::PerformOpaqueResponseSafelistCheckBeforeSniff() {
// Step 5
if (mResponseHead->Status() == 206 &&
!IsFirstPartialResponse(*mResponseHead)) {
LOGORB("Blocked: Is not a valid partial response given 0");
LogORBError(u"response status is 206 and not first partial response"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB, u"response status is 206 and not first partial response"_ns,
"Is not a valid partial response given 0");
}
// Setup for steps 6, 7, 8 and 10.
@ -3242,25 +3328,22 @@ OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
bool isMediaRequest;
mLoadInfo->GetIsMediaRequest(&isMediaRequest);
if (isMediaRequest) {
LOGORB("Blocked: media request");
LogORBError(u"after sniff: media request"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(mORB, u"after sniff: media request"_ns,
"media request");
}
// Step 11
if (aNoSniff) {
LOGORB("Blocked: nosniff");
LogORBError(u"after sniff: nosniff is true"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(mORB, u"after sniff: nosniff is true"_ns,
"nosniff");
}
// Step 12
if (mResponseHead &&
(mResponseHead->Status() < 200 || mResponseHead->Status() > 299)) {
LOGORB("Blocked: status code (%d) is not allowed ",
mResponseHead->Status());
LogORBError(u"after sniff: status code is not in allowed range"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB, u"after sniff: status code is not in allowed range"_ns,
"status code (%d) is not allowed", mResponseHead->Status());
}
// Step 13
@ -3273,10 +3356,10 @@ OpaqueResponse HttpBaseChannel::PerformOpaqueResponseSafelistCheckAfterSniff(
if (StringBeginsWith(aContentType, "image/"_ns) ||
StringBeginsWith(aContentType, "video/"_ns) ||
StringBeginsWith(aContentType, "audio/"_ns)) {
LOGORB("Blocked: ContentType is image/video/audio");
LogORBError(
u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns);
return OpaqueResponse::Block;
return BlockOrFilterOpaqueResponse(
mORB,
u"after sniff: content-type declares image/video/audio, but sniffing fails"_ns,
"ContentType is image/video/audio");
}
return OpaqueResponse::Sniff;

View File

@ -10,6 +10,7 @@
#include <utility>
#include "OpaqueResponseUtils.h"
#include "mozilla/AtomicBitfields.h"
#include "mozilla/Atomics.h"
#include "mozilla/dom/DOMTypes.h"
@ -41,6 +42,7 @@
#include "nsIURI.h"
#include "nsIUploadChannel2.h"
#include "nsStringEnumerator.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsThreadUtils.h"
@ -80,7 +82,9 @@ enum CacheDisposition : uint8_t {
kCacheUnknown = 5
};
enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff };
// These need to be kept in sync with
// "browser.opaqueResponseBlocking.filterFetchResponse"
enum class OpaqueResponseFilterFetch { Never, AllowedByORB, BlockedByORB, All };
/*
* This class is a partial implementation of nsIHttpChannel. It contains code
@ -557,7 +561,7 @@ class HttpBaseChannel : public nsHashPropertyBag,
// https://fetch.spec.whatwg.org/#concept-request-tainted-origin
bool HasRedirectTaintedOrigin() { return LoadTaintedOriginFlag(); }
bool ChannelBlockedByOpaqueResponse() {
bool ChannelBlockedByOpaqueResponse() const {
return mChannelBlockedByOpaqueResponse;
}
bool CachedOpaqueResponseBlockingPref() const {
@ -649,7 +653,11 @@ class HttpBaseChannel : public nsHashPropertyBag,
nsresult ValidateMIMEType();
bool ShouldFilterOpaqueResponse(OpaqueResponseFilterFetch aFilterType) const;
bool ShouldBlockOpaqueResponse() const;
OpaqueResponse BlockOrFilterOpaqueResponse(OpaqueResponseBlocker* aORB,
const nsAString& aReason,
const char* aFormat, ...);
OpaqueResponse PerformOpaqueResponseSafelistCheckBeforeSniff();

View File

@ -1211,7 +1211,7 @@ HttpChannelParent::OnStartRequest(nsIRequest* aRequest) {
responseHead = &cleanedUpResponseHead;
}
if (chan && chan->ChannelBlockedByOpaqueResponse() &&
if (chan->ChannelBlockedByOpaqueResponse() &&
chan->CachedOpaqueResponseBlockingPref()) {
responseHead->ClearHeaders();
}

View File

@ -14,10 +14,13 @@
#include "nsHttpResponseHead.h"
#include "nsISupports.h"
#include "nsMimeTypes.h"
#include "nsStreamUtils.h"
#include "nsThreadUtils.h"
#include "nsStringStream.h"
#include "HttpBaseChannel.h"
static mozilla::LazyLogModule gORBLog("ORB");
#define LOGORB(msg, ...) \
MOZ_LOG(gORBLog, LogLevel::Debug, \
("%s: %p " msg, __func__, this, ##__VA_ARGS__))
@ -192,6 +195,52 @@ bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead) {
return responseFirstBytePos == 0;
}
LogModule* GetORBLog() { return gORBLog; }
OpaqueResponseFilter::OpaqueResponseFilter(nsIStreamListener* aNext)
: mNext(aNext) {
LOGORB();
}
NS_IMETHODIMP
OpaqueResponseFilter::OnStartRequest(nsIRequest* aRequest) {
LOGORB();
nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest);
MOZ_ASSERT(httpBaseChannel);
nsHttpResponseHead* responseHead = httpBaseChannel->GetResponseHead();
if (responseHead) {
// Filtered opaque responses doesn't need headers, so we just drop them.
responseHead->ClearHeaders();
}
mNext->OnStartRequest(aRequest);
return NS_OK;
}
NS_IMETHODIMP
OpaqueResponseFilter::OnDataAvailable(nsIRequest* aRequest,
nsIInputStream* aInputStream,
uint64_t aOffset, uint32_t aCount) {
LOGORB();
uint32_t result;
// No data for filtered opaque responses should reach the content process, so
// we just discard them.
return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount,
&result);
}
NS_IMETHODIMP
OpaqueResponseFilter::OnStopRequest(nsIRequest* aRequest,
nsresult aStatusCode) {
LOGORB();
mNext->OnStopRequest(aRequest, aStatusCode);
return NS_OK;
}
NS_IMPL_ISUPPORTS(OpaqueResponseFilter, nsIStreamListener, nsIRequestObserver)
OpaqueResponseBlocker::OpaqueResponseBlocker(nsIStreamListener* aNext,
HttpBaseChannel* aChannel,
const nsCString& aContentType,
@ -345,6 +394,17 @@ nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff(
return ValidateJavaScript(httpBaseChannel, uri, loadInfo);
}
OpaqueResponse
OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
HttpBaseChannel* aChannel, bool aAllow) {
if (aAllow) {
return OpaqueResponse::Allow;
}
return aChannel->BlockOrFilterOpaqueResponse(
this, u"Javascript validation failed"_ns, "Javascript validation failed");
}
static void RecordTelemetry(const TimeStamp& aStartOfValidation,
const TimeStamp& aStartOfJavaScriptValidation,
OpaqueResponseBlocker::ValidatorResult aResult) {
@ -428,12 +488,27 @@ nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel,
uri->GetSpecOrDefault().get(),
aSharedData.isSome() ? "true" : "false"));
bool allowed = aResult == ValidatorResult::JavaScript;
if (allowed) {
self->AllowResponse();
} else {
self->BlockResponse(channel, NS_ERROR_FAILURE);
channel->LogORBError(u"Javascript validation failed"_ns);
switch (self->EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
channel, allowed)) {
case OpaqueResponse::Allow:
// It's possible that the JS validation failed for this request,
// however we decided that we need to filter the response instead
// of blocking. So we set allowed to true manually when that's the
// case.
allowed = true;
self->AllowResponse();
break;
case OpaqueResponse::Block:
self->BlockResponse(channel, NS_ERROR_FAILURE);
break;
default:
MOZ_ASSERT_UNREACHABLE(
"We should only ever have Allow or Block here.");
allowed = false;
self->BlockResponse(channel, NS_ERROR_FAILURE);
break;
}
self->ResolveAndProcessData(channel, allowed, aSharedData);
if (aSharedData.isSome()) {
self->mJSValidator->DeallocShmem(aSharedData.ref());
@ -470,6 +545,18 @@ void OpaqueResponseBlocker::BlockResponse(HttpBaseChannel* aChannel,
"OpaqueResponseBlocker::BlockResponse"_ns);
}
void OpaqueResponseBlocker::FilterResponse() {
MOZ_ASSERT(mState == State::Sniffing);
if (mShouldFilter) {
return;
}
mShouldFilter = true;
mNext = new OpaqueResponseFilter(mNext);
}
void OpaqueResponseBlocker::ResolveAndProcessData(
HttpBaseChannel* aChannel, bool aAllowed, Maybe<ipc::Shmem>& aSharedData) {
nsresult rv = OnStartRequest(aChannel);

View File

@ -24,7 +24,6 @@
#include "nsTArray.h"
class nsIContentSniffer;
static mozilla::LazyLogModule gORBLog("ORB");
namespace mozilla::dom {
class JSValidatorParent;
@ -47,6 +46,8 @@ enum class OpaqueResponseBlockedReason : uint32_t {
BLOCKED_SHOULD_SNIFF
};
enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff };
OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
const nsACString& aContentType, uint16_t aStatus, bool aNoSniff);
@ -60,6 +61,24 @@ ParseContentRangeHeaderString(const nsAutoCString& aRangeStr);
bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead);
LogModule* GetORBLog();
// Helper class to filter data for opaque responses destined for `Window.fetch`.
// See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque.
class OpaqueResponseFilter final : public nsIStreamListener {
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER;
explicit OpaqueResponseFilter(nsIStreamListener* aNext);
private:
virtual ~OpaqueResponseFilter() = default;
nsCOMPtr<nsIStreamListener> mNext;
};
class OpaqueResponseBlocker final : public nsIStreamListener {
enum class State { Sniffing, Allowed, Blocked };
@ -74,9 +93,13 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
bool IsSniffing() const;
void AllowResponse();
void BlockResponse(HttpBaseChannel* aChannel, nsresult aStatus);
void FilterResponse();
nsresult EnsureOpaqueResponseIsAllowedAfterSniff(nsIRequest* aRequest);
OpaqueResponse EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation(
HttpBaseChannel* aChannel, bool aAllow);
// The four possible results for validation. `JavaScript` and `JSON` are
// self-explanatory. `JavaScript` is the only successful result, in the sense
// that it will allow the opaque response, whereas `JSON` will block. `Other`
@ -100,6 +123,7 @@ class OpaqueResponseBlocker final : public nsIStreamListener {
const nsCString mContentType;
const bool mNoSniff;
bool mShouldFilter = false;
State mState = State::Sniffing;
nsresult mStatus = NS_OK;

View File

@ -0,0 +1,3 @@
prefs: [browser.opaqueResponseBlocking: true,
browser.opaqueResponseBlocking.filterFetchResponse:2]

View File

@ -0,0 +1,19 @@
// META: script=/fetch/orb/resources/utils.js
// META: script=resources/utils.js
const url =
"http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources/image.png";
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(
url,
{ headers: new Headers([["Range", "bytes 10-99"]]) },
header("Content-Range", "bytes 10-99/1010"),
"slice(10,100)",
"status(206)"
),
"ORB should filter opaque range of image/png not starting at zero, that isn't subsequent"
);

View File

@ -0,0 +1,48 @@
// META: script=/fetch/orb/resources/utils.js
// META: script=resources/utils.js
const path = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources";
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(`${path}/font.ttf`, null, contentType("font/ttf")),
"ORB should filter opaque font/ttf"
);
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(`${path}/text.txt`, null, contentType("text/plain")),
"ORB should filter opaque text/plain"
);
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(`${path}/data.json`, null, contentType("application/json")),
"ORB should filter opaque application/json (non-empty)"
);
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(`${path}/empty.json`, null, contentType("application/json")),
"ORB should filter opaque application/json (empty)"
);
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(
`${path}/data_non_ascii.json`,
null,
contentType("application/json")
),
"ORB should filter opaque application/json which contains non ascii characters"
);

View File

@ -0,0 +1,30 @@
// META: script=/fetch/orb/resources/utils.js
// META: script=resources/utils.js
const path = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources";
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(
`${path}/data.json`,
null,
contentType("application/json"),
contentTypeOptions("nosniff")
),
"ORB should filter opaque-response-blocklisted MIME type with nosniff"
);
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(
`${path}/data.json`,
null,
contentType(""),
contentTypeOptions("nosniff")
),
"ORB should filter opaque response with empty Content-Type and nosniff"
);

View File

@ -0,0 +1,21 @@
function promise_internal_response_is_filtered(fetchPromise, message) {
return promise_test(async () => {
const response = await fetchPromise;
// A parent filtered opaque response is defined here as a response that isn't just an
// opaque response, but also where the internal response has been made unavailable.
// `Response.cloneUnfiltered` is used to inspect the state of the internal response,
// which is exactly what we want to be missing in this case.
const unfiltered = SpecialPowers.wrap(response).cloneUnfiltered();
assert_equals(
await SpecialPowers.unwrap(unfiltered).text(),
"",
"The internal response should be empty"
);
assert_equals(
Array.from(await SpecialPowers.unwrap(unfiltered).headers).length,
0,
"The internal response should have no headers"
);
}, message);
}

View File

@ -0,0 +1,30 @@
// META: script=/fetch/orb/resources/utils.js
// META: script=resources/utils.js
const path = "http://{{domains[www1]}}:{{ports[http][0]}}/fetch/orb/resources";
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(
`${path}/data.json`,
null,
contentType("application/json"),
"status(206)"
),
"ORB should filter opaque-response-blocklisted MIME type with status 206"
);
// Due to web compatibility we filter opaque Response object from the
// fetch() function in the Fetch specification. See Bug 1823877. This
// might be removed in the future.
promise_internal_response_is_filtered(
fetchORB(
`${path}/data.json`,
null,
contentType("application/json"),
"status(302)"
),
"ORB should filter opaque range of image/png not starting at zero, that isn't subsequent"
);