InterceptedHttpChannel.cpp (53900B)
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 /* 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 "InterceptedHttpChannel.h" 8 #include "NetworkMarker.h" 9 #include "nsContentSecurityManager.h" 10 #include "nsEscape.h" 11 #include "mozilla/SchedulerGroup.h" 12 #include "mozilla/ScopeExit.h" 13 #include "mozilla/dom/ChannelInfo.h" 14 #include "mozilla/dom/PerformanceStorage.h" 15 #include "mozilla/glean/DomServiceworkersMetrics.h" 16 #include "nsHttpChannel.h" 17 #include "nsIHttpHeaderVisitor.h" 18 #include "nsIRedirectResultListener.h" 19 #include "nsStringStream.h" 20 #include "nsStreamUtils.h" 21 #include "nsQueryObject.h" 22 #include "mozilla/Logging.h" 23 24 namespace mozilla::net { 25 26 mozilla::LazyLogModule gInterceptedLog("Intercepted"); 27 28 #define INTERCEPTED_LOG(args) MOZ_LOG(gInterceptedLog, LogLevel::Debug, args) 29 30 NS_IMPL_ISUPPORTS_INHERITED(InterceptedHttpChannel, HttpBaseChannel, 31 nsIInterceptedChannel, nsICacheInfoChannel, 32 nsIAsyncVerifyRedirectCallback, nsIRequestObserver, 33 nsIStreamListener, nsIThreadRetargetableRequest, 34 nsIThreadRetargetableStreamListener, 35 nsIClassOfService) 36 37 InterceptedHttpChannel::InterceptedHttpChannel( 38 PRTime aCreationTime, const TimeStamp& aCreationTimestamp, 39 const TimeStamp& aAsyncOpenTimestamp) 40 : HttpAsyncAborter<InterceptedHttpChannel>(this), 41 mProgress(0), 42 mProgressReported(0), 43 mSynthesizedStreamLength(-1), 44 mResumeStartPos(0), 45 mCallingStatusAndProgress(false) { 46 // Pre-set the creation and AsyncOpen times based on the original channel 47 // we are intercepting. We don't want our extra internal redirect to mask 48 // any time spent processing the channel. 49 INTERCEPTED_LOG(("Creating InterceptedHttpChannel [%p]", this)); 50 mChannelCreationTime = aCreationTime; 51 mChannelCreationTimestamp = aCreationTimestamp; 52 mInterceptedChannelCreationTimestamp = TimeStamp::Now(); 53 mAsyncOpenTime = aAsyncOpenTimestamp; 54 } 55 56 void InterceptedHttpChannel::ReleaseListeners() { 57 if (mLoadGroup) { 58 mLoadGroup->RemoveRequest(this, nullptr, mStatus); 59 } 60 HttpBaseChannel::ReleaseListeners(); 61 mSynthesizedResponseHead.reset(); 62 mRedirectChannel = nullptr; 63 mBodyReader = nullptr; 64 mReleaseHandle = nullptr; 65 mProgressSink = nullptr; 66 mBodyCallback = nullptr; 67 mPump = nullptr; 68 69 MOZ_DIAGNOSTIC_ASSERT(!LoadIsPending()); 70 } 71 72 nsresult InterceptedHttpChannel::SetupReplacementChannel( 73 nsIURI* aURI, nsIChannel* aChannel, bool aPreserveMethod, 74 uint32_t aRedirectFlags) { 75 INTERCEPTED_LOG( 76 ("InterceptedHttpChannel::SetupReplacementChannel [%p] flag: %u", this, 77 aRedirectFlags)); 78 nsresult rv = HttpBaseChannel::SetupReplacementChannel( 79 aURI, aChannel, aPreserveMethod, aRedirectFlags); 80 if (NS_FAILED(rv)) { 81 return rv; 82 } 83 84 rv = CheckRedirectLimit(aURI, aRedirectFlags); 85 NS_ENSURE_SUCCESS(rv, rv); 86 87 // While we can't resume an synthetic response, we can still propagate 88 // the resume params across redirects for other channels to handle. 89 if (mResumeStartPos > 0) { 90 nsCOMPtr<nsIResumableChannel> resumable = do_QueryInterface(aChannel); 91 if (!resumable) { 92 return NS_ERROR_NOT_RESUMABLE; 93 } 94 95 resumable->ResumeAt(mResumeStartPos, mResumeEntityId); 96 } 97 98 return NS_OK; 99 } 100 101 void InterceptedHttpChannel::AsyncOpenInternal() { 102 // We save this timestamp from outside of the if block in case we enable the 103 // profiler after AsyncOpen(). 104 INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpenInternal [%p]", this)); 105 mLastStatusReported = TimeStamp::Now(); 106 if (profiler_thread_is_being_profiled_for_markers()) { 107 nsAutoCString requestMethod; 108 GetRequestMethod(requestMethod); 109 110 profiler_add_network_marker( 111 mURI, requestMethod, mPriority, mChannelId, NetworkLoadType::LOAD_START, 112 mChannelCreationTimestamp, mLastStatusReported, 0, kCacheUnknown, 113 mLoadInfo->GetInnerWindowID(), 114 mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(), this, mStatus); 115 } 116 117 // If an error occurs in this file we must ensure mListener callbacks are 118 // invoked in some way. We either Cancel() or ResetInterception below 119 // depending on which path we take. 120 nsresult rv = NS_OK; 121 122 // Start the interception, record the start time. 123 mTimeStamps.Init(this); 124 mTimeStamps.RecordTime(); 125 126 // We should have pre-set the AsyncOpen time based on the original channel if 127 // timings are enabled. 128 MOZ_DIAGNOSTIC_ASSERT(!mAsyncOpenTime.IsNull()); 129 130 StoreIsPending(true); 131 StoreResponseCouldBeSynthesized(true); 132 133 if (mLoadGroup) { 134 mLoadGroup->AddRequest(this, nullptr); 135 } 136 137 // If we already have a synthesized body then we are pre-synthesized. 138 // This can happen for two reasons: 139 // 1. We have a pre-synthesized redirect in e10s mode. In this case 140 // we should follow the redirect. 141 // 2. We are handling a "fake" redirect for an opaque response. Here 142 // we should just process the synthetic body. 143 if (mBodyReader) { 144 // If we fail in this path, then cancel the channel. We don't want 145 // to ResetInterception() after a synthetic result has already been 146 // produced by the ServiceWorker. 147 auto autoCancel = MakeScopeExit([&] { 148 if (NS_FAILED(rv)) { 149 Cancel(rv); 150 } 151 }); 152 153 // The fetch event will not be dispatched, record current time for 154 // FetchHandlerStart and FetchHandlerFinish. 155 SetFetchHandlerStart(TimeStamp::Now()); 156 SetFetchHandlerFinish(TimeStamp::Now()); 157 158 if (ShouldRedirect()) { 159 rv = FollowSyntheticRedirect(); 160 return; 161 } 162 163 rv = StartPump(); 164 return; 165 } 166 167 // If we fail the initial interception, then attempt to ResetInterception 168 // to fall back to network. We only cancel if the reset fails. 169 auto autoReset = MakeScopeExit([&] { 170 if (NS_FAILED(rv)) { 171 rv = ResetInterception(false); 172 if (NS_WARN_IF(NS_FAILED(rv))) { 173 Cancel(rv); 174 } 175 } 176 }); 177 178 // Otherwise we need to trigger a FetchEvent in a ServiceWorker. 179 nsCOMPtr<nsINetworkInterceptController> controller; 180 GetCallback(controller); 181 182 if (NS_WARN_IF(!controller)) { 183 rv = NS_ERROR_DOM_INVALID_STATE_ERR; 184 return; 185 } 186 187 rv = controller->ChannelIntercepted(this); 188 NS_ENSURE_SUCCESS_VOID(rv); 189 } 190 191 bool InterceptedHttpChannel::ShouldRedirect() const { 192 // Determine if the synthetic response requires us to perform a real redirect. 193 return nsHttpChannel::WillRedirect(*mResponseHead) && 194 !mLoadInfo->GetDontFollowRedirects(); 195 } 196 197 nsresult InterceptedHttpChannel::FollowSyntheticRedirect() { 198 // Perform a real redirect based on the synthetic response. 199 200 nsCOMPtr<nsIIOService> ioService; 201 nsresult rv = gHttpHandler->GetIOService(getter_AddRefs(ioService)); 202 NS_ENSURE_SUCCESS(rv, rv); 203 204 nsAutoCString location; 205 rv = mResponseHead->GetHeader(nsHttp::Location, location); 206 NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); 207 208 // make sure non-ASCII characters in the location header are escaped. 209 nsAutoCString locationBuf; 210 if (NS_EscapeURL(location.get(), -1, esc_OnlyNonASCII | esc_Spaces, 211 locationBuf)) { 212 location = locationBuf; 213 } 214 215 nsCOMPtr<nsIURI> redirectURI; 216 rv = ioService->NewURI(nsDependentCString(location.get()), nullptr, mURI, 217 getter_AddRefs(redirectURI)); 218 NS_ENSURE_SUCCESS(rv, NS_ERROR_CORRUPTED_CONTENT); 219 220 uint32_t redirectFlags = nsIChannelEventSink::REDIRECT_TEMPORARY; 221 if (nsHttp::IsPermanentRedirect(mResponseHead->Status())) { 222 redirectFlags = nsIChannelEventSink::REDIRECT_PERMANENT; 223 } 224 225 PropagateReferenceIfNeeded(mURI, redirectURI); 226 227 bool rewriteToGET = ShouldRewriteRedirectToGET(mResponseHead->Status(), 228 mRequestHead.ParsedMethod()); 229 230 nsCOMPtr<nsIChannel> newChannel; 231 nsCOMPtr<nsILoadInfo> redirectLoadInfo = 232 CloneLoadInfoForRedirect(redirectURI, redirectFlags); 233 rv = NS_NewChannelInternal(getter_AddRefs(newChannel), redirectURI, 234 redirectLoadInfo, 235 nullptr, // PerformanceStorage 236 nullptr, // aLoadGroup 237 nullptr, // aCallbacks 238 mLoadFlags, ioService); 239 NS_ENSURE_SUCCESS(rv, rv); 240 241 rv = SetupReplacementChannel(redirectURI, newChannel, !rewriteToGET, 242 redirectFlags); 243 NS_ENSURE_SUCCESS(rv, rv); 244 245 mRedirectChannel = std::move(newChannel); 246 247 rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, 248 redirectFlags); 249 250 if (NS_WARN_IF(NS_FAILED(rv))) { 251 OnRedirectVerifyCallback(rv); 252 } else { 253 // Redirect success, record the finish time and the final status. 254 mTimeStamps.RecordTime(InterceptionTimeStamps::Redirected); 255 } 256 257 return rv; 258 } 259 260 nsresult InterceptedHttpChannel::RedirectForResponseURL( 261 nsIURI* aResponseURI, bool aResponseRedirected) { 262 // Perform a service worker redirect to another InterceptedHttpChannel using 263 // the given response URL. It allows content to see the final URL where 264 // appropriate and also helps us enforce cross-origin restrictions. The 265 // resulting channel will then process the synthetic response as normal. This 266 // extra redirect is performed so that listeners treat the result as unsafe 267 // cross-origin data. 268 269 nsresult rv = NS_OK; 270 271 // We want to pass ownership of the body callback to the new synthesized 272 // channel. We need to hold a reference to the callbacks on the stack 273 // as well, though, so we can call them if a failure occurs. 274 nsCOMPtr<nsIInterceptedBodyCallback> bodyCallback = std::move(mBodyCallback); 275 276 RefPtr<InterceptedHttpChannel> newChannel = CreateForSynthesis( 277 mResponseHead.get(), mBodyReader, bodyCallback, mChannelCreationTime, 278 mChannelCreationTimestamp, mAsyncOpenTime); 279 280 // If the response has been redirected, propagate all the URLs to content. 281 // Thus, the exact value of the redirect flag does not matter as long as it's 282 // not REDIRECT_INTERNAL. 283 uint32_t flags = aResponseRedirected ? nsIChannelEventSink::REDIRECT_TEMPORARY 284 : nsIChannelEventSink::REDIRECT_INTERNAL; 285 286 nsCOMPtr<nsILoadInfo> redirectLoadInfo = 287 CloneLoadInfoForRedirect(aResponseURI, flags); 288 289 rv = newChannel->Init( 290 aResponseURI, mCaps, static_cast<nsProxyInfo*>(mProxyInfo.get()), 291 mProxyResolveFlags, mProxyURI, mChannelId, redirectLoadInfo); 292 NS_ENSURE_SUCCESS(rv, rv); 293 294 // Normally we don't propagate the LoadInfo's service worker tainting 295 // synthesis flag on redirect. A real redirect normally will want to allow 296 // normal tainting to proceed from its starting taint. For this particular 297 // redirect, though, we are performing a redirect to communicate the URL of 298 // the service worker synthetic response itself. This redirect still 299 // represents the synthetic response, so we must preserve the flag. 300 if (redirectLoadInfo && mLoadInfo && 301 mLoadInfo->GetServiceWorkerTaintingSynthesized()) { 302 redirectLoadInfo->SynthesizeServiceWorkerTainting(mLoadInfo->GetTainting()); 303 } 304 305 rv = SetupReplacementChannel(aResponseURI, newChannel, true, flags); 306 NS_ENSURE_SUCCESS(rv, rv); 307 308 mRedirectChannel = newChannel; 309 310 MOZ_ASSERT(mBodyReader); 311 MOZ_ASSERT(!LoadApplyConversion()); 312 newChannel->SetApplyConversion(false); 313 314 rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags); 315 316 if (NS_FAILED(rv)) { 317 // Make sure to call the body callback since we took ownership 318 // above. Neither the new channel or our standard 319 // OnRedirectVerifyCallback() code will invoke the callback. Do it here. 320 bodyCallback->BodyComplete(rv); 321 322 OnRedirectVerifyCallback(rv); 323 } 324 325 return rv; 326 } 327 328 nsresult InterceptedHttpChannel::StartPump() { 329 MOZ_DIAGNOSTIC_ASSERT(!mPump); 330 MOZ_DIAGNOSTIC_ASSERT(mBodyReader); 331 332 // We don't support resuming an intercepted channel. We can't guarantee the 333 // ServiceWorker will always return the same data and we can't rely on the 334 // http cache code to detect changes. For now, just force the channel to 335 // NS_ERROR_NOT_RESUMABLE which should cause the front-end to recreate the 336 // channel without calling ResumeAt(). 337 // 338 // It would also be possible to convert this information to a range request, 339 // but its unclear if we should do that for ServiceWorker FetchEvents. See: 340 // 341 // https://github.com/w3c/ServiceWorker/issues/1201 342 if (mResumeStartPos > 0) { 343 return NS_ERROR_NOT_RESUMABLE; 344 } 345 346 // For progress we trust the content-length for the "maximum" size. 347 // We can't determine the full size from the stream itself since 348 // we may only receive the data incrementally. We can't trust 349 // Available() here. 350 // TODO: We could implement an nsIFixedLengthInputStream interface and 351 // QI to it here. This would let us determine the total length 352 // for streams that support it. See bug 1388774. 353 (void)GetContentLength(&mSynthesizedStreamLength); 354 355 nsresult rv = 356 nsInputStreamPump::Create(getter_AddRefs(mPump), mBodyReader, 0, 0, true); 357 NS_ENSURE_SUCCESS(rv, rv); 358 359 rv = mPump->AsyncRead(this); 360 NS_ENSURE_SUCCESS(rv, rv); 361 362 uint32_t suspendCount = mSuspendCount; 363 while (suspendCount--) { 364 mPump->Suspend(); 365 } 366 367 MOZ_DIAGNOSTIC_ASSERT(!mCanceled); 368 369 return rv; 370 } 371 372 nsresult InterceptedHttpChannel::OpenRedirectChannel() { 373 INTERCEPTED_LOG( 374 ("InterceptedHttpChannel::OpenRedirectChannel [%p], mRedirectChannel: %p", 375 this, mRedirectChannel.get())); 376 nsresult rv = NS_OK; 377 378 if (NS_FAILED(mStatus)) { 379 return mStatus; 380 } 381 382 if (!mRedirectChannel) { 383 return NS_ERROR_DOM_ABORT_ERR; 384 } 385 386 // Make sure to do this after we received redirect veto answer, 387 // i.e. after all sinks had been notified 388 mRedirectChannel->SetOriginalURI(mOriginalURI); 389 390 // open new channel 391 rv = mRedirectChannel->AsyncOpen(mListener); 392 NS_ENSURE_SUCCESS(rv, rv); 393 394 mStatus = NS_BINDING_REDIRECTED; 395 396 return rv; 397 } 398 399 void InterceptedHttpChannel::MaybeCallStatusAndProgress() { 400 // OnStatus() and OnProgress() must only be called on the main thread. If 401 // we are on a separate thread, then we maybe need to schedule a runnable 402 // to call them asynchronousnly. 403 if (!NS_IsMainThread()) { 404 // Check to see if we are already trying to call OnStatus/OnProgress 405 // asynchronously. If we are, then don't queue up another runnable. 406 // We don't want to flood the main thread. 407 if (mCallingStatusAndProgress) { 408 return; 409 } 410 mCallingStatusAndProgress = true; 411 412 nsCOMPtr<nsIRunnable> r = NewRunnableMethod( 413 "InterceptedHttpChannel::MaybeCallStatusAndProgress", this, 414 &InterceptedHttpChannel::MaybeCallStatusAndProgress); 415 MOZ_ALWAYS_SUCCEEDS(SchedulerGroup::Dispatch(r.forget())); 416 417 return; 418 } 419 420 MOZ_ASSERT(NS_IsMainThread()); 421 422 // We are about to capture out progress position. Clear the flag we use 423 // to de-duplicate progress report runnables. We want any further progress 424 // updates to trigger another runnable. We do this before capture the 425 // progress value since we're using atomics and not a mutex lock. 426 mCallingStatusAndProgress = false; 427 428 // Capture the current status from our atomic count. 429 int64_t progress = mProgress; 430 431 MOZ_DIAGNOSTIC_ASSERT(progress >= mProgressReported); 432 433 // Do nothing if we've already made the calls for this amount of progress 434 // or if the channel is not configured for these calls. Note, the check 435 // for mProgressSink here means we will not fire any spurious late calls 436 // after ReleaseListeners() is executed. 437 if (progress <= mProgressReported || mCanceled || !mProgressSink || 438 (mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) { 439 return; 440 } 441 442 // Capture the host name on the first set of calls to avoid doing this 443 // string processing repeatedly. 444 if (mProgressReported == 0) { 445 nsAutoCString host; 446 MOZ_ALWAYS_SUCCEEDS(mURI->GetHost(host)); 447 CopyUTF8toUTF16(host, mStatusHost); 448 } 449 450 mProgressSink->OnStatus(this, NS_NET_STATUS_READING, mStatusHost.get()); 451 452 mProgressSink->OnProgress(this, progress, mSynthesizedStreamLength); 453 454 mProgressReported = progress; 455 } 456 457 void InterceptedHttpChannel::MaybeCallBodyCallback() { 458 nsCOMPtr<nsIInterceptedBodyCallback> callback = std::move(mBodyCallback); 459 if (callback) { 460 callback->BodyComplete(mStatus); 461 } 462 } 463 464 // static 465 already_AddRefed<InterceptedHttpChannel> 466 InterceptedHttpChannel::CreateForInterception( 467 PRTime aCreationTime, const TimeStamp& aCreationTimestamp, 468 const TimeStamp& aAsyncOpenTimestamp) { 469 // Create an InterceptedHttpChannel that will trigger a FetchEvent 470 // in a ServiceWorker when opened. 471 RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel( 472 aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp); 473 474 return ref.forget(); 475 } 476 477 // static 478 already_AddRefed<InterceptedHttpChannel> 479 InterceptedHttpChannel::CreateForSynthesis( 480 const nsHttpResponseHead* aHead, nsIInputStream* aBody, 481 nsIInterceptedBodyCallback* aBodyCallback, PRTime aCreationTime, 482 const TimeStamp& aCreationTimestamp, const TimeStamp& aAsyncOpenTimestamp) { 483 MOZ_DIAGNOSTIC_ASSERT(aHead); 484 MOZ_DIAGNOSTIC_ASSERT(aBody); 485 486 // Create an InterceptedHttpChannel that already has a synthesized response. 487 // The synthetic response will be processed when opened. A FetchEvent 488 // will not be triggered. 489 RefPtr<InterceptedHttpChannel> ref = new InterceptedHttpChannel( 490 aCreationTime, aCreationTimestamp, aAsyncOpenTimestamp); 491 492 ref->mResponseHead = MakeUnique<nsHttpResponseHead>(*aHead); 493 ref->mBodyReader = aBody; 494 ref->mBodyCallback = aBodyCallback; 495 496 return ref.forget(); 497 } 498 499 NS_IMETHODIMP InterceptedHttpChannel::SetCanceledReason( 500 const nsACString& aReason) { 501 return SetCanceledReasonImpl(aReason); 502 } 503 504 NS_IMETHODIMP InterceptedHttpChannel::GetCanceledReason(nsACString& aReason) { 505 return GetCanceledReasonImpl(aReason); 506 } 507 508 NS_IMETHODIMP 509 InterceptedHttpChannel::CancelWithReason(nsresult aStatus, 510 const nsACString& aReason) { 511 return CancelWithReasonImpl(aStatus, aReason); 512 } 513 514 NS_IMETHODIMP 515 InterceptedHttpChannel::Cancel(nsresult aStatus) { 516 INTERCEPTED_LOG(("InterceptedHttpChannel::Cancel [%p]", this)); 517 // Note: This class has been designed to send all error results through 518 // Cancel(). Don't add calls directly to AsyncAbort() or 519 // DoNotifyListener(). Instead call Cancel(). 520 521 if (mCanceled) { 522 return NS_OK; 523 } 524 525 // The interception is canceled, record the finish time stamp and the final 526 // status 527 mTimeStamps.RecordTime(InterceptionTimeStamps::Canceled); 528 529 mCanceled = true; 530 531 if (mLastStatusReported && profiler_thread_is_being_profiled_for_markers()) { 532 // These do allocations/frees/etc; avoid if not active 533 // mLastStatusReported can be null if Cancel is called before we added the 534 // start marker. 535 nsAutoCString requestMethod; 536 GetRequestMethod(requestMethod); 537 538 int32_t priority = PRIORITY_NORMAL; 539 GetPriority(&priority); 540 541 uint64_t size = 0; 542 GetEncodedBodySize(&size); 543 544 profiler_add_network_marker( 545 mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_CANCEL, 546 mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown, 547 mLoadInfo->GetInnerWindowID(), 548 mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(), this, mStatus, 549 &mTransactionTimings, std::move(mSource)); 550 } 551 552 MOZ_DIAGNOSTIC_ASSERT(NS_FAILED(aStatus)); 553 if (NS_SUCCEEDED(mStatus)) { 554 mStatus = aStatus; 555 } 556 557 if (mPump) { 558 return mPump->Cancel(mStatus); 559 } 560 561 return AsyncAbort(mStatus); 562 } 563 564 NS_IMETHODIMP 565 InterceptedHttpChannel::Suspend(void) { 566 ++mSuspendCount; 567 if (mPump) { 568 return mPump->Suspend(); 569 } 570 return NS_OK; 571 } 572 573 NS_IMETHODIMP 574 InterceptedHttpChannel::Resume(void) { 575 --mSuspendCount; 576 if (mPump) { 577 return mPump->Resume(); 578 } 579 return NS_OK; 580 } 581 582 NS_IMETHODIMP 583 InterceptedHttpChannel::GetSecurityInfo( 584 nsITransportSecurityInfo** aSecurityInfo) { 585 nsCOMPtr<nsITransportSecurityInfo> ref(mSecurityInfo); 586 ref.forget(aSecurityInfo); 587 return NS_OK; 588 } 589 590 NS_IMETHODIMP 591 InterceptedHttpChannel::AsyncOpen(nsIStreamListener* aListener) { 592 INTERCEPTED_LOG(("InterceptedHttpChannel::AsyncOpen [%p], listener: %p", this, 593 aListener)); 594 nsCOMPtr<nsIStreamListener> listener(aListener); 595 596 nsresult rv = 597 nsContentSecurityManager::doContentSecurityCheck(this, listener); 598 if (NS_WARN_IF(NS_FAILED(rv))) { 599 Cancel(rv); 600 return rv; 601 } 602 if (mCanceled) { 603 return mStatus; 604 } 605 606 // After this point we should try to return NS_OK and notify the listener 607 // of the result. 608 mListener = aListener; 609 610 AsyncOpenInternal(); 611 612 return NS_OK; 613 } 614 615 NS_IMETHODIMP 616 InterceptedHttpChannel::LogBlockedCORSRequest(const nsAString& aMessage, 617 const nsACString& aCategory, 618 bool aIsWarning) { 619 // Synthetic responses should not trigger CORS blocking. 620 return NS_ERROR_NOT_IMPLEMENTED; 621 } 622 623 NS_IMETHODIMP 624 InterceptedHttpChannel::LogMimeTypeMismatch(const nsACString& aMessageName, 625 bool aWarning, 626 const nsAString& aURL, 627 const nsAString& aContentType) { 628 return NS_ERROR_NOT_IMPLEMENTED; 629 } 630 631 NS_IMETHODIMP 632 InterceptedHttpChannel::GetIsAuthChannel(bool* aIsAuthChannel) { 633 return NS_ERROR_NOT_IMPLEMENTED; 634 } 635 636 NS_IMETHODIMP 637 InterceptedHttpChannel::SetPriority(int32_t aPriority) { 638 mPriority = std::clamp<int32_t>(aPriority, INT16_MIN, INT16_MAX); 639 return NS_OK; 640 } 641 642 NS_IMETHODIMP 643 InterceptedHttpChannel::SetClassFlags(uint32_t aClassFlags) { 644 mClassOfService.SetFlags(aClassFlags); 645 return NS_OK; 646 } 647 648 NS_IMETHODIMP 649 InterceptedHttpChannel::ClearClassFlags(uint32_t aClassFlags) { 650 mClassOfService.SetFlags(~aClassFlags & mClassOfService.Flags()); 651 return NS_OK; 652 } 653 654 NS_IMETHODIMP 655 InterceptedHttpChannel::AddClassFlags(uint32_t aClassFlags) { 656 mClassOfService.SetFlags(aClassFlags | mClassOfService.Flags()); 657 return NS_OK; 658 } 659 660 NS_IMETHODIMP 661 InterceptedHttpChannel::SetClassOfService(ClassOfService cos) { 662 mClassOfService = cos; 663 return NS_OK; 664 } 665 666 NS_IMETHODIMP 667 InterceptedHttpChannel::SetIncremental(bool incremental) { 668 mClassOfService.SetIncremental(incremental); 669 return NS_OK; 670 } 671 672 NS_IMETHODIMP 673 InterceptedHttpChannel::ResumeAt(uint64_t aStartPos, 674 const nsACString& aEntityId) { 675 // We don't support resuming synthesized responses, but we do track this 676 // information so it can be passed on to the resulting nsHttpChannel if 677 // ResetInterception is called. 678 mResumeStartPos = aStartPos; 679 mResumeEntityId = aEntityId; 680 return NS_OK; 681 } 682 683 void InterceptedHttpChannel::DoNotifyListenerCleanup() { 684 // Prefer to cleanup in ReleaseListeners() as it seems to be called 685 // more consistently in necko. 686 } 687 688 void InterceptedHttpChannel::DoAsyncAbort(nsresult aStatus) { 689 (void)AsyncAbort(aStatus); 690 } 691 692 namespace { 693 694 class ResetInterceptionHeaderVisitor final : public nsIHttpHeaderVisitor { 695 nsCOMPtr<nsIHttpChannel> mTarget; 696 697 ~ResetInterceptionHeaderVisitor() = default; 698 699 NS_IMETHOD 700 VisitHeader(const nsACString& aHeader, const nsACString& aValue) override { 701 // We skip Cookie header here, since it will be added during 702 // nsHttpChannel::AsyncOpen. 703 if (aHeader.Equals(nsHttp::Cookie.val())) { 704 return NS_OK; 705 } 706 if (aValue.IsEmpty()) { 707 return mTarget->SetEmptyRequestHeader(aHeader); 708 } 709 return mTarget->SetRequestHeader(aHeader, aValue, false /* merge */); 710 } 711 712 public: 713 explicit ResetInterceptionHeaderVisitor(nsIHttpChannel* aTarget) 714 : mTarget(aTarget) { 715 MOZ_DIAGNOSTIC_ASSERT(mTarget); 716 } 717 718 NS_DECL_ISUPPORTS 719 }; 720 721 NS_IMPL_ISUPPORTS(ResetInterceptionHeaderVisitor, nsIHttpHeaderVisitor) 722 723 } // anonymous namespace 724 725 NS_IMETHODIMP 726 InterceptedHttpChannel::ResetInterception(bool aBypass) { 727 INTERCEPTED_LOG(("InterceptedHttpChannel::ResetInterception [%p] bypass: %s", 728 this, aBypass ? "true" : "false")); 729 if (mCanceled) { 730 return mStatus; 731 } 732 733 mInterceptionReset = true; 734 735 uint32_t flags = nsIChannelEventSink::REDIRECT_INTERNAL; 736 737 nsCOMPtr<nsIChannel> newChannel; 738 nsCOMPtr<nsILoadInfo> redirectLoadInfo = 739 CloneLoadInfoForRedirect(mURI, flags); 740 741 if (aBypass) { 742 redirectLoadInfo->ClearController(); 743 // TODO: Audit whether we should also be calling 744 // ServiceWorkerManager::StopControllingClient for maximum correctness. 745 } 746 747 nsresult rv = 748 NS_NewChannelInternal(getter_AddRefs(newChannel), mURI, redirectLoadInfo, 749 nullptr, // PerformanceStorage 750 nullptr, // aLoadGroup 751 nullptr, // aCallbacks 752 mLoadFlags); 753 NS_ENSURE_SUCCESS(rv, rv); 754 755 if (profiler_thread_is_being_profiled_for_markers()) { 756 nsAutoCString requestMethod; 757 GetRequestMethod(requestMethod); 758 759 int32_t priority = PRIORITY_NORMAL; 760 GetPriority(&priority); 761 762 uint64_t size = 0; 763 GetEncodedBodySize(&size); 764 765 nsAutoCString contentType; 766 mozilla::Maybe<mozilla::net::HttpVersion> httpVersion = Nothing(); 767 mozilla::Maybe<uint32_t> responseStatus = Nothing(); 768 if (mResponseHead) { 769 mResponseHead->ContentType(contentType); 770 httpVersion = Some(mResponseHead->Version()); 771 responseStatus = Some(mResponseHead->Status()); 772 } 773 774 RefPtr<HttpBaseChannel> newBaseChannel = do_QueryObject(newChannel); 775 MOZ_ASSERT(newBaseChannel, 776 "The redirect channel should be a base channel."); 777 profiler_add_network_marker( 778 mURI, requestMethod, priority, mChannelId, 779 NetworkLoadType::LOAD_REDIRECT, mLastStatusReported, TimeStamp::Now(), 780 size, kCacheUnknown, mLoadInfo->GetInnerWindowID(), 781 mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(), this, mStatus, 782 &mTransactionTimings, std::move(mSource), httpVersion, responseStatus, 783 Some(nsDependentCString(contentType.get())), mURI, flags, 784 newBaseChannel->ChannelId()); 785 } 786 787 rv = SetupReplacementChannel(mURI, newChannel, true, flags); 788 NS_ENSURE_SUCCESS(rv, rv); 789 790 // Restore the non-default headers for fallback channel. 791 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(newChannel)); 792 nsCOMPtr<nsIHttpHeaderVisitor> visitor = 793 new ResetInterceptionHeaderVisitor(httpChannel); 794 rv = VisitNonDefaultRequestHeaders(visitor); 795 NS_ENSURE_SUCCESS(rv, rv); 796 797 nsCOMPtr<nsITimedChannel> newTimedChannel = do_QueryInterface(newChannel); 798 if (newTimedChannel) { 799 if (!mAsyncOpenTime.IsNull()) { 800 newTimedChannel->SetAsyncOpen(mAsyncOpenTime); 801 } 802 if (!mChannelCreationTimestamp.IsNull()) { 803 newTimedChannel->SetChannelCreation(mChannelCreationTimestamp); 804 } 805 } 806 807 if (mRedirectMode != nsIHttpChannelInternal::REDIRECT_MODE_MANUAL) { 808 nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; 809 rv = newChannel->GetLoadFlags(&loadFlags); 810 NS_ENSURE_SUCCESS(rv, rv); 811 loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; 812 rv = newChannel->SetLoadFlags(loadFlags); 813 NS_ENSURE_SUCCESS(rv, rv); 814 } 815 816 mRedirectChannel = std::move(newChannel); 817 818 rv = gHttpHandler->AsyncOnChannelRedirect(this, mRedirectChannel, flags); 819 820 if (NS_FAILED(rv)) { 821 OnRedirectVerifyCallback(rv); 822 } else { 823 // ResetInterception success, record the finish time stamps and the final 824 // status. 825 mTimeStamps.RecordTime(InterceptionTimeStamps::Reset); 826 } 827 828 return rv; 829 } 830 831 NS_IMETHODIMP 832 InterceptedHttpChannel::SynthesizeStatus(uint16_t aStatus, 833 const nsACString& aReason) { 834 if (mCanceled) { 835 return mStatus; 836 } 837 838 if (!mSynthesizedResponseHead) { 839 mSynthesizedResponseHead.reset(new nsHttpResponseHead()); 840 } 841 842 nsAutoCString statusLine; 843 statusLine.AppendLiteral("HTTP/1.1 "); 844 statusLine.AppendInt(aStatus); 845 statusLine.AppendLiteral(" "); 846 statusLine.Append(aReason); 847 848 NS_ENSURE_SUCCESS(mSynthesizedResponseHead->ParseStatusLine(statusLine), 849 NS_ERROR_FAILURE); 850 return NS_OK; 851 } 852 853 NS_IMETHODIMP 854 InterceptedHttpChannel::SynthesizeHeader(const nsACString& aName, 855 const nsACString& aValue) { 856 if (mCanceled) { 857 return mStatus; 858 } 859 860 if (!mSynthesizedResponseHead) { 861 mSynthesizedResponseHead.reset(new nsHttpResponseHead()); 862 } 863 864 nsAutoCString header = aName + ": "_ns + aValue; 865 // Overwrite any existing header. 866 nsresult rv = mSynthesizedResponseHead->ParseHeaderLine(header); 867 NS_ENSURE_SUCCESS(rv, rv); 868 return NS_OK; 869 } 870 871 NS_IMETHODIMP 872 InterceptedHttpChannel::StartSynthesizedResponse( 873 nsIInputStream* aBody, nsIInterceptedBodyCallback* aBodyCallback, 874 nsICacheInfoChannel* aSynthesizedCacheInfo, const nsACString& aFinalURLSpec, 875 bool aResponseRedirected) { 876 nsresult rv = NS_OK; 877 878 auto autoCleanup = MakeScopeExit([&] { 879 // Auto-cancel on failure. Do this first to get mStatus set, if necessary. 880 if (NS_FAILED(rv)) { 881 Cancel(rv); 882 } 883 884 // If we early exit before taking ownership of the body, then automatically 885 // invoke the callback. This could be due to an error or because we're not 886 // going to consume it due to a redirect, etc. 887 if (aBodyCallback) { 888 aBodyCallback->BodyComplete(mStatus); 889 } 890 }); 891 892 if (NS_FAILED(mStatus)) { 893 // Return NS_OK. The channel should fire callbacks with an error code 894 // if it was cancelled before this point. 895 return NS_OK; 896 } 897 898 // Take ownership of the body callbacks If a failure occurs we will 899 // automatically Cancel() the channel. This will then invoke OnStopRequest() 900 // which will invoke the correct callback. In the case of an opaque response 901 // redirect we pass ownership of the callback to the new channel. 902 mBodyCallback = aBodyCallback; 903 aBodyCallback = nullptr; 904 905 mSynthesizedCacheInfo = aSynthesizedCacheInfo; 906 907 if (!mSynthesizedResponseHead) { 908 mSynthesizedResponseHead.reset(new nsHttpResponseHead()); 909 } 910 911 mResponseHead = std::move(mSynthesizedResponseHead); 912 913 if (ShouldRedirect()) { 914 rv = FollowSyntheticRedirect(); 915 NS_ENSURE_SUCCESS(rv, rv); 916 917 return NS_OK; 918 } 919 920 // Intercepted responses should already be decoded. 921 SetApplyConversion(false); 922 923 // Errors and redirects may not have a body. Synthesize an empty string 924 // stream here so later code can be simpler. 925 mBodyReader = aBody; 926 if (!mBodyReader) { 927 rv = NS_NewCStringInputStream(getter_AddRefs(mBodyReader), ""_ns); 928 NS_ENSURE_SUCCESS(rv, rv); 929 } 930 931 nsCOMPtr<nsIURI> responseURI; 932 if (!aFinalURLSpec.IsEmpty()) { 933 rv = NS_NewURI(getter_AddRefs(responseURI), aFinalURLSpec); 934 NS_ENSURE_SUCCESS(rv, rv); 935 } else { 936 responseURI = mURI; 937 } 938 939 bool equal = false; 940 (void)mURI->Equals(responseURI, &equal); 941 if (!equal) { 942 rv = RedirectForResponseURL(responseURI, aResponseRedirected); 943 NS_ENSURE_SUCCESS(rv, rv); 944 945 return NS_OK; 946 } 947 948 rv = StartPump(); 949 NS_ENSURE_SUCCESS(rv, rv); 950 951 return NS_OK; 952 } 953 954 NS_IMETHODIMP 955 InterceptedHttpChannel::FinishSynthesizedResponse() { 956 if (mCanceled) { 957 // Return NS_OK. The channel should fire callbacks with an error code 958 // if it was cancelled before this point. 959 return NS_OK; 960 } 961 962 return NS_OK; 963 } 964 965 NS_IMETHODIMP 966 InterceptedHttpChannel::CancelInterception(nsresult aStatus) { 967 return Cancel(aStatus); 968 } 969 970 NS_IMETHODIMP 971 InterceptedHttpChannel::GetChannel(nsIChannel** aChannel) { 972 nsCOMPtr<nsIChannel> ref(this); 973 ref.forget(aChannel); 974 return NS_OK; 975 } 976 977 NS_IMETHODIMP 978 InterceptedHttpChannel::GetSecureUpgradedChannelURI( 979 nsIURI** aSecureUpgradedChannelURI) { 980 nsCOMPtr<nsIURI> ref(mURI); 981 ref.forget(aSecureUpgradedChannelURI); 982 return NS_OK; 983 } 984 985 NS_IMETHODIMP 986 InterceptedHttpChannel::SetChannelInfo( 987 mozilla::dom::ChannelInfo* aChannelInfo) { 988 return aChannelInfo->ResurrectInfoOnChannel(this); 989 } 990 991 NS_IMETHODIMP 992 InterceptedHttpChannel::GetInternalContentPolicyType( 993 nsContentPolicyType* aPolicyType) { 994 if (mLoadInfo) { 995 *aPolicyType = mLoadInfo->InternalContentPolicyType(); 996 } 997 return NS_OK; 998 } 999 1000 NS_IMETHODIMP 1001 InterceptedHttpChannel::GetConsoleReportCollector( 1002 nsIConsoleReportCollector** aConsoleReportCollector) { 1003 nsCOMPtr<nsIConsoleReportCollector> ref(this); 1004 ref.forget(aConsoleReportCollector); 1005 return NS_OK; 1006 } 1007 1008 NS_IMETHODIMP 1009 InterceptedHttpChannel::SetFetchHandlerStart(TimeStamp aTimeStamp) { 1010 mTimeStamps.RecordTime(std::move(aTimeStamp)); 1011 return NS_OK; 1012 } 1013 1014 NS_IMETHODIMP 1015 InterceptedHttpChannel::SetFetchHandlerFinish(TimeStamp aTimeStamp) { 1016 mTimeStamps.RecordTime(std::move(aTimeStamp)); 1017 return NS_OK; 1018 } 1019 1020 NS_IMETHODIMP 1021 InterceptedHttpChannel::SetRemoteWorkerLaunchStart(TimeStamp aTimeStamp) { 1022 mServiceWorkerLaunchStart = aTimeStamp > mTimeStamps.mInterceptionStart 1023 ? aTimeStamp 1024 : mTimeStamps.mInterceptionStart; 1025 return NS_OK; 1026 } 1027 1028 NS_IMETHODIMP 1029 InterceptedHttpChannel::SetRemoteWorkerLaunchEnd(TimeStamp aTimeStamp) { 1030 mServiceWorkerLaunchEnd = aTimeStamp > mTimeStamps.mInterceptionStart 1031 ? aTimeStamp 1032 : mTimeStamps.mInterceptionStart; 1033 return NS_OK; 1034 } 1035 1036 NS_IMETHODIMP 1037 InterceptedHttpChannel::SetLaunchServiceWorkerStart(TimeStamp aTimeStamp) { 1038 mServiceWorkerLaunchStart = aTimeStamp; 1039 return NS_OK; 1040 } 1041 1042 NS_IMETHODIMP 1043 InterceptedHttpChannel::GetLaunchServiceWorkerStart(TimeStamp* aRetVal) { 1044 MOZ_ASSERT(aRetVal); 1045 *aRetVal = mServiceWorkerLaunchStart; 1046 return NS_OK; 1047 } 1048 1049 NS_IMETHODIMP 1050 InterceptedHttpChannel::SetLaunchServiceWorkerEnd(TimeStamp aTimeStamp) { 1051 mServiceWorkerLaunchEnd = aTimeStamp; 1052 return NS_OK; 1053 } 1054 1055 NS_IMETHODIMP 1056 InterceptedHttpChannel::GetLaunchServiceWorkerEnd(TimeStamp* aRetVal) { 1057 MOZ_ASSERT(aRetVal); 1058 *aRetVal = mServiceWorkerLaunchEnd; 1059 return NS_OK; 1060 } 1061 1062 NS_IMETHODIMP 1063 InterceptedHttpChannel::GetDispatchFetchEventStart(TimeStamp* aRetVal) { 1064 MOZ_ASSERT(aRetVal); 1065 *aRetVal = mTimeStamps.mInterceptionStart; 1066 return NS_OK; 1067 } 1068 1069 NS_IMETHODIMP 1070 InterceptedHttpChannel::GetDispatchFetchEventEnd(TimeStamp* aRetVal) { 1071 MOZ_ASSERT(aRetVal); 1072 *aRetVal = mTimeStamps.mFetchHandlerStart; 1073 return NS_OK; 1074 } 1075 1076 NS_IMETHODIMP 1077 InterceptedHttpChannel::GetHandleFetchEventStart(TimeStamp* aRetVal) { 1078 MOZ_ASSERT(aRetVal); 1079 *aRetVal = mTimeStamps.mFetchHandlerStart; 1080 return NS_OK; 1081 } 1082 1083 NS_IMETHODIMP 1084 InterceptedHttpChannel::GetHandleFetchEventEnd(TimeStamp* aRetVal) { 1085 MOZ_ASSERT(aRetVal); 1086 *aRetVal = mTimeStamps.mFetchHandlerFinish; 1087 return NS_OK; 1088 } 1089 1090 NS_IMETHODIMP 1091 InterceptedHttpChannel::GetIsReset(bool* aResult) { 1092 *aResult = mInterceptionReset; 1093 return NS_OK; 1094 } 1095 1096 NS_IMETHODIMP 1097 InterceptedHttpChannel::SetReleaseHandle(nsISupports* aHandle) { 1098 mReleaseHandle = aHandle; 1099 return NS_OK; 1100 } 1101 1102 NS_IMETHODIMP 1103 InterceptedHttpChannel::OnRedirectVerifyCallback(nsresult rv) { 1104 MOZ_ASSERT(NS_IsMainThread()); 1105 1106 if (NS_SUCCEEDED(rv)) { 1107 rv = OpenRedirectChannel(); 1108 } 1109 1110 nsCOMPtr<nsIRedirectResultListener> hook; 1111 GetCallback(hook); 1112 if (hook) { 1113 hook->OnRedirectResult(rv); 1114 } 1115 1116 if (NS_FAILED(rv)) { 1117 Cancel(rv); 1118 } 1119 1120 MaybeCallBodyCallback(); 1121 1122 StoreIsPending(false); 1123 // We can only release listeners after the redirected channel really owns 1124 // mListener. Otherwise, the OnStart/OnStopRequest functions of mListener will 1125 // not be called. 1126 if (NS_SUCCEEDED(rv)) { 1127 ReleaseListeners(); 1128 } 1129 1130 return NS_OK; 1131 } 1132 1133 NS_IMETHODIMP 1134 InterceptedHttpChannel::OnStartRequest(nsIRequest* aRequest) { 1135 INTERCEPTED_LOG(("InterceptedHttpChannel::OnStartRequest [%p]", this)); 1136 MOZ_ASSERT(NS_IsMainThread()); 1137 1138 if (!mProgressSink) { 1139 GetCallback(mProgressSink); 1140 } 1141 1142 MOZ_ASSERT_IF(!mLoadInfo->GetServiceWorkerTaintingSynthesized(), 1143 mLoadInfo->GetLoadingPrincipal()); 1144 // No need to do ORB checks if these conditions hold. 1145 MOZ_DIAGNOSTIC_ASSERT(mLoadInfo->GetServiceWorkerTaintingSynthesized() || 1146 mLoadInfo->GetLoadingPrincipal()->IsSystemPrincipal()); 1147 1148 if (mPump && mLoadFlags & LOAD_CALL_CONTENT_SNIFFERS) { 1149 mPump->PeekStream(CallTypeSniffers, static_cast<nsIChannel*>(this)); 1150 } 1151 1152 nsresult rv = ProcessCrossOriginEmbedderPolicyHeader(); 1153 if (NS_FAILED(rv)) { 1154 mStatus = NS_ERROR_BLOCKED_BY_POLICY; 1155 Cancel(mStatus); 1156 } 1157 1158 rv = ProcessCrossOriginResourcePolicyHeader(); 1159 if (NS_FAILED(rv)) { 1160 mStatus = NS_ERROR_DOM_CORP_FAILED; 1161 Cancel(mStatus); 1162 } 1163 1164 rv = ComputeCrossOriginOpenerPolicyMismatch(); 1165 if (rv == NS_ERROR_BLOCKED_BY_POLICY) { 1166 mStatus = NS_ERROR_BLOCKED_BY_POLICY; 1167 Cancel(mStatus); 1168 } 1169 1170 rv = ValidateMIMEType(); 1171 if (NS_FAILED(rv)) { 1172 mStatus = rv; 1173 Cancel(mStatus); 1174 } 1175 1176 StoreOnStartRequestCalled(true); 1177 if (mListener) { 1178 return mListener->OnStartRequest(this); 1179 } 1180 return NS_OK; 1181 } 1182 1183 NS_IMETHODIMP 1184 InterceptedHttpChannel::OnStopRequest(nsIRequest* aRequest, nsresult aStatus) { 1185 INTERCEPTED_LOG(("InterceptedHttpChannel::OnStopRequest [%p]", this)); 1186 MOZ_ASSERT(NS_IsMainThread()); 1187 1188 if (NS_SUCCEEDED(mStatus)) { 1189 mStatus = aStatus; 1190 } 1191 1192 MaybeCallBodyCallback(); 1193 1194 mTimeStamps.RecordTime(InterceptionTimeStamps::Synthesized); 1195 1196 // Its possible that we have any async runnable queued to report some 1197 // progress when OnStopRequest() is triggered. Report any left over 1198 // progress immediately. The extra runnable will then do nothing thanks 1199 // to the ReleaseListeners() call below. 1200 MaybeCallStatusAndProgress(); 1201 1202 StoreIsPending(false); 1203 1204 // Register entry to the PerformanceStorage resource timing 1205 MaybeReportTimingData(); 1206 1207 if (profiler_thread_is_being_profiled_for_markers()) { 1208 // These do allocations/frees/etc; avoid if not active 1209 nsAutoCString requestMethod; 1210 GetRequestMethod(requestMethod); 1211 1212 int32_t priority = PRIORITY_NORMAL; 1213 GetPriority(&priority); 1214 1215 uint64_t size = 0; 1216 GetEncodedBodySize(&size); 1217 1218 nsAutoCString contentType; 1219 mozilla::Maybe<mozilla::net::HttpVersion> httpVersion = Nothing(); 1220 mozilla::Maybe<uint32_t> responseStatus = Nothing(); 1221 if (mResponseHead) { 1222 mResponseHead->ContentType(contentType); 1223 httpVersion = Some(mResponseHead->Version()); 1224 responseStatus = Some(mResponseHead->Status()); 1225 } 1226 profiler_add_network_marker( 1227 mURI, requestMethod, priority, mChannelId, NetworkLoadType::LOAD_STOP, 1228 mLastStatusReported, TimeStamp::Now(), size, kCacheUnknown, 1229 mLoadInfo->GetInnerWindowID(), 1230 mLoadInfo->GetOriginAttributes().IsPrivateBrowsing(), this, mStatus, 1231 &mTransactionTimings, std::move(mSource), httpVersion, responseStatus, 1232 Some(nsDependentCString(contentType.get()))); 1233 } 1234 1235 nsresult rv = NS_OK; 1236 if (mListener) { 1237 rv = mListener->OnStopRequest(this, mStatus); 1238 } 1239 1240 gHttpHandler->OnStopRequest(this); 1241 1242 ReleaseListeners(); 1243 1244 return rv; 1245 } 1246 1247 NS_IMETHODIMP 1248 InterceptedHttpChannel::OnDataAvailable(nsIRequest* aRequest, 1249 nsIInputStream* aInputStream, 1250 uint64_t aOffset, uint32_t aCount) { 1251 // Any thread if the channel has been retargeted. 1252 1253 if (mCanceled || !mListener) { 1254 // If there is no listener, we still need to drain the stream in order 1255 // maintain necko invariants. 1256 uint32_t unused = 0; 1257 aInputStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &unused); 1258 return mStatus; 1259 } 1260 if (mProgressSink) { 1261 if (!(mLoadFlags & HttpBaseChannel::LOAD_BACKGROUND)) { 1262 mProgress = aOffset + aCount; 1263 MaybeCallStatusAndProgress(); 1264 } 1265 } 1266 1267 return mListener->OnDataAvailable(this, aInputStream, aOffset, aCount); 1268 } 1269 1270 NS_IMETHODIMP 1271 InterceptedHttpChannel::OnDataFinished(nsresult aStatus) { 1272 if (mCanceled || !mListener) { 1273 return aStatus; 1274 } 1275 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = 1276 do_QueryInterface(mListener); 1277 if (retargetableListener) { 1278 return retargetableListener->OnDataFinished(aStatus); 1279 } 1280 1281 return NS_OK; 1282 } 1283 1284 NS_IMETHODIMP 1285 InterceptedHttpChannel::RetargetDeliveryTo(nsISerialEventTarget* aNewTarget) { 1286 MOZ_ASSERT(NS_IsMainThread()); 1287 NS_ENSURE_ARG(aNewTarget); 1288 1289 // If retargeting to the main thread, do nothing. 1290 if (aNewTarget->IsOnCurrentThread()) { 1291 return NS_OK; 1292 } 1293 1294 // Retargeting is only valid during OnStartRequest for nsIChannels. So 1295 // we should only be called if we have a pump. 1296 if (!mPump) { 1297 return NS_ERROR_NOT_AVAILABLE; 1298 } 1299 1300 return mPump->RetargetDeliveryTo(aNewTarget); 1301 } 1302 1303 NS_IMETHODIMP 1304 InterceptedHttpChannel::GetDeliveryTarget(nsISerialEventTarget** aEventTarget) { 1305 if (!mPump) { 1306 return NS_ERROR_NOT_AVAILABLE; 1307 } 1308 return mPump->GetDeliveryTarget(aEventTarget); 1309 } 1310 1311 NS_IMETHODIMP 1312 InterceptedHttpChannel::CheckListenerChain() { 1313 MOZ_ASSERT(NS_IsMainThread()); 1314 nsresult rv = NS_OK; 1315 nsCOMPtr<nsIThreadRetargetableStreamListener> retargetableListener = 1316 do_QueryInterface(mListener, &rv); 1317 if (retargetableListener) { 1318 rv = retargetableListener->CheckListenerChain(); 1319 } 1320 return rv; 1321 } 1322 1323 //----------------------------------------------------------------------------- 1324 // InterceptedHttpChannel::nsICacheInfoChannel 1325 //----------------------------------------------------------------------------- 1326 // InterceptedHttpChannel does not really implement the nsICacheInfoChannel 1327 // interface, we tranfers parameters to the saved 1328 // nsICacheInfoChannel(mSynthesizedCacheInfo) from StartSynthesizedResponse. And 1329 // we return false in IsFromCache and NS_ERROR_NOT_AVAILABLE for all other 1330 // methods while the saved mSynthesizedCacheInfo does not exist. 1331 NS_IMETHODIMP 1332 InterceptedHttpChannel::IsFromCache(bool* value) { 1333 if (mSynthesizedCacheInfo) { 1334 return mSynthesizedCacheInfo->IsFromCache(value); 1335 } 1336 *value = false; 1337 return NS_OK; 1338 } 1339 1340 NS_IMETHODIMP 1341 InterceptedHttpChannel::HasCacheEntry(bool* value) { 1342 if (mSynthesizedCacheInfo) { 1343 return mSynthesizedCacheInfo->HasCacheEntry(value); 1344 } 1345 *value = false; 1346 return NS_OK; 1347 } 1348 1349 NS_IMETHODIMP 1350 InterceptedHttpChannel::IsRacing(bool* value) { 1351 if (mSynthesizedCacheInfo) { 1352 return mSynthesizedCacheInfo->IsRacing(value); 1353 } 1354 *value = false; 1355 return NS_OK; 1356 } 1357 1358 NS_IMETHODIMP 1359 InterceptedHttpChannel::GetCacheEntryId(uint64_t* aCacheEntryId) { 1360 if (mSynthesizedCacheInfo) { 1361 return mSynthesizedCacheInfo->GetCacheEntryId(aCacheEntryId); 1362 } 1363 return NS_ERROR_NOT_AVAILABLE; 1364 } 1365 1366 NS_IMETHODIMP 1367 InterceptedHttpChannel::GetCacheTokenFetchCount(uint32_t* _retval) { 1368 NS_ENSURE_ARG_POINTER(_retval); 1369 1370 if (mSynthesizedCacheInfo) { 1371 return mSynthesizedCacheInfo->GetCacheTokenFetchCount(_retval); 1372 } 1373 return NS_ERROR_NOT_AVAILABLE; 1374 } 1375 1376 NS_IMETHODIMP 1377 InterceptedHttpChannel::GetCacheTokenExpirationTime(uint32_t* _retval) { 1378 NS_ENSURE_ARG_POINTER(_retval); 1379 1380 if (mSynthesizedCacheInfo) { 1381 return mSynthesizedCacheInfo->GetCacheTokenExpirationTime(_retval); 1382 } 1383 return NS_ERROR_NOT_AVAILABLE; 1384 } 1385 1386 NS_IMETHODIMP 1387 InterceptedHttpChannel::SetAllowStaleCacheContent( 1388 bool aAllowStaleCacheContent) { 1389 if (mSynthesizedCacheInfo) { 1390 return mSynthesizedCacheInfo->SetAllowStaleCacheContent( 1391 aAllowStaleCacheContent); 1392 } 1393 return NS_ERROR_NOT_AVAILABLE; 1394 } 1395 1396 NS_IMETHODIMP 1397 InterceptedHttpChannel::GetAllowStaleCacheContent( 1398 bool* aAllowStaleCacheContent) { 1399 if (mSynthesizedCacheInfo) { 1400 return mSynthesizedCacheInfo->GetAllowStaleCacheContent( 1401 aAllowStaleCacheContent); 1402 } 1403 return NS_ERROR_NOT_AVAILABLE; 1404 } 1405 1406 NS_IMETHODIMP 1407 InterceptedHttpChannel::SetForceValidateCacheContent( 1408 bool aForceValidateCacheContent) { 1409 // We store aForceValidateCacheContent locally because 1410 // mSynthesizedCacheInfo isn't present until a response 1411 // is actually synthesized, which is too late for the value 1412 // to be forwarded during the redirect to the intercepted 1413 // channel. 1414 StoreForceValidateCacheContent(aForceValidateCacheContent); 1415 1416 if (mSynthesizedCacheInfo) { 1417 return mSynthesizedCacheInfo->SetForceValidateCacheContent( 1418 aForceValidateCacheContent); 1419 } 1420 return NS_OK; 1421 } 1422 NS_IMETHODIMP 1423 InterceptedHttpChannel::GetForceValidateCacheContent( 1424 bool* aForceValidateCacheContent) { 1425 *aForceValidateCacheContent = LoadForceValidateCacheContent(); 1426 #ifdef DEBUG 1427 if (mSynthesizedCacheInfo) { 1428 bool synthesizedForceValidateCacheContent; 1429 mSynthesizedCacheInfo->GetForceValidateCacheContent( 1430 &synthesizedForceValidateCacheContent); 1431 MOZ_ASSERT(*aForceValidateCacheContent == 1432 synthesizedForceValidateCacheContent); 1433 } 1434 #endif 1435 return NS_OK; 1436 } 1437 1438 NS_IMETHODIMP 1439 InterceptedHttpChannel::GetPreferCacheLoadOverBypass( 1440 bool* aPreferCacheLoadOverBypass) { 1441 if (mSynthesizedCacheInfo) { 1442 return mSynthesizedCacheInfo->GetPreferCacheLoadOverBypass( 1443 aPreferCacheLoadOverBypass); 1444 } 1445 return NS_ERROR_NOT_AVAILABLE; 1446 } 1447 1448 NS_IMETHODIMP 1449 InterceptedHttpChannel::SetPreferCacheLoadOverBypass( 1450 bool aPreferCacheLoadOverBypass) { 1451 if (mSynthesizedCacheInfo) { 1452 return mSynthesizedCacheInfo->SetPreferCacheLoadOverBypass( 1453 aPreferCacheLoadOverBypass); 1454 } 1455 return NS_ERROR_NOT_AVAILABLE; 1456 } 1457 1458 NS_IMETHODIMP 1459 InterceptedHttpChannel::PreferAlternativeDataType( 1460 const nsACString& aType, const nsACString& aContentType, 1461 PreferredAlternativeDataDeliveryType aDeliverAltData) { 1462 ENSURE_CALLED_BEFORE_ASYNC_OPEN(); 1463 mPreferredCachedAltDataTypes.AppendElement(PreferredAlternativeDataTypeParams( 1464 nsCString(aType), nsCString(aContentType), aDeliverAltData)); 1465 return NS_OK; 1466 } 1467 1468 const nsTArray<PreferredAlternativeDataTypeParams>& 1469 InterceptedHttpChannel::PreferredAlternativeDataTypes() { 1470 return mPreferredCachedAltDataTypes; 1471 } 1472 1473 NS_IMETHODIMP 1474 InterceptedHttpChannel::GetAlternativeDataType(nsACString& aType) { 1475 if (mSynthesizedCacheInfo) { 1476 return mSynthesizedCacheInfo->GetAlternativeDataType(aType); 1477 } 1478 return NS_ERROR_NOT_AVAILABLE; 1479 } 1480 1481 NS_IMETHODIMP 1482 InterceptedHttpChannel::GetCacheEntryWriteHandle( 1483 nsICacheEntryWriteHandle** _retval) { 1484 if (mSynthesizedCacheInfo) { 1485 return mSynthesizedCacheInfo->GetCacheEntryWriteHandle(_retval); 1486 } 1487 return NS_ERROR_NOT_AVAILABLE; 1488 } 1489 1490 NS_IMETHODIMP 1491 InterceptedHttpChannel::OpenAlternativeOutputStream( 1492 const nsACString& type, int64_t predictedSize, 1493 nsIAsyncOutputStream** _retval) { 1494 if (mSynthesizedCacheInfo) { 1495 return mSynthesizedCacheInfo->OpenAlternativeOutputStream( 1496 type, predictedSize, _retval); 1497 } 1498 return NS_ERROR_NOT_AVAILABLE; 1499 } 1500 1501 NS_IMETHODIMP 1502 InterceptedHttpChannel::GetOriginalInputStream( 1503 nsIInputStreamReceiver* aReceiver) { 1504 if (mSynthesizedCacheInfo) { 1505 return mSynthesizedCacheInfo->GetOriginalInputStream(aReceiver); 1506 } 1507 return NS_ERROR_NOT_AVAILABLE; 1508 } 1509 1510 NS_IMETHODIMP 1511 InterceptedHttpChannel::GetAlternativeDataInputStream( 1512 nsIInputStream** aInputStream) { 1513 if (mSynthesizedCacheInfo) { 1514 return mSynthesizedCacheInfo->GetAlternativeDataInputStream(aInputStream); 1515 } 1516 return NS_ERROR_NOT_AVAILABLE; 1517 } 1518 1519 NS_IMETHODIMP 1520 InterceptedHttpChannel::GetCacheKey(uint32_t* key) { 1521 if (mSynthesizedCacheInfo) { 1522 return mSynthesizedCacheInfo->GetCacheKey(key); 1523 } 1524 return NS_ERROR_NOT_AVAILABLE; 1525 } 1526 1527 NS_IMETHODIMP 1528 InterceptedHttpChannel::SetCacheKey(uint32_t key) { 1529 if (mSynthesizedCacheInfo) { 1530 return mSynthesizedCacheInfo->SetCacheKey(key); 1531 } 1532 return NS_ERROR_NOT_AVAILABLE; 1533 } 1534 1535 NS_IMETHODIMP 1536 InterceptedHttpChannel::GetCacheDisposition(CacheDisposition* aDisposition) { 1537 if (mSynthesizedCacheInfo) { 1538 return mSynthesizedCacheInfo->GetCacheDisposition(aDisposition); 1539 } 1540 return NS_ERROR_NOT_AVAILABLE; 1541 } 1542 1543 // InterceptionTimeStamps implementation 1544 InterceptedHttpChannel::InterceptionTimeStamps::InterceptionTimeStamps() 1545 : mStage(InterceptedHttpChannel::InterceptionTimeStamps::InterceptionStart), 1546 mStatus(InterceptedHttpChannel::InterceptionTimeStamps::Created) {} 1547 1548 void InterceptedHttpChannel::InterceptionTimeStamps::Init( 1549 nsIChannel* aChannel) { 1550 MOZ_ASSERT(aChannel); 1551 MOZ_ASSERT(mStatus == Created); 1552 1553 mStatus = Initialized; 1554 1555 mIsNonSubresourceRequest = nsContentUtils::IsNonSubresourceRequest(aChannel); 1556 mKey = mIsNonSubresourceRequest ? "navigation"_ns : "subresource"_ns; 1557 nsCOMPtr<nsIInterceptedChannel> interceptedChannel = 1558 do_QueryInterface(aChannel); 1559 // It must be a InterceptedHttpChannel 1560 MOZ_ASSERT(interceptedChannel); 1561 if (!mIsNonSubresourceRequest) { 1562 interceptedChannel->GetSubresourceTimeStampKey(aChannel, mSubresourceKey); 1563 } 1564 } 1565 1566 void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime( 1567 InterceptedHttpChannel::InterceptionTimeStamps::Status&& aStatus, 1568 TimeStamp&& aTimeStamp) { 1569 // Only allow passing Synthesized, Reset, Redirected, and Canceled in this 1570 // method. 1571 MOZ_ASSERT(aStatus == Synthesized || aStatus == Reset || 1572 aStatus == Canceled || aStatus == Redirected); 1573 if (mStatus == Canceled) { 1574 return; 1575 } 1576 1577 // If current status is not Initialized, only Canceled can be recorded. 1578 // That means it is canceled after other operation is done, ex. synthesized. 1579 MOZ_ASSERT(mStatus == Initialized || aStatus == Canceled); 1580 1581 switch (mStatus) { 1582 case Initialized: 1583 mStatus = aStatus; 1584 break; 1585 case Synthesized: 1586 mStatus = CanceledAfterSynthesized; 1587 break; 1588 case Reset: 1589 mStatus = CanceledAfterReset; 1590 break; 1591 case Redirected: 1592 mStatus = CanceledAfterRedirected; 1593 break; 1594 // Channel is cancelled before calling AsyncOpenInternal(), no need to 1595 // record the cancel time stamp. 1596 case Created: 1597 return; 1598 default: 1599 MOZ_ASSERT(false); 1600 break; 1601 } 1602 1603 RecordTimeInternal(std::move(aTimeStamp)); 1604 } 1605 1606 void InterceptedHttpChannel::InterceptionTimeStamps::RecordTime( 1607 TimeStamp&& aTimeStamp) { 1608 MOZ_ASSERT(mStatus == Initialized || mStatus == Canceled); 1609 if (mStatus == Canceled) { 1610 return; 1611 } 1612 RecordTimeInternal(std::move(aTimeStamp)); 1613 } 1614 1615 void InterceptedHttpChannel::InterceptionTimeStamps::RecordTimeInternal( 1616 TimeStamp&& aTimeStamp) { 1617 MOZ_ASSERT(mStatus != Created); 1618 1619 if (mStatus == Canceled && mStage != InterceptionFinish) { 1620 mFetchHandlerStart = aTimeStamp; 1621 mFetchHandlerFinish = aTimeStamp; 1622 mStage = InterceptionFinish; 1623 } 1624 1625 switch (mStage) { 1626 case InterceptionStart: { 1627 MOZ_ASSERT(mInterceptionStart.IsNull()); 1628 mInterceptionStart = aTimeStamp; 1629 mStage = FetchHandlerStart; 1630 break; 1631 } 1632 case (FetchHandlerStart): { 1633 MOZ_ASSERT(mFetchHandlerStart.IsNull()); 1634 mFetchHandlerStart = aTimeStamp; 1635 mStage = FetchHandlerFinish; 1636 break; 1637 } 1638 case (FetchHandlerFinish): { 1639 MOZ_ASSERT(mFetchHandlerFinish.IsNull()); 1640 mFetchHandlerFinish = aTimeStamp; 1641 mStage = InterceptionFinish; 1642 break; 1643 } 1644 case InterceptionFinish: { 1645 mInterceptionFinish = aTimeStamp; 1646 SaveTimeStamps(); 1647 return; 1648 } 1649 default: { 1650 return; 1651 } 1652 } 1653 } 1654 1655 void InterceptedHttpChannel::InterceptionTimeStamps::GenKeysWithStatus( 1656 nsCString& aKey, nsCString& aSubresourceKey) { 1657 nsAutoCString statusString; 1658 switch (mStatus) { 1659 case Synthesized: 1660 statusString = "synthesized"_ns; 1661 break; 1662 case Reset: 1663 statusString = "reset"_ns; 1664 break; 1665 case Redirected: 1666 statusString = "redirected"_ns; 1667 break; 1668 case Canceled: 1669 statusString = "canceled"_ns; 1670 break; 1671 case CanceledAfterSynthesized: 1672 statusString = "canceled-after-synthesized"_ns; 1673 break; 1674 case CanceledAfterReset: 1675 statusString = "canceled-after-reset"_ns; 1676 break; 1677 case CanceledAfterRedirected: 1678 statusString = "canceled-after-redirected"_ns; 1679 break; 1680 default: 1681 return; 1682 } 1683 aKey = mKey; 1684 aSubresourceKey = mSubresourceKey; 1685 aKey.AppendLiteral("_"); 1686 aSubresourceKey.AppendLiteral("_"); 1687 aKey.Append(statusString); 1688 aSubresourceKey.Append(statusString); 1689 } 1690 1691 void InterceptedHttpChannel::InterceptionTimeStamps::SaveTimeStamps() { 1692 MOZ_ASSERT(mStatus != Initialized && mStatus != Created); 1693 1694 if (mStatus == Reset) { 1695 glean::service_worker::fetch_event_channel_reset.Get(mKey) 1696 .AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish); 1697 if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) { 1698 glean::service_worker::fetch_event_channel_reset.Get(mSubresourceKey) 1699 .AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish); 1700 } 1701 } else if (mStatus == Synthesized) { 1702 glean::service_worker::fetch_event_finish_synthesized_response.Get(mKey) 1703 .AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish); 1704 if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) { 1705 glean::service_worker::fetch_event_finish_synthesized_response 1706 .Get(mSubresourceKey) 1707 .AccumulateRawDuration(mInterceptionFinish - mFetchHandlerFinish); 1708 } 1709 } 1710 1711 if (!mFetchHandlerStart.IsNull()) { 1712 glean::service_worker::fetch_event_dispatch.Get(mKey).AccumulateRawDuration( 1713 mFetchHandlerStart - mInterceptionStart); 1714 1715 if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) { 1716 glean::service_worker::fetch_event_dispatch.Get(mSubresourceKey) 1717 .AccumulateRawDuration(mFetchHandlerStart - mInterceptionStart); 1718 } 1719 } 1720 1721 nsAutoCString key, subresourceKey; 1722 GenKeysWithStatus(key, subresourceKey); 1723 1724 glean::service_worker::fetch_interception_duration.Get(key) 1725 .AccumulateRawDuration(mInterceptionFinish - mInterceptionStart); 1726 if (!mIsNonSubresourceRequest && !mSubresourceKey.IsEmpty()) { 1727 glean::service_worker::fetch_interception_duration.Get(subresourceKey) 1728 .AccumulateRawDuration(mInterceptionFinish - mInterceptionStart); 1729 } 1730 } 1731 1732 } // namespace mozilla::net