Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set sw=2 ts=8 et 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/. */
#ifndef mozilla_net_OpaqueResponseUtils_h
#define mozilla_net_OpaqueResponseUtils_h
#include "ipc/EnumSerializer.h"
#include "mozilla/TimeStamp.h"
#include "nsIContentPolicy.h"
#include "nsIEncodedChannel.h"
#include "nsIStreamListener.h"
#include "nsUnknownDecoder.h"
#include "nsMimeTypes.h"
#include "nsIHttpChannel.h"
#include "mozilla/Variant.h"
#include "mozilla/Logging.h"
#include "nsCOMPtr.h"
#include "nsString.h"
#include "nsTArray.h"
class nsIContentSniffer;
namespace mozilla::dom {
class JSValidatorParent;
}
namespace mozilla::ipc {
class Shmem;
}
namespace mozilla::net {
class HttpBaseChannel;
class nsHttpResponseHead;
enum class OpaqueResponseBlockedReason : uint32_t {
ALLOWED_SAFE_LISTED,
ALLOWED_SAFE_LISTED_SPEC_BREAKING,
BLOCKED_BLOCKLISTED_NEVER_SNIFFED,
BLOCKED_206_AND_BLOCKLISTED,
BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN,
BLOCKED_SHOULD_SNIFF
};
enum class OpaqueResponseBlockedTelemetryReason : uint32_t {
MIME_NEVER_SNIFFED,
RESP_206_BLCLISTED,
NOSNIFF_BLC_OR_TEXTP,
RESP_206_NO_FIRST,
AFTER_SNIFF_MEDIA,
AFTER_SNIFF_NOSNIFF,
AFTER_SNIFF_STA_CODE,
AFTER_SNIFF_CT_FAIL,
MEDIA_NOT_INITIAL,
MEDIA_INCORRECT_RESP,
JS_VALIDATION_FAILED
};
enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff };
OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
const nsACString& aContentType, uint16_t aStatus, bool aNoSniff);
OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason(
nsHttpResponseHead& aResponseHead);
// Returns a tuple of (rangeStart, rangeEnd, rangeTotal) from the input range
// header string if succeed.
Result<std::tuple<int64_t, int64_t, int64_t>, nsresult>
ParseContentRangeHeaderString(const nsAutoCString& aRangeStr);
bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead);
LogModule* GetORBLog();
// Helper class to filter data for opaque responses destined for `Window.fetch`.
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 };
public:
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER;
OpaqueResponseBlocker(nsIStreamListener* aNext, HttpBaseChannel* aChannel,
const nsCString& aContentType, bool aNoSniff);
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`
// is the case where validation fails, because the response is neither
// `JavaScript` nor `JSON`, but the framework itself works as intended.
// `Failure` implies that something has gone wrong, such as allocation, etc.
enum class ValidatorResult : uint32_t { JavaScript, JSON, Other, Failure };
private:
virtual ~OpaqueResponseBlocker() = default;
nsresult ValidateJavaScript(HttpBaseChannel* aChannel, nsIURI* aURI,
nsILoadInfo* aLoadInfo);
void ResolveAndProcessData(HttpBaseChannel* aChannel, bool aAllowed,
Maybe<ipc::Shmem>& aSharedData);
void MaybeRunOnStopRequest(HttpBaseChannel* aChannel);
nsCOMPtr<nsIStreamListener> mNext;
const nsCString mContentType;
const bool mNoSniff;
bool mShouldFilter = false;
State mState = State::Sniffing;
nsresult mStatus = NS_OK;
TimeStamp mStartOfJavaScriptValidation;
RefPtr<dom::JSValidatorParent> mJSValidator;
Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()};
};
class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder {
const std::function<void(void*, const uint8_t*, uint32_t)> mCallback;
public:
nsCompressedAudioVideoImageDetector(
nsIStreamListener* aListener,
std::function<void(void*, const uint8_t*, uint32_t)>&& aCallback)
: nsUnknownDecoder(aListener), mCallback(aCallback) {}
protected:
virtual void DetermineContentType(nsIRequest* aRequest) override {
nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest);
if (!httpChannel) {
return;
}
const char* testData = mBuffer;
uint32_t testDataLen = mBufferLen;
// Check if data are compressed.
nsAutoCString decodedData;
// ConvertEncodedData is always called only on a single thread for each
// instance of an object.
nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen);
if (NS_SUCCEEDED(rv)) {
MutexAutoLock lock(mMutex);
decodedData = mDecodedData;
}
if (!decodedData.IsEmpty()) {
testData = decodedData.get();
testDataLen = std::min<uint32_t>(decodedData.Length(), 512u);
}
mCallback(httpChannel, (const uint8_t*)testData, testDataLen);
nsAutoCString contentType;
rv = httpChannel->GetContentType(contentType);
MutexAutoLock lock(mMutex);
if (!contentType.IsEmpty()) {
mContentType = contentType;
} else {
mContentType = UNKNOWN_CONTENT_TYPE;
}
nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel);
if (encodedChannel) {
encodedChannel->SetHasContentDecompressed(true);
}
}
};
} // namespace mozilla::net
namespace IPC {
template <>
struct ParamTraits<mozilla::net::OpaqueResponseBlocker::ValidatorResult>
: public ContiguousEnumSerializerInclusive<
mozilla::net::OpaqueResponseBlocker::ValidatorResult,
mozilla::net::OpaqueResponseBlocker::ValidatorResult::JavaScript,
mozilla::net::OpaqueResponseBlocker::ValidatorResult::Failure> {};
} // namespace IPC
#endif // mozilla_net_OpaqueResponseUtils_h