OpaqueResponseUtils.cpp (23240B)
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/net/OpaqueResponseUtils.h" 8 9 #include "mozilla/dom/Document.h" 10 #include "mozilla/StaticPrefs_browser.h" 11 #include "mozilla/dom/JSValidatorParent.h" 12 #include "ErrorList.h" 13 #include "nsContentUtils.h" 14 #include "nsHttpResponseHead.h" 15 #include "nsISupports.h" 16 #include "nsMimeTypes.h" 17 #include "nsStreamUtils.h" 18 #include "nsThreadUtils.h" 19 #include "nsStringStream.h" 20 #include "HttpBaseChannel.h" 21 22 static mozilla::LazyLogModule gORBLog("ORB"); 23 24 #define LOGORB(msg, ...) \ 25 MOZ_LOG(gORBLog, LogLevel::Debug, \ 26 ("%s: %p " msg, __func__, this, ##__VA_ARGS__)) 27 28 namespace mozilla::net { 29 30 static bool IsOpaqueSafeListedMIMEType(const nsACString& aContentType) { 31 if (aContentType.EqualsLiteral(TEXT_CSS) || 32 aContentType.EqualsLiteral(IMAGE_SVG_XML)) { 33 return true; 34 } 35 36 NS_ConvertUTF8toUTF16 typeString(aContentType); 37 return nsContentUtils::IsJavascriptMIMEType(typeString); 38 } 39 40 // These need to be kept in sync with 41 // "browser.opaqueResponseBlocking.mediaExceptionsStrategy" 42 enum class OpaqueResponseMediaException { NoExceptions, AllowSome, AllowAll }; 43 44 static OpaqueResponseMediaException ConfiguredMediaExceptionsStrategy() { 45 uint32_t pref = StaticPrefs:: 46 browser_opaqueResponseBlocking_mediaExceptionsStrategy_DoNotUseDirectly(); 47 if (NS_WARN_IF(pref > static_cast<uint32_t>( 48 OpaqueResponseMediaException::AllowAll))) { 49 return OpaqueResponseMediaException::AllowAll; 50 } 51 52 return static_cast<OpaqueResponseMediaException>(pref); 53 } 54 55 static bool IsOpaqueSafeListedSpecBreakingMIMEType( 56 const nsACString& aContentType, bool aNoSniff) { 57 // Avoid trouble with DASH/HLS. See bug 1698040. 58 if (aContentType.EqualsLiteral(APPLICATION_DASH_XML) || 59 aContentType.EqualsLiteral(APPLICATION_MPEGURL) || 60 aContentType.EqualsLiteral(AUDIO_MPEG_URL) || 61 aContentType.EqualsLiteral(TEXT_VTT)) { 62 return true; 63 } 64 65 // Do what Chromium does. This is from bug 1828375, and we should ideally 66 // revert this. 67 if (aContentType.EqualsLiteral(TEXT_PLAIN) && aNoSniff) { 68 return true; 69 } 70 71 switch (ConfiguredMediaExceptionsStrategy()) { 72 case OpaqueResponseMediaException::NoExceptions: 73 break; 74 case OpaqueResponseMediaException::AllowSome: 75 if (aContentType.EqualsLiteral(AUDIO_MP3) || 76 aContentType.EqualsLiteral(AUDIO_AAC) || 77 aContentType.EqualsLiteral(AUDIO_AACP) || 78 aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) { 79 return true; 80 } 81 break; 82 case OpaqueResponseMediaException::AllowAll: 83 if (StringBeginsWith(aContentType, "audio/"_ns) || 84 StringBeginsWith(aContentType, "video/"_ns) || 85 aContentType.EqualsLiteral(MULTIPART_MIXED_REPLACE)) { 86 return true; 87 } 88 break; 89 } 90 91 return false; 92 } 93 94 static bool IsOpaqueBlockListedMIMEType(const nsACString& aContentType) { 95 return aContentType.EqualsLiteral(TEXT_HTML) || 96 StringEndsWith(aContentType, "+json"_ns) || 97 aContentType.EqualsLiteral(APPLICATION_JSON) || 98 aContentType.EqualsLiteral(TEXT_JSON) || 99 StringEndsWith(aContentType, "+xml"_ns) || 100 aContentType.EqualsLiteral(APPLICATION_XML) || 101 aContentType.EqualsLiteral(TEXT_XML); 102 } 103 104 static bool IsOpaqueBlockListedNeverSniffedMIMEType( 105 const nsACString& aContentType) { 106 return aContentType.EqualsLiteral(APPLICATION_GZIP2) || 107 aContentType.EqualsLiteral(APPLICATION_MSEXCEL) || 108 aContentType.EqualsLiteral(APPLICATION_MSPPT) || 109 aContentType.EqualsLiteral(APPLICATION_MSWORD) || 110 aContentType.EqualsLiteral(APPLICATION_MSWORD_TEMPLATE) || 111 aContentType.EqualsLiteral(APPLICATION_PDF) || 112 aContentType.EqualsLiteral(APPLICATION_MPEGURL) || 113 aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKPOINT) || 114 aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKSHEET) || 115 aContentType.EqualsLiteral(APPLICATION_VND_CES_QUICKWORD) || 116 aContentType.EqualsLiteral(APPLICATION_VND_MS_EXCEL) || 117 aContentType.EqualsLiteral(APPLICATION_VND_MS_EXCEL2) || 118 aContentType.EqualsLiteral(APPLICATION_VND_MS_PPT) || 119 aContentType.EqualsLiteral(APPLICATION_VND_MS_PPT2) || 120 aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD) || 121 aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD2) || 122 aContentType.EqualsLiteral(APPLICATION_VND_MS_WORD3) || 123 aContentType.EqualsLiteral(APPLICATION_VND_MSWORD) || 124 aContentType.EqualsLiteral( 125 APPLICATION_VND_PRESENTATIONML_PRESENTATION) || 126 aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATIONML_TEMPLATE) || 127 aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEETML_SHEET) || 128 aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEETML_TEMPLATE) || 129 aContentType.EqualsLiteral( 130 APPLICATION_VND_WORDPROCESSINGML_DOCUMENT) || 131 aContentType.EqualsLiteral( 132 APPLICATION_VND_WORDPROCESSINGML_TEMPLATE) || 133 aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATION_OPENXML) || 134 aContentType.EqualsLiteral(APPLICATION_VND_PRESENTATION_OPENXMLM) || 135 aContentType.EqualsLiteral(APPLICATION_VND_SPREADSHEET_OPENXML) || 136 aContentType.EqualsLiteral(APPLICATION_VND_WORDPROSSING_OPENXML) || 137 aContentType.EqualsLiteral(APPLICATION_GZIP) || 138 aContentType.EqualsLiteral(APPLICATION_XPROTOBUF) || 139 aContentType.EqualsLiteral(APPLICATION_XPROTOBUFFER) || 140 aContentType.EqualsLiteral(APPLICATION_ZIP) || 141 aContentType.EqualsLiteral(AUDIO_MPEG_URL) || 142 aContentType.EqualsLiteral(MULTIPART_BYTERANGES) || 143 aContentType.EqualsLiteral(MULTIPART_SIGNED) || 144 aContentType.EqualsLiteral(TEXT_EVENT_STREAM) || 145 aContentType.EqualsLiteral(TEXT_CSV) || 146 aContentType.EqualsLiteral(TEXT_VTT) || 147 aContentType.EqualsLiteral(APPLICATION_DASH_XML); 148 } 149 150 OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason( 151 const nsACString& aContentType, uint16_t aStatus, bool aNoSniff) { 152 if (aContentType.IsEmpty()) { 153 return OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF; 154 } 155 156 if (IsOpaqueSafeListedMIMEType(aContentType)) { 157 return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED; 158 } 159 160 // For some MIME types we deviate from spec and allow when we ideally 161 // shouldn't. These are returnened before any blocking takes place. 162 if (IsOpaqueSafeListedSpecBreakingMIMEType(aContentType, aNoSniff)) { 163 return OpaqueResponseBlockedReason::ALLOWED_SAFE_LISTED_SPEC_BREAKING; 164 } 165 166 if (IsOpaqueBlockListedNeverSniffedMIMEType(aContentType)) { 167 return OpaqueResponseBlockedReason::BLOCKED_BLOCKLISTED_NEVER_SNIFFED; 168 } 169 170 if (aStatus == 206 && IsOpaqueBlockListedMIMEType(aContentType)) { 171 return OpaqueResponseBlockedReason::BLOCKED_206_AND_BLOCKLISTED; 172 } 173 174 nsAutoCString contentTypeOptionsHeader; 175 if (aNoSniff && (IsOpaqueBlockListedMIMEType(aContentType) || 176 aContentType.EqualsLiteral(TEXT_PLAIN))) { 177 return OpaqueResponseBlockedReason:: 178 BLOCKED_NOSNIFF_AND_EITHER_BLOCKLISTED_OR_TEXTPLAIN; 179 } 180 181 return OpaqueResponseBlockedReason::BLOCKED_SHOULD_SNIFF; 182 } 183 184 OpaqueResponseBlockedReason GetOpaqueResponseBlockedReason( 185 nsHttpResponseHead& aResponseHead) { 186 nsAutoCString contentType; 187 aResponseHead.ContentType(contentType); 188 189 nsAutoCString contentTypeOptionsHeader; 190 bool nosniff = 191 aResponseHead.GetContentTypeOptionsHeader(contentTypeOptionsHeader) && 192 contentTypeOptionsHeader.EqualsIgnoreCase("nosniff"); 193 194 return GetOpaqueResponseBlockedReason(contentType, aResponseHead.Status(), 195 nosniff); 196 } 197 198 Result<std::tuple<int64_t, int64_t, int64_t>, nsresult> 199 ParseContentRangeHeaderString(const nsAutoCString& aRangeStr) { 200 // Parse the range header: e.g. Content-Range: bytes 7000-7999/8000. 201 const int32_t spacePos = aRangeStr.Find(" "_ns); 202 const int32_t dashPos = aRangeStr.Find("-"_ns, spacePos); 203 const int32_t slashPos = aRangeStr.Find("/"_ns, dashPos); 204 205 nsAutoCString rangeStartText; 206 aRangeStr.Mid(rangeStartText, spacePos + 1, dashPos - (spacePos + 1)); 207 208 nsresult rv; 209 const int64_t rangeStart = rangeStartText.ToInteger64(&rv); 210 if (NS_FAILED(rv)) { 211 return Err(rv); 212 } 213 if (0 > rangeStart) { 214 return Err(NS_ERROR_ILLEGAL_VALUE); 215 } 216 217 nsAutoCString rangeEndText; 218 aRangeStr.Mid(rangeEndText, dashPos + 1, slashPos - (dashPos + 1)); 219 const int64_t rangeEnd = rangeEndText.ToInteger64(&rv); 220 if (NS_FAILED(rv)) { 221 return Err(rv); 222 } 223 if (rangeStart > rangeEnd) { 224 return Err(NS_ERROR_ILLEGAL_VALUE); 225 } 226 227 nsAutoCString rangeTotalText; 228 aRangeStr.Right(rangeTotalText, aRangeStr.Length() - (slashPos + 1)); 229 if (rangeTotalText[0] == '*') { 230 return std::make_tuple(rangeStart, rangeEnd, (int64_t)-1); 231 } 232 233 const int64_t rangeTotal = rangeTotalText.ToInteger64(&rv); 234 if (NS_FAILED(rv)) { 235 return Err(rv); 236 } 237 if (rangeEnd >= rangeTotal) { 238 return Err(NS_ERROR_ILLEGAL_VALUE); 239 } 240 241 return std::make_tuple(rangeStart, rangeEnd, rangeTotal); 242 } 243 244 bool IsFirstPartialResponse(nsHttpResponseHead& aResponseHead) { 245 MOZ_ASSERT(aResponseHead.Status() == 206); 246 247 nsAutoCString contentRange; 248 (void)aResponseHead.GetHeader(nsHttp::Content_Range, contentRange); 249 250 auto rangeOrErr = ParseContentRangeHeaderString(contentRange); 251 if (rangeOrErr.isErr()) { 252 return false; 253 } 254 255 const int64_t responseFirstBytePos = std::get<0>(rangeOrErr.unwrap()); 256 return responseFirstBytePos == 0; 257 } 258 259 LogModule* GetORBLog() { return gORBLog; } 260 261 OpaqueResponseFilter::OpaqueResponseFilter(nsIStreamListener* aNext) 262 : mNext(aNext) { 263 LOGORB(); 264 } 265 266 NS_IMETHODIMP 267 OpaqueResponseFilter::OnStartRequest(nsIRequest* aRequest) { 268 LOGORB(); 269 nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest); 270 MOZ_ASSERT(httpBaseChannel); 271 272 nsHttpResponseHead* responseHead = httpBaseChannel->GetResponseHead(); 273 274 if (responseHead) { 275 // Filtered opaque responses doesn't need headers, so we just drop them. 276 responseHead->ClearHeaders(); 277 } 278 279 mNext->OnStartRequest(aRequest); 280 return NS_OK; 281 } 282 283 NS_IMETHODIMP 284 OpaqueResponseFilter::OnDataAvailable(nsIRequest* aRequest, 285 nsIInputStream* aInputStream, 286 uint64_t aOffset, uint32_t aCount) { 287 LOGORB(); 288 uint32_t result; 289 // No data for filtered opaque responses should reach the content process, so 290 // we just discard them. 291 return aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, 292 &result); 293 } 294 295 NS_IMETHODIMP 296 OpaqueResponseFilter::OnStopRequest(nsIRequest* aRequest, 297 nsresult aStatusCode) { 298 LOGORB(); 299 mNext->OnStopRequest(aRequest, aStatusCode); 300 return NS_OK; 301 } 302 303 NS_IMPL_ISUPPORTS(OpaqueResponseFilter, nsIStreamListener, nsIRequestObserver) 304 305 OpaqueResponseBlocker::OpaqueResponseBlocker(nsIStreamListener* aNext, 306 HttpBaseChannel* aChannel, 307 const nsCString& aContentType, 308 bool aNoSniff) 309 : mNext(aNext), mContentType(aContentType), mNoSniff(aNoSniff) { 310 // Storing aChannel as a member is tricky as aChannel owns us and it's 311 // hard to ensure aChannel is alive when we about to use it without 312 // creating a cycle. This is all doable but need some extra efforts. 313 // 314 // So we are just passing aChannel from the caller when we need to use it. 315 MOZ_ASSERT(aChannel); 316 317 if (MOZ_UNLIKELY(MOZ_LOG_TEST(gORBLog, LogLevel::Debug))) { 318 nsCOMPtr<nsIURI> uri; 319 aChannel->GetURI(getter_AddRefs(uri)); 320 if (uri) { 321 LOGORB(" channel=%p, uri=%s", aChannel, uri->GetSpecOrDefault().get()); 322 } 323 } 324 MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess()); 325 MOZ_DIAGNOSTIC_ASSERT(aChannel->CachedOpaqueResponseBlockingPref()); 326 } 327 328 NS_IMETHODIMP 329 OpaqueResponseBlocker::OnStartRequest(nsIRequest* aRequest) { 330 LOGORB(); 331 332 if (mState == State::Sniffing) { 333 (void)EnsureOpaqueResponseIsAllowedAfterSniff(aRequest); 334 } 335 336 // mState will remain State::Sniffing if we need to wait 337 // for JS validator to make a decision. 338 // 339 // When the state is Sniffing, we can't call mNext->OnStartRequest 340 // because fetch requests need the cancellation to be done 341 // before its FetchDriver::OnStartRequest is called, otherwise it'll 342 // resolve the promise regardless the decision of JS validator. 343 if (mState != State::Sniffing) { 344 nsresult rv = mNext->OnStartRequest(aRequest); 345 return NS_SUCCEEDED(mStatus) ? rv : mStatus; 346 } 347 348 return NS_OK; 349 } 350 351 NS_IMETHODIMP 352 OpaqueResponseBlocker::OnStopRequest(nsIRequest* aRequest, 353 nsresult aStatusCode) { 354 LOGORB(); 355 356 nsresult statusForStop = aStatusCode; 357 358 if (mState == State::Blocked && NS_FAILED(mStatus)) { 359 statusForStop = mStatus; 360 } 361 362 if (mState == State::Sniffing) { 363 // It is the call to JSValidatorParent::OnStopRequest that will trigger the 364 // JS parser. 365 mStartOfJavaScriptValidation = TimeStamp::Now(); 366 367 MOZ_ASSERT(mJSValidator); 368 mPendingOnStopRequestStatus = Some(aStatusCode); 369 mJSValidator->OnStopRequest(aStatusCode, *aRequest); 370 return NS_OK; 371 } 372 373 return mNext->OnStopRequest(aRequest, statusForStop); 374 } 375 376 NS_IMETHODIMP 377 OpaqueResponseBlocker::OnDataAvailable(nsIRequest* aRequest, 378 nsIInputStream* aInputStream, 379 uint64_t aOffset, uint32_t aCount) { 380 LOGORB(); 381 382 if (mState == State::Allowed) { 383 return mNext->OnDataAvailable(aRequest, aInputStream, aOffset, aCount); 384 } 385 386 if (mState == State::Blocked) { 387 return NS_BINDING_ABORTED; 388 } 389 390 MOZ_ASSERT(mState == State::Sniffing); 391 392 nsCString data; 393 if (!data.SetLength(aCount, fallible)) { 394 return NS_ERROR_OUT_OF_MEMORY; 395 } 396 397 uint32_t read; 398 nsresult rv = aInputStream->Read(data.BeginWriting(), aCount, &read); 399 if (NS_FAILED(rv)) { 400 return rv; 401 } 402 403 MOZ_ASSERT(mJSValidator); 404 405 mJSValidator->OnDataAvailable(data); 406 407 return NS_OK; 408 } 409 410 nsresult OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterSniff( 411 nsIRequest* aRequest) { 412 nsCOMPtr<HttpBaseChannel> httpBaseChannel = do_QueryInterface(aRequest); 413 MOZ_ASSERT(httpBaseChannel); 414 415 // The `AfterSniff` check shouldn't be run when 416 // 1. We have made a decision already 417 // 2. The JS validator is running, so we should wait 418 // for its result. 419 if (mState != State::Sniffing || mJSValidator) { 420 return NS_OK; 421 } 422 423 nsCOMPtr<nsILoadInfo> loadInfo; 424 425 nsresult rv = 426 httpBaseChannel->GetLoadInfo(getter_AddRefs<nsILoadInfo>(loadInfo)); 427 if (NS_FAILED(rv)) { 428 LOGORB("Failed to get LoadInfo"); 429 BlockResponse(httpBaseChannel, rv); 430 return rv; 431 } 432 433 nsCOMPtr<nsIURI> uri; 434 rv = httpBaseChannel->GetURI(getter_AddRefs<nsIURI>(uri)); 435 if (NS_FAILED(rv)) { 436 LOGORB("Failed to get uri"); 437 BlockResponse(httpBaseChannel, rv); 438 return rv; 439 } 440 441 switch (httpBaseChannel->PerformOpaqueResponseSafelistCheckAfterSniff( 442 mContentType, mNoSniff)) { 443 case OpaqueResponse::Block: 444 BlockResponse(httpBaseChannel, NS_BINDING_ABORTED); 445 return NS_BINDING_ABORTED; 446 case OpaqueResponse::Allow: 447 AllowResponse(); 448 return NS_OK; 449 case OpaqueResponse::Sniff: 450 case OpaqueResponse::SniffCompressed: 451 break; 452 } 453 454 MOZ_ASSERT(mState == State::Sniffing); 455 return ValidateJavaScript(httpBaseChannel, uri, loadInfo); 456 } 457 458 OpaqueResponse 459 OpaqueResponseBlocker::EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation( 460 HttpBaseChannel* aChannel, bool aAllow) { 461 if (aAllow) { 462 return OpaqueResponse::Allow; 463 } 464 465 return aChannel->BlockOrFilterOpaqueResponse( 466 this, u"Javascript validation failed"_ns, 467 OpaqueResponseBlockedTelemetryReason::eJsValidationFailed, 468 "Javascript validation failed"); 469 } 470 471 static void RecordTelemetry(const TimeStamp& aStartOfValidation, 472 const TimeStamp& aStartOfJavaScriptValidation, 473 OpaqueResponseBlocker::ValidatorResult aResult) { 474 using ValidatorResult = OpaqueResponseBlocker::ValidatorResult; 475 MOZ_DIAGNOSTIC_ASSERT(aStartOfValidation); 476 477 auto key = [aResult]() { 478 switch (aResult) { 479 case ValidatorResult::JavaScript: 480 return "javascript"_ns; 481 case ValidatorResult::JSON: 482 return "json"_ns; 483 case ValidatorResult::Other: 484 return "other"_ns; 485 case ValidatorResult::Failure: 486 return "failure"_ns; 487 } 488 MOZ_ASSERT_UNREACHABLE("Switch statement should be saturated"); 489 return "failure"_ns; 490 }(); 491 492 TimeStamp now = TimeStamp::Now(); 493 PROFILER_MARKER_TEXT( 494 "ORB safelist check", NETWORK, 495 MarkerTiming::Interval(aStartOfValidation, aStartOfJavaScriptValidation), 496 nsPrintfCString("Receive data for validation (%s)", key.get())); 497 498 PROFILER_MARKER_TEXT( 499 "ORB safelist check", NETWORK, 500 MarkerTiming::Interval(aStartOfJavaScriptValidation, now), 501 nsPrintfCString("JS Validation (%s)", key.get())); 502 503 glean::orb::receive_data_for_validation.Get(key).AccumulateRawDuration( 504 aStartOfJavaScriptValidation - aStartOfValidation); 505 506 glean::orb::javascript_validation.Get(key).AccumulateRawDuration( 507 now - aStartOfJavaScriptValidation); 508 } 509 510 // The specification for ORB is currently being written: 511 // https://whatpr.org/fetch/1442.html#orb-algorithm 512 // The `opaque-response-safelist check` is implemented in: 513 // * `HttpBaseChannel::OpaqueResponseSafelistCheckBeforeSniff` 514 // * `nsHttpChannel::DisableIsOpaqueResponseAllowedAfterSniffCheck` 515 // * `HttpBaseChannel::OpaqueResponseSafelistCheckAfterSniff` 516 // * `OpaqueResponseBlocker::ValidateJavaScript` 517 nsresult OpaqueResponseBlocker::ValidateJavaScript(HttpBaseChannel* aChannel, 518 nsIURI* aURI, 519 nsILoadInfo* aLoadInfo) { 520 MOZ_DIAGNOSTIC_ASSERT(aChannel); 521 MOZ_ASSERT(aURI && aLoadInfo); 522 523 if (!StaticPrefs::browser_opaqueResponseBlocking_javascriptValidator()) { 524 LOGORB("Allowed: JS Validator is disabled"); 525 AllowResponse(); 526 return NS_OK; 527 } 528 529 int64_t contentLength; 530 nsresult rv = aChannel->GetContentLength(&contentLength); 531 if (NS_FAILED(rv)) { 532 LOGORB("Blocked: No Content Length"); 533 BlockResponse(aChannel, rv); 534 return rv; 535 } 536 537 glean::opaque_response_blocking::javascript_validation_count.Add(1); 538 539 LOGORB("Send %s to the validator", aURI->GetSpecOrDefault().get()); 540 // https://whatpr.org/fetch/1442.html#orb-algorithm, step 15 541 mJSValidator = dom::JSValidatorParent::Create(); 542 mJSValidator->IsOpaqueResponseAllowed( 543 [self = RefPtr{this}, channel = nsCOMPtr{aChannel}, uri = nsCOMPtr{aURI}, 544 loadInfo = nsCOMPtr{aLoadInfo}, startOfValidation = TimeStamp::Now()]( 545 Maybe<ipc::Shmem> aSharedData, ValidatorResult aResult) { 546 MOZ_LOG(gORBLog, LogLevel::Debug, 547 ("JSValidator resolved for %s with %s", 548 uri->GetSpecOrDefault().get(), 549 aSharedData.isSome() ? "true" : "false")); 550 bool allowed = aResult == ValidatorResult::JavaScript; 551 switch (self->EnsureOpaqueResponseIsAllowedAfterJavaScriptValidation( 552 channel, allowed)) { 553 case OpaqueResponse::Allow: 554 // It's possible that the JS validation failed for this request, 555 // however we decided that we need to filter the response instead 556 // of blocking. So we set allowed to true manually when that's the 557 // case. 558 allowed = true; 559 self->AllowResponse(); 560 break; 561 case OpaqueResponse::Block: 562 self->BlockResponse(channel, NS_ERROR_FAILURE); 563 break; 564 default: 565 MOZ_ASSERT_UNREACHABLE( 566 "We should only ever have Allow or Block here."); 567 allowed = false; 568 self->BlockResponse(channel, NS_BINDING_ABORTED); 569 break; 570 } 571 572 self->ResolveAndProcessData(channel, allowed, aSharedData); 573 if (aSharedData.isSome()) { 574 self->mJSValidator->DeallocShmem(aSharedData.ref()); 575 } 576 577 RecordTelemetry(startOfValidation, self->mStartOfJavaScriptValidation, 578 aResult); 579 580 (void)dom::PJSValidatorParent::Send__delete__(self->mJSValidator); 581 self->mJSValidator = nullptr; 582 }); 583 584 return NS_OK; 585 } 586 587 bool OpaqueResponseBlocker::IsSniffing() const { 588 return mState == State::Sniffing; 589 } 590 591 void OpaqueResponseBlocker::AllowResponse() { 592 LOGORB("Sniffer is done, allow response, this=%p", this); 593 MOZ_ASSERT(mState == State::Sniffing); 594 mState = State::Allowed; 595 } 596 597 void OpaqueResponseBlocker::BlockResponse(HttpBaseChannel* aChannel, 598 nsresult aStatus) { 599 LOGORB("Sniffer is done, block response, this=%p", this); 600 MOZ_ASSERT(mState == State::Sniffing); 601 mState = State::Blocked; 602 mStatus = aStatus; 603 aChannel->SetChannelBlockedByOpaqueResponse(); 604 aChannel->CancelWithReason(mStatus, 605 "OpaqueResponseBlocker::BlockResponse"_ns); 606 } 607 608 void OpaqueResponseBlocker::FilterResponse() { 609 MOZ_ASSERT(mState == State::Sniffing); 610 611 if (mShouldFilter) { 612 return; 613 } 614 615 mShouldFilter = true; 616 617 mNext = new OpaqueResponseFilter(mNext); 618 } 619 620 void OpaqueResponseBlocker::ResolveAndProcessData( 621 HttpBaseChannel* aChannel, bool aAllowed, Maybe<ipc::Shmem>& aSharedData) { 622 nsresult rv = OnStartRequest(aChannel); 623 624 if (!aAllowed || NS_FAILED(rv)) { 625 MOZ_ASSERT_IF(!aAllowed, mState == State::Blocked); 626 // We decided to block, so nothing more to do. 627 MaybeRunOnStopRequest(aChannel); 628 return; 629 } 630 631 MOZ_ASSERT(mState == State::Allowed); 632 633 if (aSharedData.isNothing()) { 634 MaybeRunOnStopRequest(aChannel); 635 return; 636 } 637 638 const ipc::Shmem& mem = aSharedData.ref(); 639 nsCOMPtr<nsIInputStream> input; 640 rv = NS_NewByteInputStream(getter_AddRefs(input), 641 Span(mem.get<char>(), mem.Size<char>()), 642 NS_ASSIGNMENT_DEPEND); 643 644 if (NS_WARN_IF(NS_FAILED(rv))) { 645 BlockResponse(aChannel, rv); 646 MaybeRunOnStopRequest(aChannel); 647 return; 648 } 649 650 // When this line reaches, the state is either State::Allowed or 651 // State::Blocked. The OnDataAvailable call will either call 652 // the next listener or reject the request. 653 OnDataAvailable(aChannel, input, 0, mem.Size<char>()); 654 655 MaybeRunOnStopRequest(aChannel); 656 } 657 658 void OpaqueResponseBlocker::MaybeRunOnStopRequest(HttpBaseChannel* aChannel) { 659 MOZ_ASSERT(mState != State::Sniffing); 660 if (mPendingOnStopRequestStatus.isSome()) { 661 OnStopRequest(aChannel, mPendingOnStopRequestStatus.value()); 662 } 663 } 664 665 NS_IMPL_ISUPPORTS(OpaqueResponseBlocker, nsIStreamListener, nsIRequestObserver) 666 667 } // namespace mozilla::net