OpaqueResponseUtils.h (6465B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set sw=2 ts=8 et tw=80 : */ 3 4 /* This Source Code Form is subject to the terms of the Mozilla Public 5 * License, v. 2.0. If a copy of the MPL was not distributed with this 6 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 7 8 #ifndef mozilla_net_OpaqueResponseUtils_h 9 #define mozilla_net_OpaqueResponseUtils_h 10 11 #include "ipc/EnumSerializer.h" 12 #include "mozilla/TimeStamp.h" 13 #include "nsIContentPolicy.h" 14 #include "nsIEncodedChannel.h" 15 #include "nsIStreamListener.h" 16 #include "nsUnknownDecoder.h" 17 #include "nsMimeTypes.h" 18 #include "nsIHttpChannel.h" 19 20 #include "mozilla/Logging.h" 21 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h" 22 23 #include "nsCOMPtr.h" 24 #include "nsString.h" 25 #include "nsTArray.h" 26 27 class nsIContentSniffer; 28 29 namespace mozilla::dom { 30 class JSValidatorParent; 31 } 32 33 namespace mozilla::ipc { 34 class Shmem; 35 } 36 37 namespace mozilla::net { 38 39 class HttpBaseChannel; 40 class nsHttpResponseHead; 41 42 enum class OpaqueResponseBlockedReason : uint32_t { 43 ALLOWED_SAFE_LISTED, 44 ALLOWED_SAFE_LISTED_SPEC_BREAKING, 45 BLOCKED_BLOCKLISTED_NEVER_SNIFFED, 46 BLOCKED_206_AND_BLOCKLISTED, 47 BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN, 48 BLOCKED_SHOULD_SNIFF 49 }; 50 51 using OpaqueResponseBlockedTelemetryReason = glean::orb::BlockReasonLabel; 52 53 enum class OpaqueResponse { Block, Allow, SniffCompressed, Sniff }; 54 55 OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason( 56 const nsACString& aContentType, uint16_t aStatus, bool aNoSniff); 57 58 OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason( 59 nsHttpResponseHead& aResponseHead); 60 61 // Returns a tuple of (rangeStart, rangeEnd, rangeTotal) from the input range 62 // header string if succeed. 63 Result<std::tuple<int64_t, int64_t, int64_t>, nsresult> 64 ParseContentRangeHeaderString(const nsAutoCString& aRangeStr); 65 66 bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead); 67 68 LogModule* GetORBLog(); 69 70 // Helper class to filter data for opaque responses destined for `Window.fetch`. 71 // See https://fetch.spec.whatwg.org/#concept-filtered-response-opaque. 72 class OpaqueResponseFilter final : public nsIStreamListener { 73 public: 74 NS_DECL_THREADSAFE_ISUPPORTS 75 NS_DECL_NSIREQUESTOBSERVER 76 NS_DECL_NSISTREAMLISTENER; 77 78 explicit OpaqueResponseFilter(nsIStreamListener* aNext); 79 80 private: 81 virtual ~OpaqueResponseFilter() = default; 82 83 nsCOMPtr<nsIStreamListener> mNext; 84 }; 85 86 class OpaqueResponseBlocker final : public nsIStreamListener { 87 enum class State { Sniffing, Allowed, Blocked }; 88 89 public: 90 NS_DECL_THREADSAFE_ISUPPORTS 91 NS_DECL_NSIREQUESTOBSERVER 92 NS_DECL_NSISTREAMLISTENER; 93 94 OpaqueResponseBlocker(nsIStreamListener* aNext, HttpBaseChannel* aChannel, 95 const nsCString& aContentType, bool aNoSniff); 96 97 bool IsSniffing() const; 98 void AllowResponse(); 99 void BlockResponse(HttpBaseChannel* aChannel, nsresult aStatus); 100 void FilterResponse(); 101 102 nsresult EnsureOpaqueResponseIsAllowedAfterSniff(nsIRequest* aRequest); 103 104 OpaqueResponse EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation( 105 HttpBaseChannel* aChannel, bool aAllow); 106 107 // The four possible results for validation. `JavaScript` and `JSON` are 108 // self-explanatory. `JavaScript` is the only successful result, in the sense 109 // that it will allow the opaque response, whereas `JSON` will block. `Other` 110 // is the case where validation fails, because the response is neither 111 // `JavaScript` nor `JSON`, but the framework itself works as intended. 112 // `Failure` implies that something has gone wrong, such as allocation, etc. 113 enum class ValidatorResult : uint32_t { JavaScript, JSON, Other, Failure }; 114 115 private: 116 virtual ~OpaqueResponseBlocker() = default; 117 118 nsresult ValidateJavaScript(HttpBaseChannel* aChannel, nsIURI* aURI, 119 nsILoadInfo* aLoadInfo); 120 121 void ResolveAndProcessData(HttpBaseChannel* aChannel, bool aAllowed, 122 Maybe<mozilla::ipc::Shmem>& aSharedData); 123 124 void MaybeRunOnStopRequest(HttpBaseChannel* aChannel); 125 126 nsCOMPtr<nsIStreamListener> mNext; 127 128 const nsCString mContentType; 129 const bool mNoSniff; 130 bool mShouldFilter = false; 131 132 State mState = State::Sniffing; 133 nsresult mStatus = NS_OK; 134 135 TimeStamp mStartOfJavaScriptValidation; 136 137 RefPtr<dom::JSValidatorParent> mJSValidator; 138 139 Maybe<nsresult> mPendingOnStopRequestStatus{Nothing()}; 140 }; 141 142 class nsCompressedAudioVideoImageDetector : public nsUnknownDecoder { 143 const std::function<void(void*, const uint8_t*, uint32_t)> mCallback; 144 145 public: 146 nsCompressedAudioVideoImageDetector( 147 nsIStreamListener* aListener, 148 std::function<void(void*, const uint8_t*, uint32_t)>&& aCallback) 149 : nsUnknownDecoder(aListener), mCallback(aCallback) {} 150 151 protected: 152 virtual void DetermineContentType(nsIRequest* aRequest) override { 153 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest); 154 if (!httpChannel) { 155 return; 156 } 157 158 const char* testData = mBuffer; 159 uint32_t testDataLen = mBufferLen; 160 // Check if data are compressed. 161 nsAutoCString decodedData; 162 163 // ConvertEncodedData is always called only on a single thread for each 164 // instance of an object. 165 nsresult rv = ConvertEncodedData(aRequest, mBuffer, mBufferLen); 166 if (NS_SUCCEEDED(rv)) { 167 MutexAutoLock lock(mMutex); 168 decodedData = mDecodedData; 169 } 170 if (!decodedData.IsEmpty()) { 171 testData = decodedData.get(); 172 testDataLen = std::min<uint32_t>(decodedData.Length(), 512u); 173 } 174 175 mCallback(httpChannel, (const uint8_t*)testData, testDataLen); 176 177 nsAutoCString contentType; 178 rv = httpChannel->GetContentType(contentType); 179 180 MutexAutoLock lock(mMutex); 181 if (!contentType.IsEmpty()) { 182 mContentType = contentType; 183 } else { 184 mContentType = UNKNOWN_CONTENT_TYPE; 185 } 186 187 nsCOMPtr<nsIEncodedChannel> encodedChannel = do_QueryInterface(httpChannel); 188 if (encodedChannel) { 189 encodedChannel->SetHasContentDecompressed(true); 190 } 191 } 192 }; 193 } // namespace mozilla::net 194 195 namespace IPC { 196 template <> 197 struct ParamTraits<mozilla::net::OpaqueResponseBlocker::ValidatorResult> 198 : public ContiguousEnumSerializerInclusive< 199 mozilla::net::OpaqueResponseBlocker::ValidatorResult, 200 mozilla::net::OpaqueResponseBlocker::ValidatorResult::JavaScript, 201 mozilla::net::OpaqueResponseBlocker::ValidatorResult::Failure> {}; 202 } // namespace IPC 203 204 #endif // mozilla_net_OpaqueResponseUtils_h