StreamLoader.cpp (10449B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/css/StreamLoader.h" 8 9 #include "mozilla/Encoding.h" 10 #include "mozilla/StaticPrefs_network.h" 11 #include "mozilla/TaskQueue.h" 12 #include "mozilla/dom/CacheExpirationTime.h" 13 #include "mozilla/net/UrlClassifierFeatureFactory.h" 14 #include "nsContentUtils.h" 15 #include "nsIAsyncVerifyRedirectCallback.h" 16 #include "nsIChannel.h" 17 #include "nsIInputStream.h" 18 #include "nsIStreamTransportService.h" 19 #include "nsIThreadRetargetableRequest.h" 20 #include "nsNetCID.h" 21 #include "nsNetUtil.h" 22 #include "nsProxyRelease.h" 23 #include "nsServiceManagerUtils.h" 24 25 namespace mozilla::css { 26 27 StreamLoader::StreamLoader(SheetLoadData& aSheetLoadData) 28 : mSheetLoadData(&aSheetLoadData), 29 mStatus(NS_OK), 30 mMainThreadSheetLoadData(new nsMainThreadPtrHolder<SheetLoadData>( 31 "StreamLoader::SheetLoadData", mSheetLoadData, false)) {} 32 33 StreamLoader::~StreamLoader() { 34 #ifdef NIGHTLY_BUILD 35 MOZ_RELEASE_ASSERT(mOnStopProcessingDone || mChannelOpenFailed); 36 #endif 37 } 38 39 NS_IMPL_ISUPPORTS(StreamLoader, nsIStreamListener, 40 nsIThreadRetargetableStreamListener, nsIChannelEventSink, 41 nsIInterfaceRequestor) 42 43 /* nsIRequestObserver implementation */ 44 NS_IMETHODIMP 45 StreamLoader::OnStartRequest(nsIRequest* aRequest) { 46 MOZ_ASSERT(aRequest); 47 mRequest = aRequest; 48 mSheetLoadData->OnStartRequest(aRequest); 49 50 // It's kinda bad to let Web content send a number that results 51 // in a potentially large allocation directly, but efficiency of 52 // compression bombs is so great that it doesn't make much sense 53 // to require a site to send one before going ahead and allocating. 54 if (nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest)) { 55 int64_t length; 56 nsresult rv = channel->GetContentLength(&length); 57 if (NS_SUCCEEDED(rv) && length > 0) { 58 CheckedInt<nsACString::size_type> checkedLength(length); 59 if (!checkedLength.isValid()) { 60 return (mStatus = NS_ERROR_OUT_OF_MEMORY); 61 } 62 if (!mBytes.SetCapacity(checkedLength.value(), fallible)) { 63 return (mStatus = NS_ERROR_OUT_OF_MEMORY); 64 } 65 } 66 } 67 if (nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(aRequest)) { 68 nsCOMPtr<nsIEventTarget> sts = 69 do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); 70 RefPtr queue = 71 TaskQueue::Create(sts.forget(), "css::StreamLoader Delivery Queue"); 72 rr->RetargetDeliveryTo(queue); 73 } 74 75 return NS_OK; 76 } 77 78 NS_IMETHODIMP 79 StreamLoader::CheckListenerChain() { return NS_OK; } 80 81 NS_IMETHODIMP 82 StreamLoader::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { 83 MOZ_ASSERT_IF(!StaticPrefs::network_send_OnDataFinished_cssLoader(), 84 !mOnStopProcessingDone); 85 mRequest = nullptr; 86 87 nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); 88 89 // StreamLoader::OnStopRequest can get triggered twice for a request. 90 // Once from the path 91 // nsIThreadRetargetableStreamListener::OnDataFinished->StreamLoader::OnDataFinished 92 // (non-main thread) and 93 // once from nsIRequestObserver::OnStopRequest path (main thread). It is 94 // guaranteed that we will always get 95 // nsIThreadRetargetableStreamListener::OnDataFinished trigger first and this 96 // is always followed by nsIRequestObserver::OnStopRequest 97 98 // If we are executing OnStopRequest OMT, we need to block resolution of parse 99 // promise and unblock again if we are executing this in main thread. 100 // Resolution of parse promise fires onLoadEvent and this should not happen 101 // before main thread OnStopRequest is dispatched. 102 if (NS_IsMainThread()) { 103 channel->SetNotificationCallbacks(nullptr); 104 105 mSheetLoadData->mNetworkMetadata = 106 new SubResourceNetworkMetadataHolder(aRequest); 107 108 mSheetLoadData->mSheet->UnblockParsePromise(); 109 } else { 110 if (mSheetLoadData->mRecordErrors) { 111 // We can't report errors off main thread right now. 112 return NS_OK; 113 } 114 } 115 116 auto HandleErrorInMainThread = [&] { 117 MOZ_ASSERT(mStatus != NS_OK_PARSE_SHEET); 118 MOZ_ASSERT(NS_IsMainThread()); 119 if (net::UrlClassifierFeatureFactory::IsClassifierBlockingErrorCode( 120 mStatus)) { 121 // Handle sheet not loading error because source was a tracking URL (or 122 // fingerprinting, cryptomining, etc). We make a note of this sheet node 123 // by including it in a dedicated array of blocked tracking nodes under 124 // its parent document. 125 // 126 // Multiple sheet load instances might be tied to this request, we 127 // annotate each one linked to a valid owning element (node). 128 // 129 // TODO(emilio): Maybe this should be done in Loader::NotifyObservers? 130 // Feels pretty random here. 131 for (SheetLoadData* data = mSheetLoadData; data; data = data->mNext) { 132 if (nsINode* node = data->mSheet->GetOwnerNode()) { 133 node->OwnerDoc()->AddBlockedNodeByClassifier(node); 134 } 135 } 136 } 137 mSheetLoadData->mLoader->SheetComplete(*mSheetLoadData, mStatus); 138 }; 139 140 if (mOnStopProcessingDone) { 141 MOZ_ASSERT(NS_IsMainThread()); 142 if (mStatus != NS_OK_PARSE_SHEET) { 143 HandleErrorInMainThread(); 144 } 145 return NS_OK; 146 } 147 148 mOnStopProcessingDone = true; 149 150 // Decoded data 151 nsCString utf8String; 152 { 153 nsresult status = NS_FAILED(mStatus) ? mStatus : aStatus; 154 mStatus = mSheetLoadData->VerifySheetReadyToParse(status, mBOMBytes, mBytes, 155 channel); 156 if (mStatus != NS_OK_PARSE_SHEET) { 157 if (NS_IsMainThread()) { 158 HandleErrorInMainThread(); 159 } 160 return mStatus; 161 } 162 163 // At this point all the conditions that requires us to run on main 164 // are checked in VerifySheetReadyToParse 165 166 // BOM detection generally happens during the write callback, but that 167 // won't have happened if fewer than three bytes were received. 168 if (mEncodingFromBOM.isNothing()) { 169 HandleBOM(); 170 MOZ_ASSERT(mEncodingFromBOM.isSome()); 171 } 172 // Hold the nsStringBuffer for the bytes from the stack to ensure release 173 // after its scope ends 174 nsCString bytes = std::move(mBytes); 175 // The BOM handling has happened, but we still may not have an encoding if 176 // there was no BOM. Ensure we have one. 177 const Encoding* encoding = mEncodingFromBOM.value(); 178 if (!encoding) { 179 // No BOM 180 encoding = mSheetLoadData->DetermineNonBOMEncoding(bytes, channel); 181 } 182 mSheetLoadData->mEncoding = encoding; 183 184 size_t validated = 0; 185 if (encoding == UTF_8_ENCODING) { 186 validated = Encoding::UTF8ValidUpTo(bytes); 187 } 188 189 if (validated == bytes.Length()) { 190 // Either this is UTF-8 and all valid, or it's not UTF-8 but is an empty 191 // string. This assumes that an empty string in any encoding decodes to 192 // empty string, which seems like a plausible assumption. 193 utf8String = std::move(bytes); 194 } else { 195 // FIXME: Seems early returning here is wrong, what completes the sheet? 196 MOZ_TRY(encoding->DecodeWithoutBOMHandling(bytes, utf8String, validated)); 197 } 198 } // run destructor for `bytes` 199 200 // For reasons I don't understand, factoring the below lines into 201 // a method on SheetLoadData resulted in a linker error. Hence, 202 // accessing fields of mSheetLoadData from here. 203 mSheetLoadData->mLoader->ParseSheet(utf8String, mMainThreadSheetLoadData, 204 Loader::AllowAsyncParse::Yes); 205 206 return NS_OK; 207 } 208 209 /* nsIStreamListener implementation */ 210 NS_IMETHODIMP 211 StreamLoader::OnDataAvailable(nsIRequest*, nsIInputStream* aInputStream, 212 uint64_t, uint32_t aCount) { 213 if (NS_FAILED(mStatus)) { 214 return mStatus; 215 } 216 uint32_t dummy; 217 return aInputStream->ReadSegments(WriteSegmentFun, this, aCount, &dummy); 218 } 219 220 void StreamLoader::HandleBOM() { 221 MOZ_ASSERT(mEncodingFromBOM.isNothing()); 222 MOZ_ASSERT(mBytes.IsEmpty()); 223 224 auto [encoding, bomLength] = Encoding::ForBOM(mBOMBytes); 225 mEncodingFromBOM.emplace(encoding); // Null means no BOM. 226 227 // BOMs are three bytes at most, but may be fewer. Copy over anything 228 // that wasn't part of the BOM to mBytes. Note that we need to track 229 // any BOM bytes as well for SRI handling. 230 mBytes.Append(Substring(mBOMBytes, bomLength)); 231 mBOMBytes.Truncate(bomLength); 232 } 233 234 NS_IMETHODIMP 235 StreamLoader::OnDataFinished(nsresult aResult) { 236 nsCOMPtr<nsIRequest> request = mRequest.forget(); 237 if (StaticPrefs::network_send_OnDataFinished_cssLoader()) { 238 return OnStopRequest(request, aResult); 239 } 240 241 return NS_OK; 242 } 243 244 NS_IMETHODIMP 245 StreamLoader::GetInterface(const nsIID& aIID, void** aResult) { 246 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { 247 return QueryInterface(aIID, aResult); 248 } 249 250 return NS_NOINTERFACE; 251 } 252 253 nsresult StreamLoader::AsyncOnChannelRedirect( 254 nsIChannel* aOld, nsIChannel* aNew, uint32_t aFlags, 255 nsIAsyncVerifyRedirectCallback* aCallback) { 256 mSheetLoadData->SetMinimumExpirationTime( 257 nsContentUtils::GetSubresourceCacheExpirationTime(aOld, 258 mSheetLoadData->mURI)); 259 260 aCallback->OnRedirectVerifyCallback(NS_OK); 261 262 return NS_OK; 263 } 264 265 nsresult StreamLoader::WriteSegmentFun(nsIInputStream*, void* aClosure, 266 const char* aSegment, uint32_t, 267 uint32_t aCount, uint32_t* aWriteCount) { 268 *aWriteCount = 0; 269 StreamLoader* self = static_cast<StreamLoader*>(aClosure); 270 if (NS_FAILED(self->mStatus)) { 271 return self->mStatus; 272 } 273 274 // If we haven't done BOM detection yet, divert bytes into the special buffer. 275 if (self->mEncodingFromBOM.isNothing()) { 276 size_t bytesToCopy = std::min<size_t>(3 - self->mBOMBytes.Length(), aCount); 277 self->mBOMBytes.Append(aSegment, bytesToCopy); 278 aSegment += bytesToCopy; 279 *aWriteCount += bytesToCopy; 280 aCount -= bytesToCopy; 281 282 if (self->mBOMBytes.Length() == 3) { 283 self->HandleBOM(); 284 } else { 285 return NS_OK; 286 } 287 } 288 289 if (!self->mBytes.Append(aSegment, aCount, fallible)) { 290 self->mBytes.Truncate(); 291 return (self->mStatus = NS_ERROR_OUT_OF_MEMORY); 292 } 293 294 *aWriteCount += aCount; 295 return NS_OK; 296 } 297 298 } // namespace mozilla::css