/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */
// These need to be kept in sync with // "browser.opaqueResponseBlocking.mediaExceptionsStrategy" enumclass OpaqueResponseMediaException { NoExceptions, AllowSome, AllowAll };
staticbool IsOpaqueSafeListedSpecBreakingMIMEType( const nsACString& aContentType, bool aNoSniff) { // Avoid trouble with DASH/HLS. See bug 1698040. if (aContentType.EqualsLiteral(APPLICATION_DASH_XML) ||
aContentType.EqualsLiteral(APPLICATION_MPEGURL) ||
aContentType.EqualsLiteral(AUDIO_MPEG_URL) ||
aContentType.EqualsLiteral(TEXT_VTT)) { returntrue;
}
// Do what Chromium does. This is from bug 1828375, and we should ideally // revert this. if (aContentType.EqualsLiteral(TEXT_PLAIN) && aNoSniff) { returntrue;
}
switch (ConfiguredMediaExceptionsStrategy()) { case OpaqueResponseMediaException::NoExceptions: break; case OpaqueResponseMediaException::AllowSome: if (aContentType.EqualsLiteral(AUDIO_MP3) ||
aContentType.EqualsLiteral(AUDIO_AAC) ||
aContentType.EqualsLiteral(AUDIO_AACP) ||
aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) { returntrue;
} break; case OpaqueResponseMediaException::AllowAll: if (StringBeginsWith(aContentType, "audio/"_ns) ||
StringBeginsWith(aContentType, "video/"_ns) ||
aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) { returntrue;
} break;
}
if (IsOpaqueSafeListedMIMEType(aContentType)) { return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED;
}
// For some MIME types we deviate from spec and allow when we ideally // shouldn't. These are returnened before any blocking takes place. if (IsOpaqueSafeListedSpecBreakingMIMEType(aContentType, aNoSniff)) { return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING;
}
if (IsOpaqueBlockListedNeverSniffedMIMEType(aContentType)) { return OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED;
}
if (aStatus == 206 && IsOpaqueBlockListedMIMEType(aContentType)) { return OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED;
}
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);
}
OpaqueResponseBlocker::OpaqueResponseBlocker(nsIStreamListener* aNext,
HttpBaseChannel* aChannel, const nsCString& aContentType, bool aNoSniff)
: mNext(aNext), mContentType(aContentType), mNoSniff(aNoSniff) { // Storing aChannel as a member is tricky as aChannel owns us and it's // hard to ensure aChannel is alive when we about to use it without // creating a cycle. This is all doable but need some extra efforts. // // So we are just passing aChannel from the caller when we need to use it.
MOZ_ASSERT(aChannel);
if (MOZ_UNLIKELY(MOZ_LOG_TEST(gORBLog, LogLevel::Debug))) {
nsCOMPtr<nsIURI> uri;
aChannel->GetURI(getter_AddRefs(uri)); if (uri) {
LOGORB(" channel=%p, uri=%s", aChannel, uri->GetSpecOrDefault().get());
}
}
MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
MOZ_DIAGNOSTIC_ASSERT(aChannel->CachedOpaqueResponseBlockingPref());
}
if (mState == State::Sniffing) {
Unused << EnsureOpaqueResponseIsAllowedAfterSniff(aRequest);
}
// mState will remain State::Sniffing if we need to wait // for JS validator to make a decision. // // When the state is Sniffing, we can't call mNext->OnStartRequest // because fetch requests need the cancellation to be done // before its FetchDriver::OnStartRequest is called, otherwise it'll // resolve the promise regardless the decision of JS validator. if (mState != State::Sniffing) {
nsresult rv = mNext->OnStartRequest(aRequest); return NS_SUCCEEDED(mStatus) ? rv : mStatus;
}
if (mState == State::Sniffing) { // It is the call to JSValidatorParent::OnStopRequest that will trigger the // JS parser.
mStartOfJavaScriptValidation = TimeStamp::Now();
// The `AfterSniff` check shouldn't be run when // 1. We have made a decision already // 2. The JS validator is running, so we should wait // for its result. if (mState != State::Sniffing || mJSValidator) { return NS_OK;
}
nsCOMPtr<nsILoadInfo> loadInfo;
nsresult rv =
httpBaseChannel->GetLoadInfo(getter_AddRefs<nsILoadInfo>(loadInfo)); if (NS_FAILED(rv)) {
LOGORB("Failed to get LoadInfo");
BlockResponse(httpBaseChannel, rv); return rv;
}
nsCOMPtr<nsIURI> uri;
rv = httpBaseChannel->GetURI(getter_AddRefs<nsIURI>(uri)); if (NS_FAILED(rv)) {
LOGORB("Failed to get uri");
BlockResponse(httpBaseChannel, rv); return rv;
}
switch (httpBaseChannel->PerformOpaqueResponseSafelistCheckAfterSniff(
mContentType, mNoSniff)) { case OpaqueResponse::Block:
BlockResponse(httpBaseChannel, NS_BINDING_ABORTED); return NS_BINDING_ABORTED; case OpaqueResponse::Allow:
AllowResponse(); return NS_OK; case OpaqueResponse::Sniff: case OpaqueResponse::SniffCompressed: break;
}
auto key = [aResult]() { switch (aResult) { case ValidatorResult::JavaScript: return"javascript"_ns; case ValidatorResult::JSON: return"json"_ns; case ValidatorResult::Other: return"other"_ns; case ValidatorResult::Failure: return"failure"_ns;
}
MOZ_ASSERT_UNREACHABLE("Switch statement should be saturated"); return"failure"_ns;
}();
TimeStamp now = TimeStamp::Now();
PROFILER_MARKER_TEXT( "ORB safelist check", NETWORK,
MarkerTiming::Interval(aStartOfValidation, aStartOfJavaScriptValidation),
nsPrintfCString("Receive data for validation (%s)", key.get()));
LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get()); // https://whatpr.org/fetch/1442.html#orb-algorithm, step 15
mJSValidator = dom::JSValidatorParent::Create();
mJSValidator->IsOpaqueResponseAllowed(
[self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI},
loadInfo = nsCOMPtr{aLoadInfo}, startOfValidation = TimeStamp::Now()](
Maybe<ipc::Shmem> aSharedData, ValidatorResult aResult) {
MOZ_LOG(gORBLog, LogLevel::Debug,
("JSValidator resolved for %s with %s",
uri->GetSpecOrDefault().get(),
aSharedData.isSome() ? "true" : "false")); bool allowed = aResult == ValidatorResult::JavaScript; 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: // We'll filter the data out later
self->AllowResponse(); break; default:
MOZ_ASSERT_UNREACHABLE( "We should only ever have Allow or Block here.");
allowed = false;
self->BlockResponse(channel, NS_BINDING_ABORTED); break;
}
self->ResolveAndProcessData(channel, allowed, aSharedData); if (aSharedData.isSome()) {
self->mJSValidator->DeallocShmem(aSharedData.ref());
}
void OpaqueResponseBlocker::ResolveAndProcessData(
HttpBaseChannel* aChannel, bool aAllowed, Maybe<ipc::Shmem>& aSharedData) { if (!aAllowed) { // OpaqueResponseFilter allows us to filter the headers
mNext = new OpaqueResponseFilter(mNext);
}
nsresult rv = OnStartRequest(aChannel);
if (!aAllowed || NS_FAILED(rv)) {
MOZ_ASSERT_IF(!aAllowed, mState == State::Allowed); // No need to call OnDataAvailable because // 1. The input stream is consumed by // OpaqueResponseBlocker::OnDataAvailable already // 2. We don't want to pass any data over
MaybeRunOnStopRequest(aChannel); return;
}
MOZ_ASSERT(mState == State::Allowed);
if (aSharedData.isNothing()) {
MaybeRunOnStopRequest(aChannel); return;
}
if (NS_WARN_IF(NS_FAILED(rv))) {
BlockResponse(aChannel, rv);
MaybeRunOnStopRequest(aChannel); return;
}
// When this line reaches, the state is either State::Allowed or // State::Blocked. The OnDataAvailable call will either call // the next listener or reject the request.
OnDataAvailable(aChannel, input, 0, mem.Size<char>());
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.