nsHttpTransaction.cpp (131515B)
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim:set ts=4 sw=2 sts=2 et cin: */ 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 // HttpLog.h should generally be included first 8 #include "nsHttpTransaction.h" 9 10 #include <algorithm> 11 #include <utility> 12 13 #include "HttpLog.h" 14 #include "HTTPSRecordResolver.h" 15 #include "NSSErrorsService.h" 16 #include "base/basictypes.h" 17 #include "mozilla/AppShutdown.h" 18 #include "mozilla/Components.h" 19 #include "mozilla/glean/NetwerkMetrics.h" 20 #include "mozilla/glean/NetwerkProtocolHttpMetrics.h" 21 #include "mozilla/net/SSLTokensCache.h" 22 #include "mozilla/ScopeExit.h" 23 #include "mozilla/Tokenizer.h" 24 #include "mozilla/StaticPrefs_network.h" 25 #include "MockHttpAuth.h" 26 #include "nsCRT.h" 27 #include "nsComponentManagerUtils.h" // do_CreateInstance 28 #include "nsHttpBasicAuth.h" 29 #include "nsHttpChannel.h" 30 #include "nsHttpChunkedDecoder.h" 31 #include "nsHttpDigestAuth.h" 32 #include "nsHttpHandler.h" 33 #include "nsHttpNTLMAuth.h" 34 #ifdef MOZ_AUTH_EXTENSION 35 # include "nsHttpNegotiateAuth.h" 36 #endif 37 #include "nsHttpRequestHead.h" 38 #include "nsHttpResponseHead.h" 39 #include "nsICancelable.h" 40 #include "nsIClassOfService.h" 41 #include "nsIDNSByTypeRecord.h" 42 #include "nsIDNSRecord.h" 43 #include "nsIDNSService.h" 44 #include "nsIEventTarget.h" 45 #include "nsIHttpActivityObserver.h" 46 #include "nsIHttpAuthenticator.h" 47 #include "nsIInputStream.h" 48 #include "nsIInputStreamPriority.h" 49 #include "nsIMultiplexInputStream.h" 50 #include "nsIOService.h" 51 #include "nsIPipe.h" 52 #include "nsIRequestContext.h" 53 #include "nsISeekableStream.h" 54 #include "nsITLSSocketControl.h" 55 #include "nsIThrottledInputChannel.h" 56 #include "nsITransport.h" 57 #include "nsMultiplexInputStream.h" 58 #include "nsNetCID.h" 59 #include "nsNetUtil.h" 60 #include "nsQueryObject.h" 61 #include "nsSocketTransportService2.h" 62 #include "nsStringStream.h" 63 #include "nsTransportUtils.h" 64 #include "sslerr.h" 65 #include "SpeculativeTransaction.h" 66 #include "mozilla/Preferences.h" 67 68 //----------------------------------------------------------------------------- 69 70 // Place a limit on how much non-compliant HTTP can be skipped while 71 // looking for a response header 72 #define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128) 73 74 using namespace mozilla::net; 75 76 namespace mozilla::net { 77 78 //----------------------------------------------------------------------------- 79 // nsHttpTransaction <public> 80 //----------------------------------------------------------------------------- 81 82 nsHttpTransaction::nsHttpTransaction() { 83 LOG(("Creating nsHttpTransaction @%p\n", this)); 84 85 #ifdef MOZ_VALGRIND 86 memset(&mSelfAddr, 0, sizeof(NetAddr)); 87 memset(&mPeerAddr, 0, sizeof(NetAddr)); 88 #endif 89 mSelfAddr.raw.family = PR_AF_UNSPEC; 90 mPeerAddr.raw.family = PR_AF_UNSPEC; 91 } 92 93 void nsHttpTransaction::ResumeReading() { 94 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 95 96 if (!mReadingStopped) { 97 return; 98 } 99 100 LOG(("nsHttpTransaction::ResumeReading %p", this)); 101 102 mReadingStopped = false; 103 104 // This with either reengage the limit when still throttled in WriteSegments 105 // or simply reset to allow unlimeted reading again. 106 mThrottlingReadAllowance = THROTTLE_NO_LIMIT; 107 108 if (mConnection) { 109 mConnection->TransactionHasDataToRecv(this); 110 nsresult rv = mConnection->ResumeRecv(); 111 if (NS_FAILED(rv)) { 112 LOG((" resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv))); 113 } 114 } 115 } 116 117 bool nsHttpTransaction::EligibleForThrottling() const { 118 return (mClassOfServiceFlags & 119 (nsIClassOfService::Throttleable | nsIClassOfService::DontThrottle | 120 nsIClassOfService::Leader | nsIClassOfService::Unblocked)) == 121 nsIClassOfService::Throttleable; 122 } 123 124 void nsHttpTransaction::SetClassOfService(ClassOfService cos) { 125 if (mClosed) { 126 return; 127 } 128 129 bool wasThrottling = EligibleForThrottling(); 130 mClassOfServiceFlags = cos.Flags(); 131 mClassOfServiceIncremental = cos.Incremental(); 132 bool isThrottling = EligibleForThrottling(); 133 134 if (mConnection && wasThrottling != isThrottling) { 135 // Do nothing until we are actually activated. For now 136 // only remember the throttle flag. Call to UpdateActiveTransaction 137 // would add this transaction to the list too early. 138 gHttpHandler->ConnMgr()->UpdateActiveTransaction(this); 139 140 if (mReadingStopped && !isThrottling) { 141 ResumeReading(); 142 } 143 } 144 } 145 146 nsHttpTransaction::~nsHttpTransaction() { 147 LOG(("Destroying nsHttpTransaction @%p\n", this)); 148 149 if (mTokenBucketCancel) { 150 mTokenBucketCancel->Cancel(NS_ERROR_ABORT); 151 mTokenBucketCancel = nullptr; 152 } 153 154 // Force the callbacks and connection to be released right now 155 { 156 MutexAutoLock lock(mLock); 157 mCallbacks = nullptr; 158 } 159 160 mEarlyHintObserver = nullptr; 161 162 delete mResponseHead; 163 delete mChunkedDecoder; 164 ReleaseBlockingTransaction(); 165 } 166 167 nsresult nsHttpTransaction::Init( 168 uint32_t caps, nsHttpConnectionInfo* cinfo, nsHttpRequestHead* requestHead, 169 nsIInputStream* requestBody, uint64_t requestContentLength, 170 bool requestBodyHasHeaders, nsIEventTarget* target, 171 nsIInterfaceRequestor* callbacks, nsITransportEventSink* eventsink, 172 uint64_t browserId, HttpTrafficCategory trafficCategory, 173 nsIRequestContext* requestContext, ClassOfService classOfService, 174 uint32_t initialRwin, bool responseTimeoutEnabled, uint64_t channelId, 175 TransactionObserverFunc&& transactionObserver, 176 nsILoadInfo::IPAddressSpace aParentIpAddressSpace, 177 const struct LNAPerms& aLnaPermissionStatus) { 178 nsresult rv; 179 180 LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps)); 181 182 bool isBeacon = false; 183 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(eventsink); 184 if (httpChannel) { 185 // Beacons are sometimes sent in pagehide during browser shutdown. 186 // When that happens, we should allow the beacon to maybe succeed 187 // even though it's going to be racy (shutdown may finish first). 188 // See bug 1931956. 189 nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->LoadInfo(); 190 if (loadInfo->InternalContentPolicyType() == 191 nsIContentPolicy::TYPE_BEACON) { 192 isBeacon = true; 193 } 194 } 195 196 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed) && 197 !isBeacon) { 198 LOG( 199 ("nsHttpTransaction aborting init because of app" 200 "shutdown")); 201 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 202 } 203 204 MOZ_ASSERT(cinfo); 205 MOZ_ASSERT(requestHead); 206 MOZ_ASSERT(target); 207 MOZ_ASSERT(target->IsOnCurrentThread()); 208 209 mChannelId = channelId; 210 mTransactionObserver = std::move(transactionObserver); 211 mBrowserId = browserId; 212 213 mTrafficCategory = trafficCategory; 214 215 LOG1(("nsHttpTransaction %p SetRequestContext %p\n", this, requestContext)); 216 mRequestContext = requestContext; 217 218 SetClassOfService(classOfService); 219 mResponseTimeoutEnabled = responseTimeoutEnabled; 220 mInitialRwin = initialRwin; 221 222 // create transport event sink proxy. it coalesces consecutive 223 // events of the same status type. 224 rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), eventsink, 225 target); 226 227 if (NS_FAILED(rv)) return rv; 228 229 mConnInfo = cinfo; 230 mFinalizedConnInfo = cinfo; 231 mCallbacks = callbacks; 232 mConsumerTarget = target; 233 mCaps = caps; 234 235 mParentIPAddressSpace = aParentIpAddressSpace; 236 mLnaPermissionStatus = aLnaPermissionStatus; 237 // eventsink is a nsHttpChannel when we expect "103 Early Hints" responses. 238 // We expect it in document requests and not e.g. in TRR requests. 239 mEarlyHintObserver = do_QueryInterface(eventsink); 240 241 if (requestHead->IsHead()) { 242 mNoContent = true; 243 } 244 245 // grab a weak reference to the request head 246 mRequestHead = requestHead; 247 248 mReqHeaderBuf = nsHttp::ConvertRequestHeadToString( 249 *requestHead, !!requestBody, requestBodyHasHeaders, 250 cinfo->UsingConnect()); 251 252 if (LOG1_ENABLED()) { 253 LOG1(("http request [\n")); 254 LogHeaders(mReqHeaderBuf.get()); 255 LOG1(("]\n")); 256 } 257 258 // report the request header 259 if (gHttpHandler->HttpActivityDistributorActivated()) { 260 nsCString requestBuf(mReqHeaderBuf); 261 NS_DispatchToMainThread(NS_NewRunnableFunction( 262 "ObserveHttpActivityWithArgs", [channelId(mChannelId), requestBuf]() { 263 if (!gHttpHandler) { 264 return; 265 } 266 gHttpHandler->ObserveHttpActivityWithArgs( 267 HttpActivityArgs(channelId), 268 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 269 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, requestBuf); 270 })); 271 } 272 273 // Create a string stream for the request header buf (the stream holds 274 // a non-owning reference to the request header data, so we MUST keep 275 // mReqHeaderBuf around). 276 nsCOMPtr<nsIInputStream> headers; 277 rv = NS_NewByteInputStream(getter_AddRefs(headers), mReqHeaderBuf, 278 NS_ASSIGNMENT_DEPEND); 279 if (NS_FAILED(rv)) return rv; 280 281 mHasRequestBody = !!requestBody; 282 if (mHasRequestBody && !requestContentLength) { 283 mHasRequestBody = false; 284 } 285 286 requestContentLength += mReqHeaderBuf.Length(); 287 288 if (mHasRequestBody) { 289 // wrap the headers and request body in a multiplexed input stream. 290 RefPtr<nsMultiplexInputStream> multi = new nsMultiplexInputStream(); 291 292 rv = multi->AppendStream(headers); 293 if (NS_FAILED(rv)) return rv; 294 295 rv = multi->AppendStream(requestBody); 296 if (NS_FAILED(rv)) return rv; 297 298 // wrap the multiplexed input stream with a buffered input stream, so 299 // that we write data in the largest chunks possible. this is actually 300 // necessary to workaround some common server bugs (see bug 137155). 301 rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), 302 multi.forget(), 303 nsIOService::gDefaultSegmentSize); 304 if (NS_FAILED(rv)) return rv; 305 } else { 306 mRequestStream = headers; 307 } 308 309 nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(eventsink); 310 if (throttled) { 311 nsCOMPtr<nsIInputChannelThrottleQueue> queue; 312 rv = throttled->GetThrottleQueue(getter_AddRefs(queue)); 313 // In case of failure, just carry on without throttling. 314 if (NS_SUCCEEDED(rv) && queue) { 315 nsCOMPtr<nsIAsyncInputStream> wrappedStream; 316 rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream)); 317 // Failure to throttle isn't sufficient reason to fail 318 // initialization 319 if (NS_SUCCEEDED(rv)) { 320 MOZ_ASSERT(wrappedStream != nullptr); 321 LOG( 322 ("nsHttpTransaction::Init %p wrapping input stream using throttle " 323 "queue %p\n", 324 this, queue.get())); 325 mRequestStream = wrappedStream; 326 } 327 } 328 } 329 330 // make sure request content-length fits within js MAX_SAFE_INTEGER 331 mRequestSize = InScriptableRange(requestContentLength) 332 ? static_cast<int64_t>(requestContentLength) 333 : -1; 334 335 // create pipe for response stream 336 NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), true, true, 337 nsIOService::gDefaultSegmentSize, 338 nsIOService::gDefaultSegmentCount); 339 340 bool forceUseHTTPSRR = StaticPrefs::network_dns_force_use_https_rr(); 341 if ((StaticPrefs::network_dns_use_https_rr_as_altsvc() && 342 !(mCaps & NS_HTTP_DISALLOW_HTTPS_RR)) || 343 forceUseHTTPSRR) { 344 nsCOMPtr<nsIEventTarget> target; 345 (void)gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); 346 if (target) { 347 if (forceUseHTTPSRR) { 348 mCaps |= NS_HTTP_FORCE_WAIT_HTTP_RR; 349 } 350 351 mResolver = new HTTPSRecordResolver(this); 352 nsCOMPtr<nsICancelable> dnsRequest; 353 rv = mResolver->FetchHTTPSRRInternal(target, getter_AddRefs(dnsRequest)); 354 if (NS_SUCCEEDED(rv)) { 355 mHTTPSSVCReceivedStage = HTTPSSVC_NOT_PRESENT; 356 } 357 358 { 359 MutexAutoLock lock(mLock); 360 mDNSRequest.swap(dnsRequest); 361 if (NS_FAILED(rv)) { 362 MakeDontWaitHTTPSRR(); 363 } 364 } 365 } 366 } 367 368 if (httpChannel) { 369 RefPtr<WebTransportSessionEventListener> listener = 370 httpChannel->GetWebTransportSessionEventListener(); 371 if (listener) { 372 mWebTransportSessionEventListener = std::move(listener); 373 } 374 nsCOMPtr<nsIURI> uri; 375 if (NS_SUCCEEDED(httpChannel->GetURI(getter_AddRefs(uri)))) { 376 mUrl = uri->GetSpecOrDefault(); 377 } 378 } 379 380 return NS_OK; 381 } 382 383 static inline void CreateAndStartTimer(nsCOMPtr<nsITimer>& aTimer, 384 nsITimerCallback* aCallback, 385 uint32_t aTimeout) { 386 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread"); 387 MOZ_ASSERT(!aTimer); 388 389 if (!aTimeout) { 390 return; 391 } 392 393 NS_NewTimerWithCallback(getter_AddRefs(aTimer), aCallback, aTimeout, 394 nsITimer::TYPE_ONE_SHOT); 395 } 396 397 void nsHttpTransaction::OnPendingQueueInserted( 398 const nsACString& aConnectionHashKey) { 399 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 400 401 { 402 MutexAutoLock lock(mLock); 403 mHashKeyOfConnectionEntry.Assign(aConnectionHashKey); 404 } 405 406 // Don't create mHttp3BackupTimer if HTTPS RR is in play. 407 if ((mConnInfo->IsHttp3() || mConnInfo->IsHttp3ProxyConnection()) && 408 !mOrigConnInfo && !mConnInfo->GetWebTransport()) { 409 // Backup timer should only be created once. 410 if (!mHttp3BackupTimerCreated) { 411 CreateAndStartTimer(mHttp3BackupTimer, this, 412 StaticPrefs::network_http_http3_backup_timer_delay()); 413 mHttp3BackupTimerCreated = true; 414 } 415 } 416 } 417 418 nsresult nsHttpTransaction::AsyncRead(nsIStreamListener* listener, 419 nsIRequest** pump) { 420 RefPtr<nsInputStreamPump> transactionPump; 421 nsresult rv = 422 nsInputStreamPump::Create(getter_AddRefs(transactionPump), mPipeIn); 423 NS_ENSURE_SUCCESS(rv, rv); 424 425 rv = transactionPump->AsyncRead(listener); 426 NS_ENSURE_SUCCESS(rv, rv); 427 428 transactionPump.forget(pump); 429 return NS_OK; 430 } 431 432 // This method should only be used on the socket thread 433 nsAHttpConnection* nsHttpTransaction::Connection() { 434 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 435 return mConnection.get(); 436 } 437 438 void nsHttpTransaction::SetH2WSConnRefTaken() { 439 if (!OnSocketThread()) { 440 nsCOMPtr<nsIRunnable> event = 441 NewRunnableMethod("nsHttpTransaction::SetH2WSConnRefTaken", this, 442 &nsHttpTransaction::SetH2WSConnRefTaken); 443 gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); 444 return; 445 } 446 } 447 448 UniquePtr<nsHttpResponseHead> nsHttpTransaction::TakeResponseHeadAndConnInfo( 449 nsHttpConnectionInfo** aOut) { 450 MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x"); 451 452 // Lock TakeResponseHead() against main thread 453 MutexAutoLock lock(mLock); 454 455 if (aOut) { 456 RefPtr<nsHttpConnectionInfo> connInfo = mFinalizedConnInfo; 457 connInfo.forget(aOut); 458 } 459 460 mResponseHeadTaken = true; 461 462 // Even in OnStartRequest() the headers won't be available if we were 463 // canceled 464 if (!mHaveAllHeaders) { 465 NS_WARNING("response headers not available or incomplete"); 466 return nullptr; 467 } 468 469 return WrapUnique(std::exchange(mResponseHead, nullptr)); 470 } 471 472 UniquePtr<nsHttpHeaderArray> nsHttpTransaction::TakeResponseTrailers() { 473 MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x"); 474 475 // Lock TakeResponseTrailers() against main thread 476 MutexAutoLock lock(mLock); 477 478 mResponseTrailersTaken = true; 479 return std::move(mForTakeResponseTrailers); 480 } 481 482 void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed = true; } 483 484 nsHttpRequestHead* nsHttpTransaction::RequestHead() { return mRequestHead; } 485 486 uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; } 487 488 nsresult nsHttpTransaction::TakeSubTransactions( 489 nsTArray<RefPtr<nsAHttpTransaction>>& outTransactions) { 490 return NS_ERROR_NOT_IMPLEMENTED; 491 } 492 493 //---------------------------------------------------------------------------- 494 // nsHttpTransaction::nsAHttpTransaction 495 //---------------------------------------------------------------------------- 496 497 void nsHttpTransaction::SetConnection(nsAHttpConnection* conn) { 498 { 499 MutexAutoLock lock(mLock); 500 mConnection = conn; 501 if (mConnection) { 502 mIsHttp3Used = mConnection->Version() == HttpVersion::v3_0; 503 } 504 } 505 } 506 507 void nsHttpTransaction::OnActivated() { 508 nsresult rv; 509 MOZ_ASSERT(OnSocketThread()); 510 511 if (mActivated) { 512 return; 513 } 514 515 if (mTrafficCategory != HttpTrafficCategory::eInvalid) { 516 HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer(); 517 if (hta) { 518 hta->IncrementHttpTransaction(mTrafficCategory); 519 } 520 if (mConnection) { 521 mConnection->SetTrafficCategory(mTrafficCategory); 522 } 523 } 524 525 if (mConnection && mRequestHead && 526 mConnection->Version() >= HttpVersion::v2_0) { 527 // So this is fun. On http/2, we want to send TE: trailers, to be 528 // spec-compliant. So we add it to the request head here. The fun part 529 // is that adding a header to the request head at this point has no 530 // effect on what we send on the wire, as the headers are already 531 // flattened (in Init()) by the time we get here. So the *real* adding 532 // of the header happens in the h2 compression code. We still have to 533 // add the header to the request head here, though, so that devtools can 534 // show that we sent the header. FUN! 535 nsAutoCString teHeader; 536 rv = mRequestHead->GetHeader(nsHttp::TE, teHeader); 537 if (NS_FAILED(rv) || !teHeader.Equals("moz_no_te_trailers"_ns)) { 538 // If the request already has TE:moz_no_te_trailers then 539 // Http2Compressor::EncodeHeaderBlock won't actually add this header. 540 (void)mRequestHead->SetHeader(nsHttp::TE, "trailers"_ns); 541 } 542 } 543 544 mActivated = true; 545 gHttpHandler->ConnMgr()->AddActiveTransaction(this); 546 FinalizeConnInfo(); 547 if (mConnection) { 548 RefPtr<HttpConnectionBase> conn = mConnection->HttpConnection(); 549 if (conn) { 550 conn->RecordConnectionAddressType(); 551 } 552 } 553 } 554 555 void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** cb) { 556 MutexAutoLock lock(mLock); 557 nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks); 558 tmp.forget(cb); 559 } 560 561 void nsHttpTransaction::SetSecurityCallbacks( 562 nsIInterfaceRequestor* aCallbacks) { 563 { 564 MutexAutoLock lock(mLock); 565 mCallbacks = aCallbacks; 566 } 567 568 if (gSocketTransportService) { 569 RefPtr<UpdateSecurityCallbacks> event = 570 new UpdateSecurityCallbacks(this, aCallbacks); 571 gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); 572 } 573 } 574 575 void nsHttpTransaction::OnTransportStatus(nsITransport* transport, 576 nsresult status, int64_t progress) { 577 LOG1(("nsHttpTransaction::OnTransportStatus [this=%p status=%" PRIx32 578 " progress=%" PRId64 "]\n", 579 this, static_cast<uint32_t>(status), progress)); 580 581 if (status == NS_NET_STATUS_CONNECTED_TO || 582 status == NS_NET_STATUS_WAITING_FOR) { 583 if (mConnection) { 584 MutexAutoLock lock(mLock); 585 mConnection->GetSelfAddr(&mSelfAddr); 586 mConnection->GetPeerAddr(&mPeerAddr); 587 mResolvedByTRR = mConnection->ResolvedByTRR(); 588 mEffectiveTRRMode = mConnection->EffectiveTRRMode(); 589 mTRRSkipReason = mConnection->TRRSkipReason(); 590 mEchConfigUsed = mConnection->GetEchConfigUsed(); 591 } 592 } 593 594 // If the timing is enabled, and we are not using a persistent connection 595 // then the requestStart timestamp will be null, so we mark the timestamps 596 // for domainLookupStart/End and connectStart/End 597 // If we are using a persistent connection they will remain null, 598 // and the correct value will be returned in Performance. 599 if (GetRequestStart().IsNull()) { 600 if (status == NS_NET_STATUS_RESOLVING_HOST) { 601 SetDomainLookupStart(TimeStamp::Now(), true); 602 } else if (status == NS_NET_STATUS_RESOLVED_HOST) { 603 SetDomainLookupEnd(TimeStamp::Now()); 604 } else if (status == NS_NET_STATUS_CONNECTING_TO) { 605 TimeStamp tnow = TimeStamp::Now(); 606 { 607 MutexAutoLock lock(mLock); 608 mTimings.connectStart = tnow; 609 if (mConnInfo->IsHttp3()) { 610 mTimings.secureConnectionStart = tnow; 611 } 612 } 613 } else if (status == NS_NET_STATUS_CONNECTED_TO) { 614 TimeStamp tnow = TimeStamp::Now(); 615 SetConnectEnd(tnow, true); 616 { 617 MutexAutoLock lock(mLock); 618 mTimings.tcpConnectEnd = tnow; 619 } 620 } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) { 621 { 622 MutexAutoLock lock(mLock); 623 mTimings.secureConnectionStart = TimeStamp::Now(); 624 } 625 } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) { 626 SetConnectEnd(TimeStamp::Now(), false); 627 } else if (status == NS_NET_STATUS_SENDING_TO) { 628 // Set the timestamp to Now(), only if it null 629 SetRequestStart(TimeStamp::Now(), true); 630 } 631 } 632 633 if (!mTransportSink) return; 634 635 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 636 637 // Need to do this before the STATUS_RECEIVING_FROM check below, to make 638 // sure that the activity distributor gets told about all status events. 639 640 // upon STATUS_WAITING_FOR; report request body sent 641 if ((mHasRequestBody) && (status == NS_NET_STATUS_WAITING_FOR)) { 642 gHttpHandler->ObserveHttpActivityWithArgs( 643 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 644 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, PR_Now(), 0, ""_ns); 645 } 646 647 // report the status and progress 648 gHttpHandler->ObserveHttpActivityWithArgs( 649 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, 650 static_cast<uint32_t>(status), PR_Now(), progress, ""_ns); 651 652 // nsHttpChannel synthesizes progress events in OnDataAvailable 653 if (status == NS_NET_STATUS_RECEIVING_FROM) return; 654 655 int64_t progressMax; 656 657 if (status == NS_NET_STATUS_SENDING_TO) { 658 // suppress progress when only writing request headers 659 if (!mHasRequestBody) { 660 LOG1( 661 ("nsHttpTransaction::OnTransportStatus %p " 662 "SENDING_TO without request body\n", 663 this)); 664 return; 665 } 666 667 if (mReader) { 668 // A mRequestStream method is on the stack - wait. 669 LOG( 670 ("nsHttpTransaction::OnSocketStatus [this=%p] " 671 "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n", 672 this)); 673 // its ok to coalesce several of these into one deferred event 674 mDeferredSendProgress = true; 675 return; 676 } 677 678 nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mRequestStream); 679 if (!tellable) { 680 LOG1( 681 ("nsHttpTransaction::OnTransportStatus %p " 682 "SENDING_TO without tellable request stream\n", 683 this)); 684 MOZ_ASSERT( 685 !mRequestStream, 686 "mRequestStream should be tellable as it was wrapped in " 687 "nsBufferedInputStream, which provides the tellable interface even " 688 "when wrapping non-tellable streams."); 689 progress = 0; 690 } else { 691 int64_t prog = 0; 692 tellable->Tell(&prog); 693 progress = prog; 694 } 695 696 // when uploading, we include the request headers in the progress 697 // notifications. 698 progressMax = mRequestSize; 699 } else { 700 progress = 0; 701 progressMax = 0; 702 } 703 704 mTransportSink->OnTransportStatus(transport, status, progress, progressMax); 705 } 706 707 bool nsHttpTransaction::IsDone() { return mTransactionDone; } 708 709 nsresult nsHttpTransaction::Status() { return mStatus; } 710 711 uint32_t nsHttpTransaction::Caps() { return mCaps & ~mCapsToClear; } 712 713 void nsHttpTransaction::SetDNSWasRefreshed() { 714 MOZ_ASSERT(mConsumerTarget->IsOnCurrentThread(), 715 "SetDNSWasRefreshed on target thread only!"); 716 mCapsToClear |= NS_HTTP_REFRESH_DNS; 717 } 718 719 nsresult nsHttpTransaction::ReadRequestSegment(nsIInputStream* stream, 720 void* closure, const char* buf, 721 uint32_t offset, uint32_t count, 722 uint32_t* countRead) { 723 // For the tracking of sent bytes that we used to do for the networkstats 724 // API, please see bug 1318883 where it was removed. 725 726 nsHttpTransaction* trans = (nsHttpTransaction*)closure; 727 nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead); 728 if (NS_FAILED(rv)) { 729 trans->MaybeRefreshSecurityInfo(); 730 return rv; 731 } 732 733 LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans, *countRead)); 734 735 trans->mSentData = true; 736 return NS_OK; 737 } 738 739 nsresult nsHttpTransaction::ReadSegments(nsAHttpSegmentReader* reader, 740 uint32_t count, uint32_t* countRead) { 741 LOG(("nsHttpTransaction::ReadSegments %p", this)); 742 743 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 744 745 if (mTransactionDone) { 746 *countRead = 0; 747 return mStatus; 748 } 749 750 if (!m0RTTInProgress) { 751 MaybeCancelFallbackTimer(); 752 } 753 754 if (!mConnected && !m0RTTInProgress) { 755 mConnected = true; 756 MaybeRefreshSecurityInfo(); 757 } 758 759 mDeferredSendProgress = false; 760 mReader = reader; 761 nsresult rv = 762 mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead); 763 mReader = nullptr; 764 765 if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) && 766 NS_SUCCEEDED(rv) && (*countRead > 0)) { 767 LOG(("mEarlyDataDisposition = EARLY_SENT")); 768 mEarlyDataDisposition = EARLY_SENT; 769 } 770 771 if (mDeferredSendProgress && mConnection) { 772 // to avoid using mRequestStream concurrently, OnTransportStatus() 773 // did not report upload status off the ReadSegments() stack from 774 // nsSocketTransport do it now. 775 OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0); 776 } 777 mDeferredSendProgress = false; 778 779 if (mForceRestart) { 780 // The forceRestart condition was dealt with on the stack, but it did not 781 // clear the flag because nsPipe in the readsegment stack clears out 782 // return codes, so we need to use the flag here as a cue to return 783 // ERETARGETED 784 if (NS_SUCCEEDED(rv)) { 785 rv = NS_BINDING_RETARGETED; 786 } 787 mForceRestart = false; 788 } 789 790 // if read would block then we need to AsyncWait on the request stream. 791 // have callback occur on socket thread so we stay synchronized. 792 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 793 nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(mRequestStream); 794 if (asyncIn) { 795 nsCOMPtr<nsIEventTarget> target; 796 (void)gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); 797 if (target) { 798 asyncIn->AsyncWait(this, 0, 0, target); 799 } else { 800 NS_ERROR("no socket thread event target"); 801 rv = NS_ERROR_UNEXPECTED; 802 } 803 } 804 } 805 806 return rv; 807 } 808 809 nsresult nsHttpTransaction::WritePipeSegment(nsIOutputStream* stream, 810 void* closure, char* buf, 811 uint32_t offset, uint32_t count, 812 uint32_t* countWritten) { 813 nsHttpTransaction* trans = (nsHttpTransaction*)closure; 814 815 if (trans->mTransactionDone) return NS_BASE_STREAM_CLOSED; // stop iterating 816 817 // Set the timestamp to Now(), only if it null 818 trans->SetResponseStart(TimeStamp::Now(), true); 819 820 // Bug 1153929 - add checks to fix windows crash 821 MOZ_ASSERT(trans->mWriter); 822 if (!trans->mWriter) { 823 return NS_ERROR_UNEXPECTED; 824 } 825 826 nsresult rv; 827 // 828 // OK, now let the caller fill this segment with data. 829 // 830 rv = trans->mWriter->OnWriteSegment(buf, count, countWritten); 831 if (NS_FAILED(rv)) { 832 trans->MaybeRefreshSecurityInfo(); 833 return rv; // caller didn't want to write anything 834 } 835 836 LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans, 837 *countWritten)); 838 839 MOZ_ASSERT(*countWritten > 0, "bad writer"); 840 trans->mReceivedData = true; 841 trans->mTransferSize += *countWritten; 842 843 // Let the transaction "play" with the buffer. It is free to modify 844 // the contents of the buffer and/or modify countWritten. 845 // - Bytes in HTTP headers don't count towards countWritten, so the input 846 // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit 847 // OnInputStreamReady until all headers have been parsed. 848 // 849 rv = trans->ProcessData(buf, *countWritten, countWritten); 850 if (NS_FAILED(rv)) trans->Close(rv); 851 852 return rv; // failure code only stops WriteSegments; it is not propagated. 853 } 854 855 bool nsHttpTransaction::ShouldThrottle() { 856 if (mClassOfServiceFlags & nsIClassOfService::DontThrottle) { 857 // We deliberately don't touch the throttling window here since 858 // DontThrottle requests are expected to be long-standing media 859 // streams and would just unnecessarily block running downloads. 860 // If we want to ballance bandwidth for media responses against 861 // running downloads, we need to find something smarter like 862 // changing the suspend/resume throttling intervals at-runtime. 863 return false; 864 } 865 866 if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) { 867 // We are not obligated to throttle 868 return false; 869 } 870 871 if (mContentRead < 16000) { 872 // Let the first bytes go, it may also well be all the content we get 873 LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64 874 ") this=%p", 875 mContentRead, this)); 876 return false; 877 } 878 879 if (!(mClassOfServiceFlags & nsIClassOfService::Throttleable) && 880 gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) { 881 LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this)); 882 // This is expensive to check (two hashtable lookups) but may help 883 // freeing connections for active tab transactions. 884 // Checking this only for transactions that are not explicitly marked 885 // as throttleable because trackers and (specially) downloads should 886 // keep throttling even under pressure. 887 return false; 888 } 889 890 return true; 891 } 892 893 void nsHttpTransaction::DontReuseConnection() { 894 LOG(("nsHttpTransaction::DontReuseConnection %p\n", this)); 895 if (!OnSocketThread()) { 896 LOG(("DontReuseConnection %p not on socket thread\n", this)); 897 nsCOMPtr<nsIRunnable> event = 898 NewRunnableMethod("nsHttpTransaction::DontReuseConnection", this, 899 &nsHttpTransaction::DontReuseConnection); 900 gSocketTransportService->Dispatch(event, NS_DISPATCH_NORMAL); 901 return; 902 } 903 904 if (mConnection) { 905 mConnection->DontReuse(); 906 } 907 } 908 909 nsresult nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer, 910 uint32_t count, 911 uint32_t* countWritten) { 912 LOG(("nsHttpTransaction::WriteSegments %p", this)); 913 914 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 915 916 if (mTransactionDone) { 917 return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus; 918 } 919 920 if (ShouldThrottle()) { 921 if (mThrottlingReadAllowance == THROTTLE_NO_LIMIT) { // no limit set 922 // V1: ThrottlingReadLimit() returns 0 923 mThrottlingReadAllowance = gHttpHandler->ThrottlingReadLimit(); 924 } 925 } else { 926 mThrottlingReadAllowance = THROTTLE_NO_LIMIT; // don't limit 927 } 928 929 if (mThrottlingReadAllowance == 0) { // depleted 930 if (gHttpHandler->ConnMgr()->CurrentBrowserId() != mBrowserId) { 931 nsHttp::NotifyActiveTabLoadOptimization(); 932 } 933 934 // Must remember that we have to call ResumeRecv() on our connection when 935 // called back by the conn manager to resume reading. 936 LOG(("nsHttpTransaction::WriteSegments %p response throttled", this)); 937 mReadingStopped = true; 938 // This makes the underlaying connection or stream wait for explicit resume. 939 // For h1 this means we stop reading from the socket. 940 // For h2 this means we stop updating recv window for the stream. 941 return NS_BASE_STREAM_WOULD_BLOCK; 942 } 943 944 mWriter = writer; 945 946 if (!mPipeOut) { 947 return NS_ERROR_UNEXPECTED; 948 } 949 950 if (mThrottlingReadAllowance > 0) { 951 LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d", 952 this, count, mThrottlingReadAllowance)); 953 count = std::min(count, static_cast<uint32_t>(mThrottlingReadAllowance)); 954 } 955 956 nsresult rv = 957 mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten); 958 959 mWriter = nullptr; 960 961 if (mForceRestart) { 962 // The forceRestart condition was dealt with on the stack, but it did not 963 // clear the flag because nsPipe in the writesegment stack clears out 964 // return codes, so we need to use the flag here as a cue to return 965 // ERETARGETED 966 if (NS_SUCCEEDED(rv)) { 967 rv = NS_BINDING_RETARGETED; 968 } 969 mForceRestart = false; 970 } 971 972 // if pipe would block then we need to AsyncWait on it. have callback 973 // occur on socket thread so we stay synchronized. 974 if (rv == NS_BASE_STREAM_WOULD_BLOCK) { 975 nsCOMPtr<nsIEventTarget> target; 976 (void)gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); 977 if (target) { 978 mPipeOut->AsyncWait(this, 0, 0, target); 979 mWaitingOnPipeOut = true; 980 } else { 981 NS_ERROR("no socket thread event target"); 982 rv = NS_ERROR_UNEXPECTED; 983 } 984 } else if (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) { 985 MOZ_ASSERT(count >= *countWritten); 986 mThrottlingReadAllowance -= *countWritten; 987 } 988 989 return rv; 990 } 991 992 bool nsHttpTransaction::ProxyConnectFailed() { return mProxyConnectFailed; } 993 994 bool nsHttpTransaction::DataSentToChildProcess() { return false; } 995 996 already_AddRefed<nsITransportSecurityInfo> nsHttpTransaction::SecurityInfo() { 997 MutexAutoLock lock(mLock); 998 return do_AddRef(mSecurityInfo); 999 } 1000 1001 bool nsHttpTransaction::HasStickyConnection() const { 1002 return mCaps & NS_HTTP_STICKY_CONNECTION; 1003 } 1004 1005 bool nsHttpTransaction::ResponseIsComplete() { return mResponseIsComplete; } 1006 1007 int64_t nsHttpTransaction::GetTransferSize() { return mTransferSize; } 1008 1009 int64_t nsHttpTransaction::GetRequestSize() { return mRequestSize; } 1010 1011 bool nsHttpTransaction::IsHttp3Used() { return mIsHttp3Used; } 1012 1013 bool nsHttpTransaction::Http2Disabled() const { 1014 return mCaps & NS_HTTP_DISALLOW_SPDY; 1015 } 1016 1017 bool nsHttpTransaction::Http3Disabled() const { 1018 return mCaps & NS_HTTP_DISALLOW_HTTP3; 1019 } 1020 1021 already_AddRefed<nsHttpConnectionInfo> nsHttpTransaction::GetConnInfo() const { 1022 RefPtr<nsHttpConnectionInfo> connInfo = mConnInfo->Clone(); 1023 return connInfo.forget(); 1024 } 1025 1026 nsHttpTransaction* nsHttpTransaction::AsHttpTransaction() { return this; } 1027 1028 HttpTransactionParent* nsHttpTransaction::AsHttpTransactionParent() { 1029 return nullptr; 1030 } 1031 1032 nsHttpTransaction::HTTPSSVC_CONNECTION_FAILED_REASON 1033 nsHttpTransaction::ErrorCodeToFailedReason(nsresult aErrorCode) { 1034 HTTPSSVC_CONNECTION_FAILED_REASON reason = HTTPSSVC_CONNECTION_OTHERS; 1035 switch (aErrorCode) { 1036 case NS_ERROR_UNKNOWN_HOST: 1037 reason = HTTPSSVC_CONNECTION_UNKNOWN_HOST; 1038 break; 1039 case NS_ERROR_CONNECTION_REFUSED: 1040 reason = HTTPSSVC_CONNECTION_UNREACHABLE; 1041 break; 1042 default: 1043 if (m421Received) { 1044 reason = HTTPSSVC_CONNECTION_421_RECEIVED; 1045 } else if (NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_SECURITY) { 1046 reason = HTTPSSVC_CONNECTION_SECURITY_ERROR; 1047 } 1048 break; 1049 } 1050 return reason; 1051 } 1052 1053 bool nsHttpTransaction::PrepareSVCBRecordsForRetry( 1054 const nsACString& aFailedDomainName, const nsACString& aFailedAlpn, 1055 bool& aAllRecordsHaveEchConfig) { 1056 MOZ_ASSERT(mRecordsForRetry.IsEmpty()); 1057 if (!mHTTPSSVCRecord) { 1058 return false; 1059 } 1060 1061 // If we already failed to connect with h3, don't select records that supports 1062 // h3. 1063 bool noHttp3 = mCaps & NS_HTTP_DISALLOW_HTTP3; 1064 1065 bool unused; 1066 nsTArray<RefPtr<nsISVCBRecord>> records; 1067 (void)mHTTPSSVCRecord->GetAllRecordsWithEchConfig( 1068 mCaps & NS_HTTP_DISALLOW_SPDY, noHttp3, mCname, &aAllRecordsHaveEchConfig, 1069 &unused, records); 1070 1071 // Note that it's possible that we can't get any usable record here. For 1072 // example, when http3 connection is failed, we won't select records with 1073 // http3 alpn. 1074 1075 // If not all records have echConfig, we'll directly fallback to the origin 1076 // server. 1077 if (!aAllRecordsHaveEchConfig) { 1078 return false; 1079 } 1080 1081 // Take the records behind the failed one and put them into mRecordsForRetry. 1082 for (const auto& record : records) { 1083 nsAutoCString name; 1084 record->GetName(name); 1085 nsAutoCString alpn; 1086 nsresult rv = record->GetSelectedAlpn(alpn); 1087 1088 if (name == aFailedDomainName) { 1089 // If the record has no alpn or the alpn is already tried, we skip this 1090 // record. 1091 if (NS_FAILED(rv) || alpn == aFailedAlpn) { 1092 continue; 1093 } 1094 } 1095 1096 mRecordsForRetry.InsertElementAt(0, record); 1097 } 1098 1099 // Set mHTTPSSVCRecord to null to avoid this function being executed twice. 1100 mHTTPSSVCRecord = nullptr; 1101 return !mRecordsForRetry.IsEmpty(); 1102 } 1103 1104 already_AddRefed<nsHttpConnectionInfo> 1105 nsHttpTransaction::PrepareFastFallbackConnInfo(bool aEchConfigUsed) { 1106 MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo); 1107 1108 RefPtr<nsHttpConnectionInfo> fallbackConnInfo; 1109 nsCOMPtr<nsISVCBRecord> fastFallbackRecord; 1110 (void)mHTTPSSVCRecord->GetServiceModeRecordWithCname( 1111 mCaps & NS_HTTP_DISALLOW_SPDY, true, mCname, 1112 getter_AddRefs(fastFallbackRecord)); 1113 1114 if (fastFallbackRecord && aEchConfigUsed) { 1115 nsAutoCString echConfig; 1116 (void)fastFallbackRecord->GetEchConfig(echConfig); 1117 if (echConfig.IsEmpty()) { 1118 fastFallbackRecord = nullptr; 1119 } 1120 } 1121 1122 if (!fastFallbackRecord) { 1123 if (aEchConfigUsed) { 1124 LOG( 1125 ("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record " 1126 "can be used", 1127 this)); 1128 return nullptr; 1129 } 1130 1131 if (mOrigConnInfo->IsHttp3()) { 1132 mOrigConnInfo->CloneAsDirectRoute(getter_AddRefs(fallbackConnInfo)); 1133 } else { 1134 fallbackConnInfo = mOrigConnInfo; 1135 } 1136 return fallbackConnInfo.forget(); 1137 } 1138 1139 fallbackConnInfo = 1140 mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(fastFallbackRecord); 1141 return fallbackConnInfo.forget(); 1142 } 1143 1144 void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) { 1145 LOG(("nsHttpTransaction::PrepareConnInfoForRetry [this=%p reason=%" PRIx32 1146 "]", 1147 this, static_cast<uint32_t>(aReason))); 1148 RefPtr<nsHttpConnectionInfo> failedConnInfo = mConnInfo->Clone(); 1149 mConnInfo = nullptr; 1150 bool echConfigUsed = 1151 nsHttpHandler::EchConfigEnabled(failedConnInfo->IsHttp3()) && 1152 !failedConnInfo->GetEchConfig().IsEmpty(); 1153 1154 if (mFastFallbackTriggered) { 1155 mFastFallbackTriggered = false; 1156 MOZ_ASSERT(mBackupConnInfo); 1157 mConnInfo.swap(mBackupConnInfo); 1158 return; 1159 } 1160 1161 auto useOrigConnInfoToRetry = [&]() { 1162 mOrigConnInfo.swap(mConnInfo); 1163 if (mConnInfo->IsHttp3() && 1164 ((mCaps & NS_HTTP_DISALLOW_HTTP3) || 1165 gHttpHandler->IsHttp3Excluded(mConnInfo->GetRoutedHost().IsEmpty() 1166 ? mConnInfo->GetOrigin() 1167 : mConnInfo->GetRoutedHost()))) { 1168 RefPtr<nsHttpConnectionInfo> ci; 1169 mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci)); 1170 mConnInfo = ci; 1171 } 1172 }; 1173 1174 if (!echConfigUsed) { 1175 LOG((" echConfig is not used, fallback to origin conn info")); 1176 useOrigConnInfoToRetry(); 1177 return; 1178 } 1179 1180 TRANSACTION_ECH_RETRY_COUNT id = TRANSACTION_ECH_RETRY_OTHERS_COUNT; 1181 auto updateCount = MakeScopeExit([&] { 1182 auto entry = mEchRetryCounterMap.Lookup(id); 1183 MOZ_ASSERT(entry, "table not initialized"); 1184 if (entry) { 1185 *entry += 1; 1186 } 1187 }); 1188 1189 if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH)) { 1190 LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry")); 1191 failedConnInfo->SetEchConfig(EmptyCString()); 1192 failedConnInfo.swap(mConnInfo); 1193 id = TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT; 1194 return; 1195 } 1196 1197 if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) { 1198 LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig")); 1199 MOZ_ASSERT(mConnection); 1200 1201 nsCOMPtr<nsITLSSocketControl> socketControl; 1202 if (mConnection) { 1203 mConnection->GetTLSSocketControl(getter_AddRefs(socketControl)); 1204 } 1205 MOZ_ASSERT(socketControl); 1206 1207 nsAutoCString retryEchConfig; 1208 if (socketControl && 1209 NS_SUCCEEDED(socketControl->GetRetryEchConfig(retryEchConfig))) { 1210 MOZ_ASSERT(!retryEchConfig.IsEmpty()); 1211 1212 failedConnInfo->SetEchConfig(retryEchConfig); 1213 failedConnInfo.swap(mConnInfo); 1214 } 1215 id = TRANSACTION_ECH_RETRY_WITH_ECH_COUNT; 1216 return; 1217 } 1218 1219 // Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but 1220 // also for all failure cases. 1221 if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED) || 1222 NS_FAILED(aReason)) { 1223 LOG((" Got SSL_ERROR_ECH_FAILED, try other records")); 1224 if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED)) { 1225 id = TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT; 1226 } 1227 if (mRecordsForRetry.IsEmpty()) { 1228 if (mHTTPSSVCRecord) { 1229 bool allRecordsHaveEchConfig = true; 1230 if (!PrepareSVCBRecordsForRetry(failedConnInfo->GetRoutedHost(), 1231 failedConnInfo->GetNPNToken(), 1232 allRecordsHaveEchConfig)) { 1233 LOG( 1234 (" Can't find other records with echConfig, " 1235 "allRecordsHaveEchConfig=%d", 1236 allRecordsHaveEchConfig)); 1237 if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() || 1238 !allRecordsHaveEchConfig) { 1239 useOrigConnInfoToRetry(); 1240 } 1241 return; 1242 } 1243 } else { 1244 LOG((" No available records to retry")); 1245 if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed()) { 1246 useOrigConnInfoToRetry(); 1247 } 1248 return; 1249 } 1250 } 1251 1252 if (LOG5_ENABLED()) { 1253 LOG(("SvcDomainName to retry: [")); 1254 for (const auto& r : mRecordsForRetry) { 1255 nsAutoCString name; 1256 r->GetName(name); 1257 nsAutoCString alpn; 1258 r->GetSelectedAlpn(alpn); 1259 LOG((" name=%s alpn=%s", name.get(), alpn.get())); 1260 } 1261 LOG(("]")); 1262 } 1263 1264 RefPtr<nsISVCBRecord> recordsForRetry = 1265 mRecordsForRetry.PopLastElement().forget(); 1266 mConnInfo = mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(recordsForRetry); 1267 } 1268 } 1269 1270 void nsHttpTransaction::MaybeReportFailedSVCDomain( 1271 nsresult aReason, nsHttpConnectionInfo* aFailedConnInfo) { 1272 if (aReason == psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH) || 1273 aReason != psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH)) { 1274 return; 1275 } 1276 1277 glean::http::dns_httpssvc_connection_failed_reason.AccumulateSingleSample( 1278 ErrorCodeToFailedReason(aReason)); 1279 1280 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); 1281 if (dns) { 1282 const nsCString& failedHost = aFailedConnInfo->GetRoutedHost().IsEmpty() 1283 ? aFailedConnInfo->GetOrigin() 1284 : aFailedConnInfo->GetRoutedHost(); 1285 LOG(("add failed domain name [%s] -> [%s] to exclusion list", 1286 aFailedConnInfo->GetOrigin().get(), failedHost.get())); 1287 (void)dns->ReportFailedSVCDomainName(aFailedConnInfo->GetOrigin(), 1288 failedHost); 1289 } 1290 } 1291 1292 bool nsHttpTransaction::ShouldRestartOn0RttError(nsresult reason) { 1293 LOG( 1294 ("nsHttpTransaction::ShouldRestartOn0RttError [this=%p, " 1295 "mEarlyDataWasAvailable=%d error=%" PRIx32 "]\n", 1296 this, mEarlyDataWasAvailable, static_cast<uint32_t>(reason))); 1297 return StaticPrefs::network_http_early_data_disable_on_error() && 1298 mEarlyDataWasAvailable && PossibleZeroRTTRetryError(reason); 1299 } 1300 1301 static void MaybeRemoveSSLToken(nsITransportSecurityInfo* aSecurityInfo) { 1302 if (!StaticPrefs:: 1303 network_http_remove_resumption_token_when_early_data_failed()) { 1304 return; 1305 } 1306 if (!aSecurityInfo) { 1307 return; 1308 } 1309 nsAutoCString key; 1310 aSecurityInfo->GetPeerId(key); 1311 nsresult rv = SSLTokensCache::RemoveAll(key); 1312 LOG(("RemoveSSLToken [key=%s, rv=%" PRIx32 "]", key.get(), 1313 static_cast<uint32_t>(rv))); 1314 } 1315 1316 const int64_t TELEMETRY_REQUEST_SIZE_1M = (int64_t)(1 << 20); 1317 const int64_t TELEMETRY_REQUEST_SIZE_10M = 1318 (int64_t)10 * TELEMETRY_REQUEST_SIZE_1M; 1319 const int64_t TELEMETRY_REQUEST_SIZE_50M = 1320 (int64_t)50 * TELEMETRY_REQUEST_SIZE_1M; 1321 const int64_t TELEMETRY_REQUEST_SIZE_100M = 1322 (int64_t)100 * TELEMETRY_REQUEST_SIZE_1M; 1323 1324 void nsHttpTransaction::Close(nsresult reason) { 1325 LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this, 1326 static_cast<uint32_t>(reason))); 1327 1328 if (!mClosed) { 1329 gHttpHandler->ConnMgr()->RemoveActiveTransaction(this); 1330 mActivated = false; 1331 } 1332 1333 if (mDNSRequest) { 1334 mDNSRequest->Cancel(NS_ERROR_ABORT); 1335 mDNSRequest = nullptr; 1336 } 1337 1338 // If an HTTP/3 backup timer is active and this transaction ends in error, 1339 // treat it as NS_ERROR_NET_RESET so the transaction will retry once. 1340 // NOTE: This is a temporary workaround; the proper fix belongs in 1341 // the Happy Eyeballs project. 1342 if (NS_FAILED(reason) && AllowedErrorForTransactionRetry(reason) && 1343 mHttp3BackupTimerCreated && mHttp3BackupTimer) { 1344 reason = NS_ERROR_NET_RESET; 1345 } 1346 1347 MaybeCancelFallbackTimer(); 1348 1349 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1350 if (reason == NS_BINDING_RETARGETED) { 1351 LOG((" close %p skipped due to ERETARGETED\n", this)); 1352 return; 1353 } 1354 1355 if (mClosed) { 1356 LOG((" already closed\n")); 1357 return; 1358 } 1359 1360 NotifyTransactionObserver(reason); 1361 1362 if (mTokenBucketCancel) { 1363 mTokenBucketCancel->Cancel(reason); 1364 mTokenBucketCancel = nullptr; 1365 } 1366 1367 // report the reponse is complete if not already reported 1368 if (!mResponseIsComplete) { 1369 gHttpHandler->ObserveHttpActivityWithArgs( 1370 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 1371 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(), 1372 static_cast<uint64_t>(mContentRead), ""_ns); 1373 } 1374 1375 // report that this transaction is closing 1376 gHttpHandler->ObserveHttpActivityWithArgs( 1377 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 1378 NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0, ""_ns); 1379 1380 // we must no longer reference the connection! find out if the 1381 // connection was being reused before letting it go. 1382 bool connReused = false; 1383 bool isHttp2or3 = false; 1384 if (mConnection) { 1385 connReused = mConnection->IsReused(); 1386 isHttp2or3 = mConnection->Version() >= HttpVersion::v2_0; 1387 if (!mConnected) { 1388 MaybeRefreshSecurityInfo(); 1389 } 1390 } 1391 mConnected = false; 1392 1393 // When mDoNotRemoveAltSvc is true, this means we want to keep the AltSvc in 1394 // in the conncetion info. In this case, let's not apply HTTPS RR retry logic 1395 // to make sure this transaction can be restarted with the same conncetion 1396 // info. 1397 bool shouldRestartTransactionForHTTPSRR = 1398 mOrigConnInfo && AllowedErrorForTransactionRetry(reason) && 1399 !mDoNotRemoveAltSvc; 1400 1401 // 1402 // if the connection was reset or closed before we wrote any part of the 1403 // request or if we wrote the request but didn't receive any part of the 1404 // response and the connection was being reused, then we can (and really 1405 // should) assume that we wrote to a stale connection and we must therefore 1406 // repeat the request over a new connection. 1407 // 1408 // We have decided to retry not only in case of the reused connections, but 1409 // all safe methods(bug 1236277). 1410 // 1411 // NOTE: the conditions under which we will automatically retry the HTTP 1412 // request have to be carefully selected to avoid duplication of the 1413 // request from the point-of-view of the server. such duplication could 1414 // have dire consequences including repeated purchases, etc. 1415 // 1416 // NOTE: because of the way SSL proxy CONNECT is implemented, it is 1417 // possible that the transaction may have received data without having 1418 // sent any data. for this reason, mSendData == FALSE does not imply 1419 // mReceivedData == FALSE. (see bug 203057 for more info.) 1420 // 1421 // Never restart transactions that are marked as sticky to their conenction. 1422 // We use that capability to identify transactions bound to connection based 1423 // authentication. Reissuing them on a different connections will break 1424 // this bondage. Major issue may arise when there is an NTLM message auth 1425 // header on the transaction and we send it to a different NTLM authenticated 1426 // connection. It will break that connection and also confuse the channel's 1427 // auth provider, beliving the cached credentials are wrong and asking for 1428 // the password mistakenly again from the user. 1429 if ((reason == NS_ERROR_NET_RESET || reason == NS_OK || 1430 reason == 1431 psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) || 1432 reason == NS_ERROR_HTTP2_FALLBACK_TO_HTTP1 || 1433 ShouldRestartOn0RttError(reason) || 1434 shouldRestartTransactionForHTTPSRR) && 1435 (!(mCaps & NS_HTTP_STICKY_CONNECTION) || 1436 (mCaps & NS_HTTP_CONNECTION_RESTARTABLE) || 1437 (mEarlyDataDisposition == EARLY_425))) { 1438 if (mForceRestart) { 1439 SetRestartReason(TRANSACTION_RESTART_FORCED); 1440 if (NS_SUCCEEDED(Restart())) { 1441 if (mResponseHead) { 1442 mResponseHead->Reset(); 1443 } 1444 mContentRead = 0; 1445 mContentLength = -1; 1446 delete mChunkedDecoder; 1447 mChunkedDecoder = nullptr; 1448 mHaveStatusLine = false; 1449 mHaveAllHeaders = false; 1450 mHttpResponseMatched = false; 1451 mResponseIsComplete = false; 1452 mDidContentStart = false; 1453 mNoContent = false; 1454 mSentData = false; 1455 mReceivedData = false; 1456 mSupportsHTTP3 = false; 1457 LOG(("transaction force restarted\n")); 1458 return; 1459 } 1460 } 1461 1462 mDoNotTryEarlyData = true; 1463 1464 // reallySentData is meant to separate the instances where data has 1465 // been sent by this transaction but buffered at a higher level while 1466 // a TLS session (perhaps via a tunnel) is setup. 1467 bool reallySentData = 1468 mSentData && (!mConnection || mConnection->BytesWritten()); 1469 1470 // If this is true, it means we failed to use the HTTPSSVC connection info 1471 // to connect to the server. We need to retry with the original connection 1472 // info. 1473 shouldRestartTransactionForHTTPSRR &= !reallySentData; 1474 1475 if (reason == 1476 psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) || 1477 PossibleZeroRTTRetryError(reason) || 1478 (!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) || 1479 !reallySentData || connReused)) || 1480 shouldRestartTransactionForHTTPSRR) { 1481 if (shouldRestartTransactionForHTTPSRR) { 1482 MaybeReportFailedSVCDomain(reason, mConnInfo); 1483 PrepareConnInfoForRetry(reason); 1484 mDontRetryWithDirectRoute = true; 1485 LOG( 1486 ("transaction will be restarted with the fallback connection info " 1487 "key=%s", 1488 mConnInfo ? mConnInfo->HashKey().get() : "None")); 1489 } 1490 1491 if (shouldRestartTransactionForHTTPSRR) { 1492 auto toRestartReason = 1493 [](nsresult aStatus) -> TRANSACTION_RESTART_REASON { 1494 if (aStatus == NS_ERROR_NET_RESET) { 1495 return TRANSACTION_RESTART_HTTPS_RR_NET_RESET; 1496 } 1497 if (aStatus == NS_ERROR_CONNECTION_REFUSED) { 1498 return TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED; 1499 } 1500 if (aStatus == NS_ERROR_UNKNOWN_HOST) { 1501 return TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST; 1502 } 1503 if (aStatus == NS_ERROR_NET_TIMEOUT) { 1504 return TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT; 1505 } 1506 if (psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aStatus))) { 1507 return TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR; 1508 } 1509 MOZ_ASSERT_UNREACHABLE("Unexpected reason"); 1510 return TRANSACTION_RESTART_OTHERS; 1511 }; 1512 SetRestartReason(toRestartReason(reason)); 1513 } else if (!reallySentData) { 1514 SetRestartReason(TRANSACTION_RESTART_NO_DATA_SENT); 1515 } else if (reason == psm::GetXPCOMFromNSSError( 1516 SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) { 1517 SetRestartReason(TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA); 1518 } else if (PossibleZeroRTTRetryError(reason)) { 1519 SetRestartReason(TRANSACTION_RESTART_POSSIBLE_0RTT_ERROR); 1520 } 1521 // if restarting fails, then we must proceed to close the pipe, 1522 // which will notify the channel that the transaction failed. 1523 // Note that when echConfig is enabled, it's possible that we don't have a 1524 // usable connection info to retry. 1525 if (mConnInfo && NS_SUCCEEDED(Restart())) { 1526 return; 1527 } 1528 // mConnInfo could be set to null in PrepareConnInfoForRetry() when we 1529 // can't find an available https rr to retry. We have to set mConnInfo 1530 // back to mOrigConnInfo to make sure no crash when mConnInfo being 1531 // accessed again. 1532 if (!mConnInfo) { 1533 mConnInfo.swap(mOrigConnInfo); 1534 MOZ_ASSERT(mConnInfo); 1535 } 1536 } 1537 } 1538 1539 glean::http::transaction_restart_reason.AccumulateSingleSample( 1540 mRestartReason); 1541 1542 if (!mResponseIsComplete && NS_SUCCEEDED(reason) && isHttp2or3) { 1543 // Responses without content-length header field are still complete if 1544 // they are transfered over http2 or http3 and the stream is properly 1545 // closed. 1546 mResponseIsComplete = true; 1547 } 1548 1549 if (reason == NS_ERROR_NET_RESET && mResponseIsComplete && isHttp2or3) { 1550 // See bug 1940663. When using HTTP/2 or HTTP/3, receiving the 1551 // NS_ERROR_NET_RESET error code indicates that the connection intends 1552 // to restart this transaction. However, if the transaction has already 1553 // completed and we've passed the point of restarting, we should avoid 1554 // propagating the error code and overwrite it to NS_OK. 1555 // 1556 // TODO: Refactor the mechanism by which a connection instructs a 1557 // transaction to restart. This will allow us to remove this hack. 1558 LOG(("Transaction is already done, overriding error code to NS_OK")); 1559 reason = NS_OK; 1560 } 1561 1562 if ((mChunkedDecoder || (mContentLength >= int64_t(0))) && 1563 (NS_SUCCEEDED(reason) && !mResponseIsComplete)) { 1564 NS_WARNING("Partial transfer, incomplete HTTP response received"); 1565 1566 if ((mHttpResponseCode / 100 == 2) && (mHttpVersion >= HttpVersion::v1_1)) { 1567 FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing(); 1568 if (clevel >= FRAMECHECK_BARELY) { 1569 // If clevel == FRAMECHECK_STRICT mark any incomplete response as 1570 // partial. 1571 // if clevel == FRAMECHECK_BARELY: 1) mark a chunked-encoded response 1572 // that do not ends on exactly a chunk boundary as partial; We are not 1573 // strict about the last 0-size chunk and do not mark as parial 1574 // responses that do not have the last 0-size chunk but do end on a 1575 // chunk boundary. (check mChunkedDecoder->GetChunkRemaining() != 0) 1576 // 2) mark a transfer that is partial and it is not chunk-encoded or 1577 // gzip-encoded or other content-encoding as partial. (check 1578 // !mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) 1579 // if clevel == FRAMECHECK_STRICT_CHUNKED mark a chunked-encoded 1580 // response that ends on exactly a chunk boundary also as partial. 1581 // Here a response must have the last 0-size chunk. 1582 if ((clevel == FRAMECHECK_STRICT) || 1583 (mChunkedDecoder && (mChunkedDecoder->GetChunkRemaining() || 1584 (clevel == FRAMECHECK_STRICT_CHUNKED))) || 1585 (!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) { 1586 reason = NS_ERROR_NET_PARTIAL_TRANSFER; 1587 LOG(("Partial transfer, incomplete HTTP response received: %s", 1588 mChunkedDecoder ? "broken chunk" : "c-l underrun")); 1589 } 1590 } 1591 } 1592 1593 if (mConnection) { 1594 // whether or not we generate an error for the transaction 1595 // bad framing means we don't want a pconn 1596 mConnection->DontReuse(); 1597 } 1598 } 1599 1600 bool relConn = true; 1601 if (NS_SUCCEEDED(reason)) { 1602 // the server has not sent the final \r\n terminating the header 1603 // section, and there may still be a header line unparsed. let's make 1604 // sure we parse the remaining header line, and then hopefully, the 1605 // response will be usable (see bug 88792). 1606 if (!mHaveAllHeaders) { 1607 char data[] = "\n\n"; 1608 uint32_t unused = 0; 1609 // If we have a partial line already, we actually need two \ns to finish 1610 // the headers section. 1611 (void)ParseHead(data, mLineBuf.IsEmpty() ? 1 : 2, &unused); 1612 1613 if (mResponseHead->Version() == HttpVersion::v0_9) { 1614 // Reject 0 byte HTTP/0.9 Responses - bug 423506 1615 LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this)); 1616 reason = NS_ERROR_NET_RESET; 1617 } 1618 } 1619 1620 // honor the sticky connection flag... 1621 if (mCaps & NS_HTTP_STICKY_CONNECTION) { 1622 LOG((" keeping the connection because of STICKY_CONNECTION flag")); 1623 relConn = false; 1624 } 1625 1626 // if the proxy connection has failed, we want the connection be held 1627 // to allow the upper layers (think nsHttpChannel) to close it when 1628 // the failure is unrecoverable. 1629 // we can't just close it here, because mProxyConnectFailed is to a general 1630 // flag and is also set for e.g. 407 which doesn't mean to kill the 1631 // connection, specifically when connection oriented auth may be involved. 1632 if (mProxyConnectFailed) { 1633 LOG((" keeping the connection because of mProxyConnectFailed")); 1634 relConn = false; 1635 } 1636 1637 // Use mOrigConnInfo as an indicator that this transaction is completed 1638 // successfully with an HTTPSSVC record. 1639 if (mOrigConnInfo) { 1640 glean::http::dns_httpssvc_connection_failed_reason.AccumulateSingleSample( 1641 HTTPSSVC_CONNECTION_OK); 1642 } 1643 } 1644 1645 // mTimings.responseEnd is normally recorded based on the end of a 1646 // HTTP delimiter such as chunked-encodings or content-length. However, 1647 // EOF or an error still require an end time be recorded. 1648 1649 const TimingStruct timings = Timings(); 1650 if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) { 1651 SetResponseEnd(TimeStamp::Now()); 1652 } 1653 1654 if (!timings.requestStart.IsNull() && !timings.responseEnd.IsNull()) { 1655 TimeDuration elapsed = timings.responseEnd - timings.requestStart; 1656 double megabits = static_cast<double>(mContentRead) * 8.0 / 1000000.0; 1657 uint32_t mbps = static_cast<uint32_t>(megabits / elapsed.ToSeconds()); 1658 nsAutoCString serverKey; 1659 1660 switch (mHttpVersion) { 1661 case HttpVersion::v1_0: 1662 case HttpVersion::v1_1: { 1663 if (NS_SUCCEEDED(reason)) { 1664 serverKey.Assign(mServerHeader.EqualsLiteral("cloudflare") 1665 ? "h1_cloudflare"_ns 1666 : "h1_others"_ns); 1667 } 1668 if (mContentRead > TELEMETRY_REQUEST_SIZE_10M) { 1669 glean::networking::http_1_download_throughput.AccumulateSingleSample( 1670 mbps); 1671 if (mContentRead <= TELEMETRY_REQUEST_SIZE_50M) { 1672 glean::networking::http_1_download_throughput_10_50 1673 .AccumulateSingleSample(mbps); 1674 } else if (mContentRead <= TELEMETRY_REQUEST_SIZE_100M) { 1675 glean::networking::http_1_download_throughput_50_100 1676 .AccumulateSingleSample(mbps); 1677 } else { 1678 glean::networking::http_1_download_throughput_100 1679 .AccumulateSingleSample(mbps); 1680 } 1681 } 1682 break; 1683 } 1684 case HttpVersion::v2_0: { 1685 if (NS_SUCCEEDED(reason)) { 1686 serverKey.Assign(mServerHeader.EqualsLiteral("cloudflare") 1687 ? "h2_cloudflare"_ns 1688 : "h2_others"_ns); 1689 } 1690 if (mContentRead > TELEMETRY_REQUEST_SIZE_10M) { 1691 if (mContentRead <= TELEMETRY_REQUEST_SIZE_50M) { 1692 glean::networking::http_2_download_throughput_10_50 1693 .AccumulateSingleSample(mbps); 1694 } else if (mContentRead <= TELEMETRY_REQUEST_SIZE_100M) { 1695 glean::networking::http_2_download_throughput_50_100 1696 .AccumulateSingleSample(mbps); 1697 } else { 1698 glean::networking::http_2_download_throughput_100 1699 .AccumulateSingleSample(mbps); 1700 } 1701 } 1702 break; 1703 } 1704 case HttpVersion::v3_0: { 1705 if (NS_SUCCEEDED(reason)) { 1706 serverKey.Assign(mServerHeader.EqualsLiteral("cloudflare") 1707 ? "h3_cloudflare"_ns 1708 : "h3_others"_ns); 1709 } 1710 if (mContentRead > TELEMETRY_REQUEST_SIZE_10M) { 1711 if (mContentRead <= TELEMETRY_REQUEST_SIZE_50M) { 1712 glean::networking::http_3_download_throughput_10_50 1713 .AccumulateSingleSample(mbps); 1714 } else if (mContentRead <= TELEMETRY_REQUEST_SIZE_100M) { 1715 glean::networking::http_3_download_throughput_50_100 1716 .AccumulateSingleSample(mbps); 1717 } else { 1718 glean::networking::http_3_download_throughput_100 1719 .AccumulateSingleSample(mbps); 1720 } 1721 } 1722 break; 1723 } 1724 default: 1725 break; 1726 } 1727 } 1728 1729 if (mTrafficCategory != HttpTrafficCategory::eInvalid) { 1730 HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer(); 1731 if (hta) { 1732 hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize, 1733 mContentRead); 1734 } 1735 } 1736 1737 if (isHttp2or3 && 1738 reason == psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT)) { 1739 // Change reason to NS_ERROR_ABORT, so we avoid showing a missleading 1740 // error page tthat TLS1.0 is disabled. H2 or H3 is used here so the 1741 // TLS version is not a problem. 1742 reason = NS_ERROR_ABORT; 1743 } 1744 mStatus = reason; 1745 mTransactionDone = true; // forcibly flag the transaction as complete 1746 mClosed = true; 1747 if (mResolver) { 1748 mResolver->Close(); 1749 mResolver = nullptr; 1750 } 1751 1752 { 1753 MutexAutoLock lock(mLock); 1754 mEarlyHintObserver = nullptr; 1755 mWebTransportSessionEventListener = nullptr; 1756 if (relConn && mConnection) { 1757 mConnection = nullptr; 1758 } 1759 } 1760 1761 ReleaseBlockingTransaction(); 1762 1763 // release some resources that we no longer need 1764 mRequestStream = nullptr; 1765 mReqHeaderBuf.Truncate(); 1766 mLineBuf.Truncate(); 1767 if (mChunkedDecoder) { 1768 delete mChunkedDecoder; 1769 mChunkedDecoder = nullptr; 1770 } 1771 1772 for (const auto& entry : mEchRetryCounterMap) { 1773 switch (entry.GetKey()) { 1774 case TRANSACTION_ECH_RETRY_OTHERS_COUNT: 1775 glean::http::transaction_ech_retry_others_count.AccumulateSingleSample( 1776 entry.GetData()); 1777 break; 1778 case TRANSACTION_ECH_RETRY_WITH_ECH_COUNT: 1779 glean::http::transaction_ech_retry_with_ech_count 1780 .AccumulateSingleSample(entry.GetData()); 1781 break; 1782 case TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT: 1783 glean::http::transaction_ech_retry_without_ech_count 1784 .AccumulateSingleSample(entry.GetData()); 1785 break; 1786 case TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT: 1787 glean::http::transaction_ech_retry_ech_failed_count 1788 .AccumulateSingleSample(entry.GetData()); 1789 break; 1790 } 1791 } 1792 1793 // closing this pipe triggers the channel's OnStopRequest method. 1794 mPipeOut->CloseWithStatus(reason); 1795 } 1796 1797 nsHttpConnectionInfo* nsHttpTransaction::ConnectionInfo() { 1798 return mConnInfo.get(); 1799 } 1800 1801 bool // NOTE BASE CLASS 1802 nsAHttpTransaction::ResponseTimeoutEnabled() const { 1803 return false; 1804 } 1805 1806 PRIntervalTime // NOTE BASE CLASS 1807 nsAHttpTransaction::ResponseTimeout() { 1808 return gHttpHandler->ResponseTimeout(); 1809 } 1810 1811 bool nsHttpTransaction::ResponseTimeoutEnabled() const { 1812 return mResponseTimeoutEnabled; 1813 } 1814 1815 //----------------------------------------------------------------------------- 1816 // nsHttpTransaction <private> 1817 //----------------------------------------------------------------------------- 1818 1819 static inline void RemoveAlternateServiceUsedHeader( 1820 nsHttpRequestHead* aRequestHead) { 1821 if (aRequestHead) { 1822 DebugOnly<nsresult> rv = 1823 aRequestHead->SetHeader(nsHttp::Alternate_Service_Used, "0"_ns); 1824 MOZ_ASSERT(NS_SUCCEEDED(rv)); 1825 } 1826 } 1827 1828 void nsHttpTransaction::FinalizeConnInfo() { 1829 RefPtr<nsHttpConnectionInfo> cloned = mConnInfo->Clone(); 1830 { 1831 MutexAutoLock lock(mLock); 1832 mFinalizedConnInfo.swap(cloned); 1833 } 1834 } 1835 1836 void nsHttpTransaction::SetRestartReason(TRANSACTION_RESTART_REASON aReason) { 1837 if (mRestartReason == TRANSACTION_RESTART_NONE) { 1838 mRestartReason = aReason; 1839 } 1840 } 1841 1842 nsresult nsHttpTransaction::Restart() { 1843 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 1844 1845 // limit the number of restart attempts - bug 92224 1846 if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) { 1847 LOG(("reached max request attempts, failing transaction @%p\n", this)); 1848 return NS_ERROR_NET_RESET; 1849 } 1850 1851 // Let's not restart during shutdown. 1852 if (AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed)) { 1853 return NS_ERROR_ILLEGAL_DURING_SHUTDOWN; 1854 } 1855 1856 LOG(("restarting transaction @%p\n", this)); 1857 1858 if (mRequestHead) { 1859 // Dispatching on a new connection better w/o an ambient connection proxy 1860 // auth request header to not confuse the proxy authenticator. 1861 nsAutoCString proxyAuth; 1862 if (NS_SUCCEEDED( 1863 mRequestHead->GetHeader(nsHttp::Proxy_Authorization, proxyAuth)) && 1864 IsStickyAuthSchemeAt(proxyAuth)) { 1865 (void)mRequestHead->ClearHeader(nsHttp::Proxy_Authorization); 1866 } 1867 } 1868 1869 // rewind streams in case we already wrote out the request 1870 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream); 1871 if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); 1872 1873 if (mDoNotTryEarlyData) { 1874 MutexAutoLock lock(mLock); 1875 MaybeRemoveSSLToken(mSecurityInfo); 1876 } 1877 1878 // clear old connection state... 1879 { 1880 MutexAutoLock lock(mLock); 1881 mSecurityInfo = nullptr; 1882 } 1883 1884 if (mConnection) { 1885 if (!mReuseOnRestart) { 1886 mConnection->DontReuse(); 1887 } 1888 MutexAutoLock lock(mLock); 1889 mConnection = nullptr; 1890 } 1891 1892 // Reset this to our default state, since this may change from one restart 1893 // to the next 1894 mReuseOnRestart = false; 1895 1896 if (!mDoNotRemoveAltSvc && !mDontRetryWithDirectRoute) { 1897 if (mConnInfo->IsHttp3ProxyConnection()) { 1898 RefPtr<nsHttpConnectionInfo> ci = 1899 mConnInfo->CreateConnectUDPFallbackConnInfo(); 1900 mConnInfo = ci; 1901 RemoveAlternateServiceUsedHeader(mRequestHead); 1902 } else if (!mConnInfo->GetRoutedHost().IsEmpty() || mConnInfo->IsHttp3()) { 1903 RefPtr<nsHttpConnectionInfo> ci; 1904 mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci)); 1905 mConnInfo = ci; 1906 RemoveAlternateServiceUsedHeader(mRequestHead); 1907 } 1908 } 1909 1910 // Reset mDoNotRemoveAltSvc for the next try. 1911 mDoNotRemoveAltSvc = false; 1912 mEarlyDataWasAvailable = false; 1913 mRestarted = true; 1914 1915 // If we weren't trying to do 'proper' ECH, disable ECH GREASE when retrying. 1916 if (mConnInfo->GetEchConfig().IsEmpty() && 1917 StaticPrefs::security_tls_ech_disable_grease_on_fallback()) { 1918 mCaps |= NS_HTTP_DISALLOW_ECH; 1919 } 1920 1921 mCaps |= NS_HTTP_IS_RETRY; 1922 1923 // Use TRANSACTION_RESTART_OTHERS as a catch-all. 1924 SetRestartReason(TRANSACTION_RESTART_OTHERS); 1925 1926 if (!mDoNotResetIPFamilyPreference) { 1927 // Reset the IP family preferences, so the new connection can try to use 1928 // another IPv4 or IPv6 address. 1929 gHttpHandler->ConnMgr()->ResetIPFamilyPreference(mConnInfo); 1930 } 1931 1932 return gHttpHandler->InitiateTransaction(this, mPriority); 1933 } 1934 1935 bool nsHttpTransaction::TakeRestartedState() { 1936 // This return true if the transaction has been restarted internally. Used to 1937 // let the consuming nsHttpChannel reset proxy authentication. The flag is 1938 // reset to false by this method. 1939 return mRestarted.exchange(false); 1940 } 1941 1942 char* nsHttpTransaction::LocateHttpStart(char* buf, uint32_t len, 1943 bool aAllowPartialMatch) { 1944 MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty()); 1945 1946 static const char HTTPHeader[] = "HTTP/1."; 1947 static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1; 1948 static const char HTTP2Header[] = "HTTP/2"; 1949 static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1; 1950 static const char HTTP3Header[] = "HTTP/3"; 1951 static const uint32_t HTTP3HeaderLen = sizeof(HTTP3Header) - 1; 1952 // ShoutCast ICY is treated as HTTP/1.0 1953 static const char ICYHeader[] = "ICY "; 1954 static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1; 1955 1956 if (aAllowPartialMatch && (len < HTTPHeaderLen)) { 1957 return (nsCRT::strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr; 1958 } 1959 1960 // mLineBuf can contain partial match from previous search 1961 if (!mLineBuf.IsEmpty()) { 1962 MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen); 1963 int32_t checkChars = 1964 std::min<uint32_t>(len, HTTPHeaderLen - mLineBuf.Length()); 1965 if (nsCRT::strncasecmp(buf, HTTPHeader + mLineBuf.Length(), checkChars) == 1966 0) { 1967 mLineBuf.Append(buf, checkChars); 1968 if (mLineBuf.Length() == HTTPHeaderLen) { 1969 // We've found whole HTTPHeader sequence. Return pointer at the 1970 // end of matched sequence since it is stored in mLineBuf. 1971 return (buf + checkChars); 1972 } 1973 // Response matches pattern but is still incomplete. 1974 return nullptr; 1975 } 1976 // Previous partial match together with new data doesn't match the 1977 // pattern. Start the search again. 1978 mLineBuf.Truncate(); 1979 } 1980 1981 bool firstByte = true; 1982 while (len > 0) { 1983 if (nsCRT::strncasecmp(buf, HTTPHeader, 1984 std::min<uint32_t>(len, HTTPHeaderLen)) == 0) { 1985 if (len < HTTPHeaderLen) { 1986 // partial HTTPHeader sequence found 1987 // save partial match to mLineBuf 1988 mLineBuf.Assign(buf, len); 1989 return nullptr; 1990 } 1991 1992 // whole HTTPHeader sequence found 1993 return buf; 1994 } 1995 1996 // At least "SmarterTools/2.0.3974.16813" generates nonsensical 1997 // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of 1998 // it as HTTP/1.1 to be compatible with old versions of ourselves and 1999 // other browsers 2000 2001 if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen && 2002 (nsCRT::strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) { 2003 LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n")); 2004 return buf; 2005 } 2006 2007 // HTTP/3.0 responses to our HTTP/1 requests. Treat the minimal case of 2008 // it as HTTP/1.1 to be compatible with old versions of ourselves and 2009 // other browsers 2010 2011 if (firstByte && !mInvalidResponseBytesRead && len >= HTTP3HeaderLen && 2012 (nsCRT::strncasecmp(buf, HTTP3Header, HTTP3HeaderLen) == 0)) { 2013 LOG(("nsHttpTransaction:: Identified HTTP/3.0 treating as 1.x\n")); 2014 return buf; 2015 } 2016 2017 // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion 2018 // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted 2019 // as HTTP/1.0 in nsHttpResponseHead::ParseVersion 2020 2021 if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen && 2022 (nsCRT::strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) { 2023 LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n")); 2024 return buf; 2025 } 2026 2027 if (!nsCRT::IsAsciiSpace(*buf)) firstByte = false; 2028 buf++; 2029 len--; 2030 } 2031 return nullptr; 2032 } 2033 2034 nsresult nsHttpTransaction::ParseLine(nsACString& line) { 2035 LOG1(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get())); 2036 nsresult rv = NS_OK; 2037 2038 if (!mHaveStatusLine) { 2039 rv = mResponseHead->ParseStatusLine(line); 2040 if (NS_SUCCEEDED(rv)) { 2041 mHaveStatusLine = true; 2042 } 2043 // XXX this should probably never happen 2044 if (mResponseHead->Version() == HttpVersion::v0_9) mHaveAllHeaders = true; 2045 } else { 2046 rv = mResponseHead->ParseHeaderLine(line); 2047 } 2048 return rv; 2049 } 2050 2051 nsresult nsHttpTransaction::ParseLineSegment(char* segment, uint32_t len) { 2052 MOZ_ASSERT(!mHaveAllHeaders, "already have all headers"); 2053 2054 if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') { 2055 // trim off the new line char, and if this segment is 2056 // not a continuation of the previous or if we haven't 2057 // parsed the status line yet, then parse the contents 2058 // of mLineBuf. 2059 mLineBuf.Truncate(mLineBuf.Length() - 1); 2060 if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) { 2061 nsresult rv = ParseLine(mLineBuf); 2062 mLineBuf.Truncate(); 2063 if (NS_FAILED(rv)) { 2064 return rv; 2065 } 2066 } 2067 } 2068 2069 // append segment to mLineBuf... 2070 mLineBuf.Append(segment, len); 2071 2072 // a line buf with only a new line char signifies the end of headers. 2073 if (mLineBuf.First() == '\n') { 2074 mLineBuf.Truncate(); 2075 // discard this response if it is a 100 continue or other 1xx status. 2076 uint16_t status = mResponseHead->Status(); 2077 if (status == 103 && 2078 (StaticPrefs::network_early_hints_over_http_v1_1_enabled() || 2079 mResponseHead->Version() != HttpVersion::v1_1)) { 2080 // Observe Early Hints info for interfacing with Devtools 2081 ReportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_EARLYHINT_RESPONSE_HEADER); 2082 2083 nsCString linkHeader; 2084 nsresult rv = mResponseHead->GetHeader(nsHttp::Link, linkHeader); 2085 2086 nsCString referrerPolicy; 2087 (void)mResponseHead->GetHeader(nsHttp::Referrer_Policy, referrerPolicy); 2088 2089 if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) { 2090 nsCString cspHeader; 2091 (void)mResponseHead->GetHeader(nsHttp::Content_Security_Policy, 2092 cspHeader); 2093 2094 nsCOMPtr<nsIEarlyHintObserver> earlyHint; 2095 { 2096 MutexAutoLock lock(mLock); 2097 earlyHint = mEarlyHintObserver; 2098 } 2099 if (earlyHint) { 2100 DebugOnly<nsresult> rv = NS_DispatchToMainThread( 2101 NS_NewRunnableFunction( 2102 "nsIEarlyHintObserver->EarlyHint", 2103 [obs{std::move(earlyHint)}, header{std::move(linkHeader)}, 2104 referrerPolicy{std::move(referrerPolicy)}, 2105 cspHeader{std::move(cspHeader)}]() { 2106 obs->EarlyHint(header, referrerPolicy, cspHeader); 2107 }), 2108 NS_DISPATCH_NORMAL); 2109 MOZ_ASSERT(NS_SUCCEEDED(rv)); 2110 } 2111 } 2112 } 2113 if ((status != 101) && (status / 100 == 1)) { 2114 LOG(("ignoring 1xx response except 101 and 103\n")); 2115 mHaveStatusLine = false; 2116 mHttpResponseMatched = false; 2117 mConnection->SetLastTransactionExpectedNoContent(true); 2118 mResponseHead->Reset(); 2119 return NS_OK; 2120 } 2121 if (!mConnection->IsProxyConnectInProgress()) { 2122 MutexAutoLock lock(mLock); 2123 mEarlyHintObserver = nullptr; 2124 } 2125 mHaveAllHeaders = true; 2126 } 2127 return NS_OK; 2128 } 2129 2130 nsresult nsHttpTransaction::ParseHead(char* buf, uint32_t count, 2131 uint32_t* countRead) { 2132 nsresult rv; 2133 uint32_t len; 2134 char* eol; 2135 2136 LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count)); 2137 2138 *countRead = 0; 2139 2140 MOZ_ASSERT(!mHaveAllHeaders, "oops"); 2141 2142 // allocate the response head object if necessary 2143 if (!mResponseHead) { 2144 mResponseHead = new nsHttpResponseHead(); 2145 if (!mResponseHead) return NS_ERROR_OUT_OF_MEMORY; 2146 2147 // report that we have a least some of the response 2148 if (!mReportedStart) { 2149 mReportedStart = true; 2150 gHttpHandler->ObserveHttpActivityWithArgs( 2151 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 2152 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, PR_Now(), 0, ""_ns); 2153 } 2154 } 2155 2156 if (!mHttpResponseMatched) { 2157 // Normally we insist on seeing HTTP/1.x in the first few bytes, 2158 // but if we are on a persistent connection and the previous transaction 2159 // was not supposed to have any content then we need to be prepared 2160 // to skip over a response body that the server may have sent even 2161 // though it wasn't allowed. 2162 if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) { 2163 // tolerate only minor junk before the status line 2164 mHttpResponseMatched = true; 2165 char* p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true); 2166 if (!p) { 2167 // Treat any 0.9 style response of a put as a failure. 2168 if (mRequestHead->IsPut()) return NS_ERROR_ABORT; 2169 2170 if (NS_FAILED(mResponseHead->ParseStatusLine(""_ns))) { 2171 return NS_ERROR_FAILURE; 2172 } 2173 mHaveStatusLine = true; 2174 mHaveAllHeaders = true; 2175 return NS_OK; 2176 } 2177 if (p > buf) { 2178 // skip over the junk 2179 mInvalidResponseBytesRead += p - buf; 2180 *countRead = p - buf; 2181 buf = p; 2182 } 2183 } else { 2184 char* p = LocateHttpStart(buf, count, false); 2185 if (p) { 2186 mInvalidResponseBytesRead += p - buf; 2187 *countRead = p - buf; 2188 buf = p; 2189 mHttpResponseMatched = true; 2190 } else { 2191 mInvalidResponseBytesRead += count; 2192 *countRead = count; 2193 if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) { 2194 LOG( 2195 ("nsHttpTransaction::ParseHead() " 2196 "Cannot find Response Header\n")); 2197 // cannot go back and call this 0.9 anymore as we 2198 // have thrown away a lot of the leading junk 2199 return NS_ERROR_ABORT; 2200 } 2201 return NS_OK; 2202 } 2203 } 2204 } 2205 // otherwise we can assume that we don't have a HTTP/0.9 response. 2206 2207 MOZ_ASSERT(mHttpResponseMatched); 2208 while ((eol = static_cast<char*>(memchr(buf, '\n', count - *countRead))) != 2209 nullptr) { 2210 // found line in range [buf:eol] 2211 len = eol - buf + 1; 2212 2213 *countRead += len; 2214 2215 // actually, the line is in the range [buf:eol-1] 2216 if ((eol > buf) && (*(eol - 1) == '\r')) len--; 2217 2218 buf[len - 1] = '\n'; 2219 rv = ParseLineSegment(buf, len); 2220 if (NS_FAILED(rv)) return rv; 2221 2222 if (mHaveAllHeaders) return NS_OK; 2223 2224 // skip over line 2225 buf = eol + 1; 2226 2227 if (!mHttpResponseMatched) { 2228 // a 100 class response has caused us to throw away that set of 2229 // response headers and look for the next response 2230 return NS_ERROR_NET_INTERRUPT; 2231 } 2232 } 2233 2234 // do something about a partial header line 2235 if (!mHaveAllHeaders && (len = count - *countRead)) { 2236 *countRead = count; 2237 // ignore a trailing carriage return, and don't bother calling 2238 // ParseLineSegment if buf only contains a carriage return. 2239 if ((buf[len - 1] == '\r') && (--len == 0)) return NS_OK; 2240 rv = ParseLineSegment(buf, len); 2241 if (NS_FAILED(rv)) return rv; 2242 } 2243 return NS_OK; 2244 } 2245 2246 bool nsHttpTransaction::HandleWebTransportResponse(uint16_t aStatus) { 2247 MOZ_ASSERT(mIsForWebTransport); 2248 if (!(aStatus >= 200 && aStatus < 300)) { 2249 return false; 2250 } 2251 LOG(("HandleWebTransportResponse mConnection=%p", mConnection.get())); 2252 RefPtr<WebTransportSessionBase> wtSession = 2253 mConnection->GetWebTransportSession(this); 2254 if (!wtSession) { 2255 return false; 2256 } 2257 2258 nsCOMPtr<WebTransportSessionEventListener> webTransportListener; 2259 { 2260 MutexAutoLock lock(mLock); 2261 webTransportListener = mWebTransportSessionEventListener; 2262 mWebTransportSessionEventListener = nullptr; 2263 } 2264 if (nsCOMPtr<WebTransportSessionEventListenerInternal> listener = 2265 do_QueryInterface(webTransportListener)) { 2266 listener->OnSessionReadyInternal(wtSession); 2267 wtSession->SetWebTransportSessionEventListener(webTransportListener); 2268 } 2269 2270 return true; 2271 } 2272 2273 nsresult nsHttpTransaction::HandleContentStart() { 2274 LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this)); 2275 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2276 2277 if (mResponseHead) { 2278 if (mEarlyDataDisposition == EARLY_ACCEPTED) { 2279 if (mResponseHead->Status() == 425) { 2280 // We will report this state when the final responce arrives. 2281 mEarlyDataDisposition = EARLY_425; 2282 } else { 2283 (void)mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data, 2284 "accepted"_ns); 2285 } 2286 } else if (mEarlyDataDisposition == EARLY_SENT) { 2287 (void)mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data, "sent"_ns); 2288 } else if (mEarlyDataDisposition == EARLY_425) { 2289 (void)mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data, 2290 "received 425"_ns); 2291 mEarlyDataDisposition = EARLY_NONE; 2292 } // no header on NONE case 2293 2294 if (LOG3_ENABLED()) { 2295 LOG3(("http response [\n")); 2296 nsAutoCString headers; 2297 mResponseHead->Flatten(headers, false); 2298 headers.AppendLiteral(" OriginalHeaders"); 2299 headers.AppendLiteral("\r\n"); 2300 mResponseHead->FlattenNetworkOriginalHeaders(headers); 2301 LogHeaders(headers.get()); 2302 LOG3(("]\n")); 2303 } 2304 2305 CheckForStickyAuthScheme(); 2306 2307 // Save http version, mResponseHead isn't available anymore after 2308 // TakeResponseHead() is called 2309 mHttpVersion = mResponseHead->Version(); 2310 mHttpResponseCode = mResponseHead->Status(); 2311 2312 // notify the connection, give it a chance to cause a reset. 2313 bool reset = false; 2314 nsresult rv = mConnection->OnHeadersAvailable(this, mRequestHead, 2315 mResponseHead, &reset); 2316 NS_ENSURE_SUCCESS(rv, rv); 2317 2318 // looks like we should ignore this response, resetting... 2319 if (reset) { 2320 LOG(("resetting transaction's response head\n")); 2321 mHaveAllHeaders = false; 2322 mHaveStatusLine = false; 2323 mReceivedData = false; 2324 mSentData = false; 2325 mHttpResponseMatched = false; 2326 mResponseHead->Reset(); 2327 // wait to be called again... 2328 return NS_OK; 2329 } 2330 2331 (void)mResponseHead->GetHeader(nsHttp::Server, mServerHeader); 2332 2333 bool responseChecked = false; 2334 if (mIsForWebTransport) { 2335 responseChecked = HandleWebTransportResponse(mResponseHead->Status()); 2336 LOG(("HandleWebTransportResponse res=%d", responseChecked)); 2337 if (responseChecked) { 2338 mNoContent = true; 2339 mPreserveStream = true; 2340 } 2341 } 2342 2343 if (!responseChecked) { 2344 // check if this is a no-content response 2345 switch (mResponseHead->Status()) { 2346 case 101: 2347 mPreserveStream = true; 2348 [[fallthrough]]; // to other no content cases: 2349 case 204: 2350 case 205: 2351 case 304: 2352 mNoContent = true; 2353 LOG(("this response should not contain a body.\n")); 2354 break; 2355 case 408: 2356 LOG(("408 Server Timeouts")); 2357 2358 if (mConnection->Version() >= HttpVersion::v2_0) { 2359 mForceRestart = true; 2360 return NS_ERROR_NET_RESET; 2361 } 2362 2363 // If this error could be due to a persistent connection 2364 // reuse then we pass an error code of NS_ERROR_NET_RESET 2365 // to trigger the transaction 'restart' mechanism. We 2366 // tell it to reset its response headers so that it will 2367 // be ready to receive the new response. 2368 LOG(("408 Server Timeouts now=%d lastWrite=%d", PR_IntervalNow(), 2369 mConnection->LastWriteTime())); 2370 if ((PR_IntervalNow() - mConnection->LastWriteTime()) >= 2371 PR_MillisecondsToInterval(1000)) { 2372 mForceRestart = true; 2373 return NS_ERROR_NET_RESET; 2374 } 2375 break; 2376 case 421: 2377 LOG(("Misdirected Request.\n")); 2378 gHttpHandler->ClearHostMapping(mConnInfo); 2379 2380 m421Received = true; 2381 mCaps |= NS_HTTP_REFRESH_DNS; 2382 2383 // retry on a new connection - just in case 2384 // See bug 1609410, we can't restart the transaction when 2385 // NS_HTTP_STICKY_CONNECTION is set. In the case that a connection 2386 // already passed NTLM authentication, restarting the transaction will 2387 // cause the connection to be closed. 2388 if (!mRestartCount && !(mCaps & NS_HTTP_STICKY_CONNECTION)) { 2389 mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE; 2390 mForceRestart = true; // force restart has built in loop protection 2391 return NS_ERROR_NET_RESET; 2392 } 2393 break; 2394 case 425: 2395 LOG(("Too Early.")); 2396 if ((mEarlyDataDisposition == EARLY_425) && !mDoNotTryEarlyData) { 2397 mDoNotTryEarlyData = true; 2398 mForceRestart = true; // force restart has built in loop protection 2399 if (mConnection->Version() >= HttpVersion::v2_0) { 2400 mReuseOnRestart = true; 2401 } 2402 return NS_ERROR_NET_RESET; 2403 } 2404 break; 2405 } 2406 } 2407 2408 // Remember whether HTTP3 is supported 2409 mSupportsHTTP3 = nsHttpHandler::IsHttp3SupportedByServer(mResponseHead); 2410 2411 CollectTelemetryForUploads(); 2412 2413 // Report telemetry 2414 if (mSupportsHTTP3) { 2415 glean::http::transaction_wait_time_http2_sup_http3.AccumulateRawDuration( 2416 mPendingDurationTime); 2417 } 2418 2419 // If we're only connecting then we're going to be upgrading this 2420 // connection since we were successful. Any data from now on belongs to 2421 // the upgrade handler. If we're not successful the content body doesn't 2422 // matter. Proxy http errors are treated as network errors. This 2423 // connection won't be reused since it's marked sticky and no 2424 // keep-alive. 2425 if (mCaps & NS_HTTP_CONNECT_ONLY) { 2426 MOZ_ASSERT(!(mCaps & NS_HTTP_ALLOW_KEEPALIVE) && 2427 (mCaps & NS_HTTP_STICKY_CONNECTION), 2428 "connection should be sticky and no keep-alive"); 2429 // The transaction will expect the server to close the socket if 2430 // there's no content length instead of doing the upgrade. 2431 mNoContent = true; 2432 } 2433 2434 // preserve connection for tunnel setup - h2 websocket upgrade only 2435 if (mIsHttp2Websocket && mResponseHead->Status() == 200) { 2436 LOG(("nsHttpTransaction::HandleContentStart websocket upgrade resp 200")); 2437 mNoContent = true; 2438 } 2439 2440 if (mResponseHead->Status() == 200 && 2441 mConnection->IsProxyConnectInProgress()) { 2442 // successful CONNECTs do not have response bodies 2443 mNoContent = true; 2444 } 2445 mConnection->SetLastTransactionExpectedNoContent(mNoContent); 2446 2447 if (mNoContent) { 2448 mContentLength = 0; 2449 } else { 2450 // grab the content-length from the response headers 2451 mContentLength = mResponseHead->ContentLength(); 2452 2453 // handle chunked encoding here, so we'll know immediately when 2454 // we're done with the socket. please note that _all_ other 2455 // decoding is done when the channel receives the content data 2456 // so as not to block the socket transport thread too much. 2457 if (mResponseHead->Version() >= HttpVersion::v1_0 && 2458 mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) { 2459 // we only support the "chunked" transfer encoding right now. 2460 mChunkedDecoder = new nsHttpChunkedDecoder(); 2461 LOG(("nsHttpTransaction %p chunked decoder created\n", this)); 2462 // Ignore server specified Content-Length. 2463 if (mContentLength != int64_t(-1)) { 2464 LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this)); 2465 mContentLength = -1; 2466 if (mConnection) { 2467 mConnection->DontReuse(); 2468 } 2469 } 2470 } else if (mContentLength == int64_t(-1)) { 2471 LOG(("waiting for the server to close the connection.\n")); 2472 } 2473 } 2474 } 2475 2476 mDidContentStart = true; 2477 return NS_OK; 2478 } 2479 2480 // called on the socket thread 2481 nsresult nsHttpTransaction::HandleContent(char* buf, uint32_t count, 2482 uint32_t* contentRead, 2483 uint32_t* contentRemaining) { 2484 nsresult rv; 2485 2486 LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count)); 2487 2488 *contentRead = 0; 2489 *contentRemaining = 0; 2490 2491 MOZ_ASSERT(mConnection); 2492 2493 if (!mDidContentStart) { 2494 rv = HandleContentStart(); 2495 if (NS_FAILED(rv)) return rv; 2496 // Do not write content to the pipe if we haven't started streaming yet 2497 if (!mDidContentStart) return NS_OK; 2498 } 2499 2500 if (mChunkedDecoder) { 2501 // give the buf over to the chunked decoder so it can reformat the 2502 // data and tell us how much is really there. 2503 rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, 2504 contentRemaining); 2505 if (NS_FAILED(rv)) return rv; 2506 } else if (mContentLength >= int64_t(0)) { 2507 // HTTP/1.0 servers have been known to send erroneous Content-Length 2508 // headers. So, unless the connection is persistent, we must make 2509 // allowances for a possibly invalid Content-Length header. Thus, if 2510 // NOT persistent, we simply accept everything in |buf|. 2511 if (mConnection->IsPersistent() || mPreserveStream || 2512 mHttpVersion >= HttpVersion::v1_1) { 2513 int64_t remaining = mContentLength - mContentRead; 2514 *contentRead = uint32_t(std::min<int64_t>(count, remaining)); 2515 *contentRemaining = count - *contentRead; 2516 } else { 2517 *contentRead = count; 2518 // mContentLength might need to be increased... 2519 int64_t position = mContentRead + int64_t(count); 2520 if (position > mContentLength) { 2521 mContentLength = position; 2522 // mResponseHead->SetContentLength(mContentLength); 2523 } 2524 } 2525 } else { 2526 // when we are just waiting for the server to close the connection... 2527 // (no explicit content-length given) 2528 *contentRead = count; 2529 } 2530 2531 if (*contentRead) { 2532 // update count of content bytes read and report progress... 2533 mContentRead += *contentRead; 2534 } 2535 2536 LOG1( 2537 ("nsHttpTransaction::HandleContent [this=%p count=%u read=%u " 2538 "mContentRead=%" PRId64 " mContentLength=%" PRId64 "]\n", 2539 this, count, *contentRead, mContentRead, mContentLength)); 2540 2541 // check for end-of-file 2542 if ((mContentRead == mContentLength) || 2543 (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) { 2544 { 2545 MutexAutoLock lock(mLock); 2546 if (mChunkedDecoder) { 2547 mForTakeResponseTrailers = mChunkedDecoder->TakeTrailers(); 2548 } 2549 2550 // the transaction is done with a complete response. 2551 mTransactionDone = true; 2552 mResponseIsComplete = true; 2553 } 2554 ReleaseBlockingTransaction(); 2555 2556 SetResponseEnd(TimeStamp::Now()); 2557 2558 // report the entire response has arrived 2559 gHttpHandler->ObserveHttpActivityWithArgs( 2560 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 2561 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(), 2562 static_cast<uint64_t>(mContentRead), ""_ns); 2563 } 2564 2565 return NS_OK; 2566 } 2567 2568 nsresult nsHttpTransaction::ProcessData(char* buf, uint32_t count, 2569 uint32_t* countRead) { 2570 nsresult rv; 2571 2572 LOG1(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count)); 2573 2574 *countRead = 0; 2575 2576 // we may not have read all of the headers yet... 2577 if (!mHaveAllHeaders) { 2578 uint32_t bytesConsumed = 0; 2579 2580 do { 2581 uint32_t localBytesConsumed = 0; 2582 char* localBuf = buf + bytesConsumed; 2583 uint32_t localCount = count - bytesConsumed; 2584 2585 rv = ParseHead(localBuf, localCount, &localBytesConsumed); 2586 if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT) return rv; 2587 bytesConsumed += localBytesConsumed; 2588 } while (rv == NS_ERROR_NET_INTERRUPT); 2589 2590 mCurrentHttpResponseHeaderSize += bytesConsumed; 2591 if (mCurrentHttpResponseHeaderSize > 2592 StaticPrefs::network_http_max_response_header_size()) { 2593 LOG(("nsHttpTransaction %p The response header exceeds the limit.\n", 2594 this)); 2595 return NS_ERROR_FILE_TOO_BIG; 2596 } 2597 count -= bytesConsumed; 2598 2599 // if buf has some content in it, shift bytes to top of buf. 2600 if (count && bytesConsumed) memmove(buf, buf + bytesConsumed, count); 2601 2602 if (mResponseHead && mHaveAllHeaders) { 2603 if (mConnection->IsProxyConnectInProgress()) { 2604 ReportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER); 2605 } else if (!mReportedResponseHeader) { 2606 mReportedResponseHeader = true; 2607 ReportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER); 2608 } 2609 } 2610 } 2611 2612 // even though count may be 0, we still want to call HandleContent 2613 // so it can complete the transaction if this is a "no-content" response. 2614 if (mHaveAllHeaders) { 2615 uint32_t countRemaining = 0; 2616 // 2617 // buf layout: 2618 // 2619 // +--------------------------------------+----------------+-----+ 2620 // | countRead | countRemaining | | 2621 // +--------------------------------------+----------------+-----+ 2622 // 2623 // count : bytes read from the socket 2624 // countRead : bytes corresponding to this transaction 2625 // countRemaining : bytes corresponding to next transaction on conn 2626 // 2627 // NOTE: 2628 // count > countRead + countRemaining <==> chunked transfer encoding 2629 // 2630 rv = HandleContent(buf, count, countRead, &countRemaining); 2631 if (NS_FAILED(rv)) return rv; 2632 // we may have read more than our share, in which case we must give 2633 // the excess bytes back to the connection 2634 if (mResponseIsComplete && countRemaining && 2635 (mConnection->Version() != HttpVersion::v3_0)) { 2636 MOZ_ASSERT(mConnection); 2637 rv = mConnection->PushBack(buf + *countRead, countRemaining); 2638 NS_ENSURE_SUCCESS(rv, rv); 2639 } 2640 2641 if (!mContentDecodingCheck && mResponseHead) { 2642 mContentDecoding = mResponseHead->HasHeader(nsHttp::Content_Encoding); 2643 mContentDecodingCheck = true; 2644 } 2645 } 2646 2647 return NS_OK; 2648 } 2649 2650 // Used to report response header data to devtools 2651 void nsHttpTransaction::ReportResponseHeader(uint32_t aSubType) { 2652 nsAutoCString completeResponseHeaders; 2653 mResponseHead->Flatten(completeResponseHeaders, false); 2654 completeResponseHeaders.AppendLiteral("\r\n"); 2655 gHttpHandler->ObserveHttpActivityWithArgs( 2656 HttpActivityArgs(mChannelId), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, 2657 aSubType, PR_Now(), 0, completeResponseHeaders); 2658 }; 2659 2660 // Called when the transaction marked for blocking is associated with a 2661 // connection (i.e. added to a new h1 conn, an idle http connection, etc..) It 2662 // is safe to call this multiple times with it only having an effect once. 2663 void nsHttpTransaction::DispatchedAsBlocking() { 2664 if (mDispatchedAsBlocking) return; 2665 2666 LOG(("nsHttpTransaction %p dispatched as blocking\n", this)); 2667 2668 if (!mRequestContext) return; 2669 2670 LOG( 2671 ("nsHttpTransaction adding blocking transaction %p from " 2672 "request context %p\n", 2673 this, mRequestContext.get())); 2674 2675 mRequestContext->AddBlockingTransaction(); 2676 mDispatchedAsBlocking = true; 2677 } 2678 2679 void nsHttpTransaction::RemoveDispatchedAsBlocking() { 2680 if (!mRequestContext || !mDispatchedAsBlocking) { 2681 LOG(("nsHttpTransaction::RemoveDispatchedAsBlocking this=%p not blocking", 2682 this)); 2683 return; 2684 } 2685 2686 uint32_t blockers = 0; 2687 nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers); 2688 2689 LOG( 2690 ("nsHttpTransaction removing blocking transaction %p from " 2691 "request context %p. %d blockers remain.\n", 2692 this, mRequestContext.get(), blockers)); 2693 2694 if (NS_SUCCEEDED(rv) && !blockers) { 2695 LOG( 2696 ("nsHttpTransaction %p triggering release of blocked channels " 2697 " with request context=%p\n", 2698 this, mRequestContext.get())); 2699 rv = gHttpHandler->ConnMgr()->ProcessPendingQ(); 2700 if (NS_FAILED(rv)) { 2701 LOG( 2702 ("nsHttpTransaction::RemoveDispatchedAsBlocking\n" 2703 " failed to process pending queue\n")); 2704 } 2705 } 2706 2707 mDispatchedAsBlocking = false; 2708 } 2709 2710 void nsHttpTransaction::ReleaseBlockingTransaction() { 2711 RemoveDispatchedAsBlocking(); 2712 LOG( 2713 ("nsHttpTransaction %p request context set to null " 2714 "in ReleaseBlockingTransaction() - was %p\n", 2715 this, mRequestContext.get())); 2716 mRequestContext = nullptr; 2717 } 2718 2719 void nsHttpTransaction::DisableSpdy() { 2720 mCaps |= NS_HTTP_DISALLOW_SPDY; 2721 if (mConnInfo) { 2722 // This is our clone of the connection info, not the persistent one that 2723 // is owned by the connection manager, so we're safe to change this here 2724 mConnInfo->SetNoSpdy(true); 2725 } 2726 } 2727 2728 void nsHttpTransaction::DisableHttp2ForProxy() { 2729 mCaps |= NS_HTTP_DISALLOW_HTTP2_PROXY; 2730 } 2731 2732 void nsHttpTransaction::DisableHttp3(bool aAllowRetryHTTPSRR) { 2733 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2734 2735 // mOrigConnInfo is an indicator that HTTPS RR is used, so don't mess up the 2736 // connection info. 2737 // When HTTPS RR is used, PrepareConnInfoForRetry() could select other h3 2738 // record to connect. 2739 if (mOrigConnInfo) { 2740 LOG( 2741 ("nsHttpTransaction::DisableHttp3 this=%p mOrigConnInfo=%s " 2742 "aAllowRetryHTTPSRR=%d", 2743 this, mOrigConnInfo->HashKey().get(), aAllowRetryHTTPSRR)); 2744 if (!aAllowRetryHTTPSRR) { 2745 mCaps |= NS_HTTP_DISALLOW_HTTP3; 2746 } 2747 return; 2748 } 2749 2750 mCaps |= NS_HTTP_DISALLOW_HTTP3; 2751 2752 MOZ_ASSERT(mConnInfo); 2753 if (mConnInfo) { 2754 // After CloneAsDirectRoute(), http3 will be disabled. 2755 RefPtr<nsHttpConnectionInfo> connInfo; 2756 mConnInfo->CloneAsDirectRoute(getter_AddRefs(connInfo)); 2757 RemoveAlternateServiceUsedHeader(mRequestHead); 2758 MOZ_ASSERT(!connInfo->IsHttp3()); 2759 mConnInfo.swap(connInfo); 2760 } 2761 } 2762 2763 void nsHttpTransaction::CheckForStickyAuthScheme() { 2764 LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p", this)); 2765 2766 MOZ_ASSERT(mHaveAllHeaders); 2767 MOZ_ASSERT(mResponseHead); 2768 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 2769 2770 CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate); 2771 CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate); 2772 } 2773 2774 void nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom const& header) { 2775 if (mCaps & NS_HTTP_STICKY_CONNECTION) { 2776 LOG((" already sticky")); 2777 return; 2778 } 2779 2780 nsAutoCString auth; 2781 if (NS_FAILED(mResponseHead->GetHeader(header, auth))) { 2782 return; 2783 } 2784 2785 if (IsStickyAuthSchemeAt(auth)) { 2786 LOG((" connection made sticky")); 2787 // This is enough to make this transaction keep it's current connection, 2788 // prevents the connection from being released back to the pool. 2789 mCaps |= NS_HTTP_STICKY_CONNECTION; 2790 } 2791 } 2792 2793 bool nsHttpTransaction::IsStickyAuthSchemeAt(nsACString const& auth) { 2794 Tokenizer p(auth); 2795 nsAutoCString schema; 2796 while (p.ReadWord(schema)) { 2797 ToLowerCase(schema); 2798 2799 // using a new instance because of thread safety of auth modules refcnt 2800 nsCOMPtr<nsIHttpAuthenticator> authenticator; 2801 if (schema.EqualsLiteral("negotiate")) { 2802 #ifdef MOZ_AUTH_EXTENSION 2803 authenticator = new nsHttpNegotiateAuth(); 2804 #endif 2805 } else if (schema.EqualsLiteral("basic")) { 2806 authenticator = new nsHttpBasicAuth(); 2807 } else if (schema.EqualsLiteral("digest")) { 2808 authenticator = new nsHttpDigestAuth(); 2809 } else if (schema.EqualsLiteral("ntlm")) { 2810 authenticator = new nsHttpNTLMAuth(); 2811 } else if (schema.EqualsLiteral("mock_auth") && 2812 PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) { 2813 authenticator = new MockHttpAuth(); 2814 } 2815 if (authenticator) { 2816 uint32_t flags; 2817 nsresult rv = authenticator->GetAuthFlags(&flags); 2818 if (NS_SUCCEEDED(rv) && 2819 (flags & nsIHttpAuthenticator::CONNECTION_BASED)) { 2820 return true; 2821 } 2822 } 2823 2824 // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader 2825 p.SkipUntil(Tokenizer::Token::NewLine()); 2826 p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE); 2827 } 2828 2829 return false; 2830 } 2831 2832 TimingStruct nsHttpTransaction::Timings() { 2833 mozilla::MutexAutoLock lock(mLock); 2834 TimingStruct timings = mTimings; 2835 return timings; 2836 } 2837 2838 void nsHttpTransaction::BootstrapTimings(TimingStruct times) { 2839 mozilla::MutexAutoLock lock(mLock); 2840 mTimings = times; 2841 } 2842 2843 void nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp, 2844 bool onlyIfNull) { 2845 mozilla::MutexAutoLock lock(mLock); 2846 if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) { 2847 return; // We only set the timestamp if it was previously null 2848 } 2849 mTimings.domainLookupStart = timeStamp; 2850 } 2851 2852 void nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp, 2853 bool onlyIfNull) { 2854 mozilla::MutexAutoLock lock(mLock); 2855 if (onlyIfNull && !mTimings.domainLookupEnd.IsNull()) { 2856 return; // We only set the timestamp if it was previously null 2857 } 2858 mTimings.domainLookupEnd = timeStamp; 2859 } 2860 2861 void nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp, 2862 bool onlyIfNull) { 2863 mozilla::MutexAutoLock lock(mLock); 2864 if (onlyIfNull && !mTimings.connectStart.IsNull()) { 2865 return; // We only set the timestamp if it was previously null 2866 } 2867 mTimings.connectStart = timeStamp; 2868 } 2869 2870 void nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp, 2871 bool onlyIfNull) { 2872 mozilla::MutexAutoLock lock(mLock); 2873 if (onlyIfNull && !mTimings.connectEnd.IsNull()) { 2874 return; // We only set the timestamp if it was previously null 2875 } 2876 mTimings.connectEnd = timeStamp; 2877 } 2878 2879 void nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp, 2880 bool onlyIfNull) { 2881 mozilla::MutexAutoLock lock(mLock); 2882 if (onlyIfNull && !mTimings.requestStart.IsNull()) { 2883 return; // We only set the timestamp if it was previously null 2884 } 2885 mTimings.requestStart = timeStamp; 2886 } 2887 2888 void nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp, 2889 bool onlyIfNull) { 2890 mozilla::MutexAutoLock lock(mLock); 2891 if (onlyIfNull && !mTimings.responseStart.IsNull()) { 2892 return; // We only set the timestamp if it was previously null 2893 } 2894 mTimings.responseStart = timeStamp; 2895 } 2896 2897 void nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp, 2898 bool onlyIfNull) { 2899 mozilla::MutexAutoLock lock(mLock); 2900 if (onlyIfNull && !mTimings.responseEnd.IsNull()) { 2901 return; // We only set the timestamp if it was previously null 2902 } 2903 mTimings.responseEnd = timeStamp; 2904 } 2905 2906 mozilla::TimeStamp nsHttpTransaction::GetDomainLookupStart() { 2907 mozilla::MutexAutoLock lock(mLock); 2908 return mTimings.domainLookupStart; 2909 } 2910 2911 mozilla::TimeStamp nsHttpTransaction::GetDomainLookupEnd() { 2912 mozilla::MutexAutoLock lock(mLock); 2913 return mTimings.domainLookupEnd; 2914 } 2915 2916 mozilla::TimeStamp nsHttpTransaction::GetConnectStart() { 2917 mozilla::MutexAutoLock lock(mLock); 2918 return mTimings.connectStart; 2919 } 2920 2921 mozilla::TimeStamp nsHttpTransaction::GetTcpConnectEnd() { 2922 mozilla::MutexAutoLock lock(mLock); 2923 return mTimings.tcpConnectEnd; 2924 } 2925 2926 mozilla::TimeStamp nsHttpTransaction::GetSecureConnectionStart() { 2927 mozilla::MutexAutoLock lock(mLock); 2928 return mTimings.secureConnectionStart; 2929 } 2930 2931 mozilla::TimeStamp nsHttpTransaction::GetConnectEnd() { 2932 mozilla::MutexAutoLock lock(mLock); 2933 return mTimings.connectEnd; 2934 } 2935 2936 mozilla::TimeStamp nsHttpTransaction::GetRequestStart() { 2937 mozilla::MutexAutoLock lock(mLock); 2938 return mTimings.requestStart; 2939 } 2940 2941 mozilla::TimeStamp nsHttpTransaction::GetResponseStart() { 2942 mozilla::MutexAutoLock lock(mLock); 2943 return mTimings.responseStart; 2944 } 2945 2946 mozilla::TimeStamp nsHttpTransaction::GetResponseEnd() { 2947 mozilla::MutexAutoLock lock(mLock); 2948 return mTimings.responseEnd; 2949 } 2950 2951 //----------------------------------------------------------------------------- 2952 // nsHttpTransaction deletion event 2953 //----------------------------------------------------------------------------- 2954 2955 class DeleteHttpTransaction : public Runnable { 2956 public: 2957 explicit DeleteHttpTransaction(nsHttpTransaction* trans) 2958 : Runnable("net::DeleteHttpTransaction"), mTrans(trans) {} 2959 2960 NS_IMETHOD Run() override { 2961 delete mTrans; 2962 return NS_OK; 2963 } 2964 2965 private: 2966 nsHttpTransaction* mTrans; 2967 }; 2968 2969 void nsHttpTransaction::DeleteSelfOnConsumerThread() { 2970 LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this)); 2971 2972 if (mConnection && OnSocketThread()) { 2973 mConnection = nullptr; 2974 } 2975 2976 bool val; 2977 if (!mConsumerTarget || 2978 (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) { 2979 delete this; 2980 } else { 2981 LOG(("proxying delete to consumer thread...\n")); 2982 nsCOMPtr<nsIRunnable> event = new DeleteHttpTransaction(this); 2983 if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL))) { 2984 NS_WARNING("failed to dispatch nsHttpDeleteTransaction event"); 2985 } 2986 } 2987 } 2988 2989 bool nsHttpTransaction::TryToRunPacedRequest() { 2990 if (mSubmittedRatePacing) return mPassedRatePacing; 2991 2992 mSubmittedRatePacing = true; 2993 mSynchronousRatePaceRequest = true; 2994 (void)gHttpHandler->SubmitPacedRequest(this, 2995 getter_AddRefs(mTokenBucketCancel)); 2996 mSynchronousRatePaceRequest = false; 2997 return mPassedRatePacing; 2998 } 2999 3000 void nsHttpTransaction::OnTokenBucketAdmitted() { 3001 mPassedRatePacing = true; 3002 mTokenBucketCancel = nullptr; 3003 3004 if (!mSynchronousRatePaceRequest) { 3005 nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); 3006 if (NS_FAILED(rv)) { 3007 LOG( 3008 ("nsHttpTransaction::OnTokenBucketAdmitted\n" 3009 " failed to process pending queue\n")); 3010 } 3011 } 3012 } 3013 3014 void nsHttpTransaction::CancelPacing(nsresult reason) { 3015 if (mTokenBucketCancel) { 3016 mTokenBucketCancel->Cancel(reason); 3017 mTokenBucketCancel = nullptr; 3018 } 3019 } 3020 3021 //----------------------------------------------------------------------------- 3022 // nsHttpTransaction::nsISupports 3023 //----------------------------------------------------------------------------- 3024 3025 NS_IMPL_ADDREF(nsHttpTransaction) 3026 3027 NS_IMETHODIMP_(MozExternalRefCountType) 3028 nsHttpTransaction::Release() { 3029 nsrefcnt count; 3030 MOZ_ASSERT(0 != mRefCnt, "dup release"); 3031 count = --mRefCnt; 3032 NS_LOG_RELEASE(this, count, "nsHttpTransaction"); 3033 if (0 == count) { 3034 mRefCnt = 1; /* stablize */ 3035 // it is essential that the transaction be destroyed on the consumer 3036 // thread (we could be holding the last reference to our consumer). 3037 DeleteSelfOnConsumerThread(); 3038 return 0; 3039 } 3040 return count; 3041 } 3042 3043 NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, nsIInputStreamCallback, 3044 nsIOutputStreamCallback, nsITimerCallback, nsINamed) 3045 3046 //----------------------------------------------------------------------------- 3047 // nsHttpTransaction::nsIInputStreamCallback 3048 //----------------------------------------------------------------------------- 3049 3050 // called on the socket thread 3051 NS_IMETHODIMP 3052 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream* out) { 3053 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3054 if (mConnection) { 3055 mConnection->TransactionHasDataToWrite(this); 3056 nsresult rv = mConnection->ResumeSend(); 3057 if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed"); 3058 } 3059 return NS_OK; 3060 } 3061 3062 //----------------------------------------------------------------------------- 3063 // nsHttpTransaction::nsIOutputStreamCallback 3064 //----------------------------------------------------------------------------- 3065 3066 // called on the socket thread 3067 NS_IMETHODIMP 3068 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream* out) { 3069 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3070 mWaitingOnPipeOut = false; 3071 if (mConnection) { 3072 mConnection->TransactionHasDataToRecv(this); 3073 nsresult rv = mConnection->ResumeRecv(); 3074 if (NS_FAILED(rv) && rv != NS_BASE_STREAM_WOULD_BLOCK) { 3075 NS_ERROR("ResumeRecv failed"); 3076 } 3077 } 3078 return NS_OK; 3079 } 3080 3081 void nsHttpTransaction::GetNetworkAddresses( 3082 NetAddr& self, NetAddr& peer, bool& aResolvedByTRR, 3083 nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason, 3084 bool& aEchConfigUsed) { 3085 MutexAutoLock lock(mLock); 3086 self = mSelfAddr; 3087 peer = mPeerAddr; 3088 aResolvedByTRR = mResolvedByTRR; 3089 aEffectiveTRRMode = mEffectiveTRRMode; 3090 aSkipReason = mTRRSkipReason; 3091 aEchConfigUsed = mEchConfigUsed; 3092 } 3093 3094 bool nsHttpTransaction::Do0RTT() { 3095 LOG(("nsHttpTransaction::Do0RTT")); 3096 mEarlyDataWasAvailable = true; 3097 if (mRequestHead->IsSafeMethod() && !mDoNotTryEarlyData && 3098 (!mConnection || !mConnection->IsProxyConnectInProgress())) { 3099 m0RTTInProgress = true; 3100 } 3101 return m0RTTInProgress; 3102 } 3103 3104 nsresult nsHttpTransaction::Finish0RTT(bool aRestart, 3105 bool aAlpnChanged /* ignored */) { 3106 LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart, 3107 aAlpnChanged)); 3108 MOZ_ASSERT(m0RTTInProgress); 3109 m0RTTInProgress = false; 3110 3111 MaybeCancelFallbackTimer(); 3112 3113 if (!aRestart && (mEarlyDataDisposition == EARLY_SENT)) { 3114 // note that if this is invoked by a 3 param version of finish0rtt this 3115 // disposition might be reverted 3116 mEarlyDataDisposition = EARLY_ACCEPTED; 3117 } 3118 if (aRestart) { 3119 // Not to use 0RTT when this transaction is restarted next time. 3120 mDoNotTryEarlyData = true; 3121 3122 // Reset request headers to be sent again. 3123 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream); 3124 if (seekable) { 3125 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); 3126 } else { 3127 return NS_ERROR_FAILURE; 3128 } 3129 } else if (!mConnected) { 3130 // this is code that was skipped in ::ReadSegments while in 0RTT 3131 mConnected = true; 3132 MaybeRefreshSecurityInfo(); 3133 } 3134 return NS_OK; 3135 } 3136 3137 void nsHttpTransaction::Refused0RTT() { 3138 LOG(("nsHttpTransaction::Refused0RTT %p\n", this)); 3139 if (mEarlyDataDisposition == EARLY_ACCEPTED) { 3140 mEarlyDataDisposition = EARLY_SENT; // undo accepted state 3141 } 3142 } 3143 3144 void nsHttpTransaction::SetHttpTrailers(nsCString& aTrailers) { 3145 LOG(("nsHttpTransaction::SetHttpTrailers %p", this)); 3146 LOG(("[\n %s\n]", aTrailers.BeginReading())); 3147 3148 // Introduce a local variable to minimize the critical section. 3149 UniquePtr<nsHttpHeaderArray> httpTrailers(new nsHttpHeaderArray()); 3150 // Given it's usually null, use double-check locking for performance. 3151 if (mForTakeResponseTrailers) { 3152 MutexAutoLock lock(mLock); 3153 if (mForTakeResponseTrailers) { 3154 // Copy the trailer. |TakeResponseTrailers| gets the original trailer 3155 // until the final swap. 3156 *httpTrailers = *mForTakeResponseTrailers; 3157 } 3158 } 3159 3160 int32_t cur = 0; 3161 int32_t len = aTrailers.Length(); 3162 while (cur < len) { 3163 int32_t newline = aTrailers.FindCharInSet("\n", cur); 3164 if (newline == -1) { 3165 newline = len; 3166 } 3167 3168 int32_t end = 3169 (newline && aTrailers[newline - 1] == '\r') ? newline - 1 : newline; 3170 nsDependentCSubstring line(aTrailers, cur, end); 3171 nsHttpAtom hdr; 3172 nsAutoCString hdrNameOriginal; 3173 nsAutoCString val; 3174 if (NS_SUCCEEDED(httpTrailers->ParseHeaderLine(line, &hdr, &hdrNameOriginal, 3175 &val))) { 3176 if (hdr == nsHttp::Server_Timing) { 3177 (void)httpTrailers->SetHeaderFromNet(hdr, hdrNameOriginal, val, true); 3178 } 3179 } 3180 3181 cur = newline + 1; 3182 } 3183 3184 if (httpTrailers->Count() == 0) { 3185 // Didn't find a Server-Timing header, so get rid of this. 3186 httpTrailers = nullptr; 3187 } 3188 3189 MutexAutoLock lock(mLock); 3190 std::swap(mForTakeResponseTrailers, httpTrailers); 3191 } 3192 3193 bool nsHttpTransaction::IsWebsocketUpgrade() { 3194 if (mRequestHead) { 3195 nsAutoCString upgradeHeader; 3196 if (NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Upgrade, upgradeHeader)) && 3197 upgradeHeader.LowerCaseEqualsLiteral("websocket")) { 3198 return true; 3199 } 3200 } 3201 return false; 3202 } 3203 3204 void nsHttpTransaction::OnProxyConnectComplete(int32_t aResponseCode) { 3205 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3206 MOZ_ASSERT(mConnInfo->UsingConnect()); 3207 3208 LOG(("nsHttpTransaction::OnProxyConnectComplete %p aResponseCode=%d", this, 3209 aResponseCode)); 3210 3211 mProxyConnectResponseCode = aResponseCode; 3212 3213 if (mConnInfo->IsHttp3() && mProxyConnectResponseCode == 200 && 3214 !mHttp3TunnelFallbackTimerCreated) { 3215 mHttp3TunnelFallbackTimerCreated = true; 3216 CreateAndStartTimer(mHttp3TunnelFallbackTimer, this, 3217 StaticPrefs::network_http_http3_inner_fallback_delay()); 3218 } 3219 } 3220 3221 int32_t nsHttpTransaction::GetProxyConnectResponseCode() { 3222 return mProxyConnectResponseCode; 3223 } 3224 3225 void nsHttpTransaction::SetFlat407Headers(const nsACString& aHeaders) { 3226 MOZ_ASSERT(mProxyConnectResponseCode == 407); 3227 MOZ_ASSERT(!mResponseHead); 3228 3229 LOG(("nsHttpTransaction::SetFlat407Headers %p", this)); 3230 mFlat407Headers = aHeaders; 3231 } 3232 3233 void nsHttpTransaction::NotifyTransactionObserver(nsresult reason) { 3234 MOZ_ASSERT(OnSocketThread()); 3235 3236 if (!mTransactionObserver) { 3237 return; 3238 } 3239 3240 bool versionOk = false; 3241 bool authOk = false; 3242 3243 LOG(("nsHttpTransaction::NotifyTransactionObserver %p reason %" PRIx32 3244 " conn %p\n", 3245 this, static_cast<uint32_t>(reason), mConnection.get())); 3246 3247 if (mConnection) { 3248 HttpVersion version = mConnection->Version(); 3249 versionOk = (((reason == NS_BASE_STREAM_CLOSED) || (reason == NS_OK)) && 3250 ((mConnection->Version() == HttpVersion::v2_0) || 3251 (mConnection->Version() == HttpVersion::v3_0))); 3252 3253 nsCOMPtr<nsITLSSocketControl> socketControl; 3254 mConnection->GetTLSSocketControl(getter_AddRefs(socketControl)); 3255 LOG( 3256 ("nsHttpTransaction::NotifyTransactionObserver" 3257 " version %u socketControl %p\n", 3258 static_cast<int32_t>(version), socketControl.get())); 3259 if (socketControl) { 3260 authOk = !socketControl->GetFailedVerification(); 3261 } 3262 } 3263 3264 TransactionObserverResult result; 3265 result.versionOk() = versionOk; 3266 result.authOk() = authOk; 3267 result.closeReason() = reason; 3268 3269 TransactionObserverFunc obs = nullptr; 3270 std::swap(obs, mTransactionObserver); 3271 obs(std::move(result)); 3272 } 3273 3274 void nsHttpTransaction::UpdateConnectionInfo(nsHttpConnectionInfo* aConnInfo) { 3275 MOZ_ASSERT(aConnInfo); 3276 3277 if (mActivated) { 3278 MOZ_ASSERT(false, "Should not update conn info after activated"); 3279 return; 3280 } 3281 3282 mOrigConnInfo = mConnInfo->Clone(); 3283 mConnInfo = aConnInfo; 3284 } 3285 3286 nsresult nsHttpTransaction::OnHTTPSRRAvailable( 3287 nsIDNSHTTPSSVCRecord* aHTTPSSVCRecord, 3288 nsISVCBRecord* aHighestPriorityRecord, const nsACString& aCname) { 3289 LOG(("nsHttpTransaction::OnHTTPSRRAvailable [this=%p] mActivated=%d", this, 3290 mActivated)); 3291 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3292 3293 { 3294 MutexAutoLock lock(mLock); 3295 MakeDontWaitHTTPSRR(); 3296 mDNSRequest = nullptr; 3297 } 3298 3299 if (!mResolver) { 3300 LOG(("The transaction is not interested in HTTPS record anymore.")); 3301 return NS_OK; 3302 } 3303 3304 RefPtr<nsHttpTransaction> deleteProtector(this); 3305 3306 uint32_t receivedStage = HTTPSSVC_NO_USABLE_RECORD; 3307 // Make sure we set the correct value to |mHTTPSSVCReceivedStage|, since we 3308 // also use this value to indicate whether HTTPS RR is used or not. 3309 auto updateHTTPSSVCReceivedStage = MakeScopeExit([&] { 3310 mHTTPSSVCReceivedStage = receivedStage; 3311 3312 // In the case that an HTTPS RR is unavailable, we should call 3313 // ProcessPendingQ to make sure this transition to be processed soon. 3314 if (!mHTTPSSVCRecord) { 3315 gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); 3316 } 3317 }); 3318 3319 nsCOMPtr<nsIDNSHTTPSSVCRecord> record = aHTTPSSVCRecord; 3320 if (!record) { 3321 return NS_ERROR_FAILURE; 3322 } 3323 3324 bool hasIPAddress = false; 3325 (void)record->GetHasIPAddresses(&hasIPAddress); 3326 3327 if (mActivated) { 3328 receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2 3329 : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_2; 3330 return NS_OK; 3331 } 3332 3333 receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_1 3334 : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_1; 3335 3336 nsCOMPtr<nsISVCBRecord> svcbRecord = aHighestPriorityRecord; 3337 if (!svcbRecord) { 3338 LOG((" no usable record!")); 3339 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID); 3340 bool allRecordsExcluded = false; 3341 (void)record->GetAllRecordsExcluded(&allRecordsExcluded); 3342 glean::http::dns_httpssvc_connection_failed_reason.AccumulateSingleSample( 3343 allRecordsExcluded ? HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED 3344 : HTTPSSVC_CONNECTION_NO_USABLE_RECORD); 3345 if (allRecordsExcluded && 3346 StaticPrefs::network_dns_httpssvc_reset_exclustion_list() && dns) { 3347 (void)dns->ResetExcludedSVCDomainName(mConnInfo->GetOrigin()); 3348 if (NS_FAILED(record->GetServiceModeRecordWithCname( 3349 mCaps & NS_HTTP_DISALLOW_SPDY, mCaps & NS_HTTP_DISALLOW_HTTP3, 3350 aCname, getter_AddRefs(svcbRecord)))) { 3351 return NS_ERROR_FAILURE; 3352 } 3353 } else { 3354 return NS_ERROR_FAILURE; 3355 } 3356 } 3357 3358 // Remember this RR set. In the case that the connection establishment failed, 3359 // we will use other records to retry. 3360 mHTTPSSVCRecord = record; 3361 mCname = aCname; 3362 LOG(("has cname:%s", mCname.get())); 3363 3364 RefPtr<nsHttpConnectionInfo> newInfo = 3365 mConnInfo->CloneAndAdoptHTTPSSVCRecord(svcbRecord); 3366 // Don't fallback until we support WebTransport over HTTP/2. 3367 // TODO: implement fallback in bug 1874102. 3368 // Note: We don't support HTTPS RR for proxy connection yet, so disable the 3369 // fallback. 3370 bool needFastFallback = newInfo->IsHttp3() && !newInfo->GetWebTransport() && 3371 !newInfo->IsHttp3ProxyConnection(); 3372 bool foundInPendingQ = gHttpHandler->ConnMgr()->RemoveTransFromConnEntry( 3373 this, mHashKeyOfConnectionEntry); 3374 3375 // Adopt the new connection info, so this transaction will be added into the 3376 // new connection entry. 3377 UpdateConnectionInfo(newInfo); 3378 3379 // If this transaction is sucessfully removed from a connection entry, we call 3380 // ProcessNewTransaction to process it immediately. 3381 // If not, this means that nsHttpTransaction::OnHTTPSRRAvailable happens 3382 // before ProcessNewTransaction and this transaction will be processed later. 3383 if (foundInPendingQ) { 3384 if (NS_FAILED(gHttpHandler->ConnMgr()->ProcessNewTransaction(this))) { 3385 LOG(("Failed to process this transaction.")); 3386 return NS_ERROR_FAILURE; 3387 } 3388 } 3389 3390 // In case we already have mHttp3BackupTimer, cancel it. 3391 MaybeCancelFallbackTimer(); 3392 3393 if (needFastFallback) { 3394 CreateAndStartTimer( 3395 mFastFallbackTimer, this, 3396 StaticPrefs::network_dns_httpssvc_http3_fast_fallback_timeout()); 3397 } 3398 3399 // Prefetch the A/AAAA records of the target name. 3400 nsAutoCString targetName; 3401 (void)svcbRecord->GetName(targetName); 3402 if (mResolver) { 3403 mResolver->PrefetchAddrRecord(targetName, mCaps & NS_HTTP_REFRESH_DNS); 3404 } 3405 3406 // echConfig is used, so initialize the retry counters to 0. 3407 if (!mConnInfo->GetEchConfig().IsEmpty()) { 3408 mEchRetryCounterMap.InsertOrUpdate(TRANSACTION_ECH_RETRY_WITH_ECH_COUNT, 0); 3409 mEchRetryCounterMap.InsertOrUpdate(TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT, 3410 0); 3411 mEchRetryCounterMap.InsertOrUpdate(TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT, 3412 0); 3413 mEchRetryCounterMap.InsertOrUpdate(TRANSACTION_ECH_RETRY_OTHERS_COUNT, 0); 3414 } 3415 3416 return NS_OK; 3417 } 3418 3419 uint32_t nsHttpTransaction::HTTPSSVCReceivedStage() { 3420 return mHTTPSSVCReceivedStage; 3421 } 3422 3423 void nsHttpTransaction::MaybeCancelFallbackTimer() { 3424 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread"); 3425 3426 if (mFastFallbackTimer) { 3427 mFastFallbackTimer->Cancel(); 3428 mFastFallbackTimer = nullptr; 3429 } 3430 3431 if (mHttp3BackupTimer) { 3432 mHttp3BackupTimer->Cancel(); 3433 mHttp3BackupTimer = nullptr; 3434 } 3435 3436 if (mHttp3TunnelFallbackTimer) { 3437 mHttp3TunnelFallbackTimer->Cancel(); 3438 mHttp3TunnelFallbackTimer = nullptr; 3439 } 3440 } 3441 3442 void nsHttpTransaction::OnBackupConnectionReady(bool aTriggeredByHTTPSRR) { 3443 LOG( 3444 ("nsHttpTransaction::OnBackupConnectionReady [%p] mConnected=%d " 3445 "aTriggeredByHTTPSRR=%d", 3446 this, mConnected, aTriggeredByHTTPSRR)); 3447 if (mConnected || mClosed || mRestarted) { 3448 return; 3449 } 3450 3451 // If HTTPS RR is in play, don't mess up the fallback mechansim of HTTPS RR. 3452 if (!aTriggeredByHTTPSRR && mOrigConnInfo) { 3453 return; 3454 } 3455 3456 if (mConnection) { 3457 if (mConnection->Version() != HttpVersion::v3_0) { 3458 LOG(("Already have non-HTTP/3 conn:%p", mConnection.get())); 3459 return; 3460 } 3461 // The transaction will only be restarted when we already have a connection. 3462 // When there is no connection, this transaction will be moved to another 3463 // connection entry. 3464 SetRestartReason(aTriggeredByHTTPSRR 3465 ? TRANSACTION_RESTART_HTTPS_RR_FAST_FALLBACK 3466 : TRANSACTION_RESTART_HTTP3_FAST_FALLBACK); 3467 } 3468 3469 mCaps |= NS_HTTP_DISALLOW_HTTP3; 3470 3471 // Need to backup the origin conn info, since UpdateConnectionInfo() will be 3472 // called in HandleFallback() and mOrigConnInfo will be 3473 // replaced. 3474 RefPtr<nsHttpConnectionInfo> backup = mOrigConnInfo; 3475 HandleFallback(mBackupConnInfo); 3476 mOrigConnInfo.swap(backup); 3477 3478 RemoveAlternateServiceUsedHeader(mRequestHead); 3479 3480 if (mResolver) { 3481 if (mBackupConnInfo) { 3482 const nsCString& host = mBackupConnInfo->GetRoutedHost().IsEmpty() 3483 ? mBackupConnInfo->GetOrigin() 3484 : mBackupConnInfo->GetRoutedHost(); 3485 mResolver->PrefetchAddrRecord(host, Caps() & NS_HTTP_REFRESH_DNS); 3486 } 3487 3488 if (!aTriggeredByHTTPSRR) { 3489 // We are about to use this backup connection. We shoud not try to use 3490 // HTTPS RR at this point. 3491 mResolver->Close(); 3492 mResolver = nullptr; 3493 } 3494 } 3495 } 3496 3497 static void CreateBackupConnection( 3498 nsHttpConnectionInfo* aBackupConnInfo, nsIInterfaceRequestor* aCallbacks, 3499 uint32_t aCaps, std::function<void(bool)>&& aResultCallback) { 3500 aBackupConnInfo->SetFallbackConnection(true); 3501 RefPtr<SpeculativeTransaction> trans = new FallbackTransaction( 3502 aBackupConnInfo, aCallbacks, aCaps | NS_HTTP_DISALLOW_HTTP3, 3503 std::move(aResultCallback)); 3504 uint32_t limit = 3505 StaticPrefs::network_http_http3_parallel_fallback_conn_limit(); 3506 if (limit) { 3507 trans->SetParallelSpeculativeConnectLimit(limit); 3508 trans->SetIgnoreIdle(true); 3509 } 3510 gHttpHandler->ConnMgr()->DoFallbackConnection(trans, false); 3511 } 3512 3513 void nsHttpTransaction::OnHttp3BackupTimer() { 3514 LOG(("nsHttpTransaction::OnHttp3BackupTimer [%p]", this)); 3515 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3516 MOZ_ASSERT(mConnInfo->IsHttp3() || mConnInfo->IsHttp3ProxyConnection()); 3517 3518 mHttp3BackupTimer = nullptr; 3519 3520 if (mConnInfo->IsHttp3ProxyConnection()) { 3521 mBackupConnInfo = mConnInfo->CreateConnectUDPFallbackConnInfo(); 3522 } else { 3523 mConnInfo->CloneAsDirectRoute(getter_AddRefs(mBackupConnInfo)); 3524 MOZ_ASSERT(!mBackupConnInfo->IsHttp3()); 3525 } 3526 3527 RefPtr<nsHttpTransaction> self = this; 3528 auto callback = [self](bool aSucceded) { 3529 if (aSucceded) { 3530 self->OnBackupConnectionReady(false); 3531 } 3532 }; 3533 3534 nsCOMPtr<nsIInterfaceRequestor> callbacks; 3535 { 3536 MutexAutoLock lock(mLock); 3537 callbacks = mCallbacks; 3538 } 3539 CreateBackupConnection(mBackupConnInfo, callbacks, mCaps, 3540 std::move(callback)); 3541 } 3542 3543 void nsHttpTransaction::OnHttp3TunnelFallbackTimer() { 3544 LOG(("nsHttpTransaction::OnHttp3TunnelFallbackTimer [%p]", this)); 3545 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3546 3547 mHttp3TunnelFallbackTimer = nullptr; 3548 3549 // Don't disturb the HTTPS RR fallback mechanism. 3550 if (mOrigConnInfo) { 3551 return; 3552 } 3553 3554 DisableHttp3(false); 3555 // Setting this flag since DisableHttp3 is already called. 3556 mDontRetryWithDirectRoute = true; 3557 if (mConnection) { 3558 mConnection->CloseTransaction(this, NS_ERROR_NET_RESET); 3559 } 3560 } 3561 3562 void nsHttpTransaction::OnFastFallbackTimer() { 3563 MOZ_ASSERT(OnSocketThread(), "not on socket thread"); 3564 LOG(("nsHttpTransaction::OnFastFallbackTimer [%p] mConnected=%d", this, 3565 mConnected)); 3566 3567 mFastFallbackTimer = nullptr; 3568 3569 MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo); 3570 if (!mHTTPSSVCRecord || !mOrigConnInfo) { 3571 return; 3572 } 3573 3574 bool echConfigUsed = nsHttpHandler::EchConfigEnabled(mConnInfo->IsHttp3()) && 3575 !mConnInfo->GetEchConfig().IsEmpty(); 3576 mBackupConnInfo = PrepareFastFallbackConnInfo(echConfigUsed); 3577 if (!mBackupConnInfo) { 3578 return; 3579 } 3580 3581 MOZ_ASSERT(!mBackupConnInfo->IsHttp3()); 3582 3583 RefPtr<nsHttpTransaction> self = this; 3584 auto callback = [self](bool aSucceded) { 3585 if (!aSucceded) { 3586 return; 3587 } 3588 3589 self->mFastFallbackTriggered = true; 3590 self->OnBackupConnectionReady(true); 3591 }; 3592 3593 nsCOMPtr<nsIInterfaceRequestor> callbacks; 3594 { 3595 MutexAutoLock lock(mLock); 3596 callbacks = mCallbacks; 3597 } 3598 CreateBackupConnection(mBackupConnInfo, callbacks, mCaps, 3599 std::move(callback)); 3600 } 3601 3602 void nsHttpTransaction::HandleFallback( 3603 nsHttpConnectionInfo* aFallbackConnInfo) { 3604 if (mConnection) { 3605 // Close the transaction with NS_ERROR_NET_RESET, since we know doing this 3606 // will make transaction to be restarted. 3607 mConnection->CloseTransaction(this, NS_ERROR_NET_RESET); 3608 return; 3609 } 3610 3611 if (!aFallbackConnInfo) { 3612 // Nothing to do here. 3613 return; 3614 } 3615 3616 LOG(("nsHttpTransaction %p HandleFallback to connInfo[%s]", this, 3617 aFallbackConnInfo->HashKey().get())); 3618 3619 bool foundInPendingQ = gHttpHandler->ConnMgr()->RemoveTransFromConnEntry( 3620 this, mHashKeyOfConnectionEntry); 3621 if (!foundInPendingQ) { 3622 MOZ_ASSERT(false, "transaction not in entry"); 3623 return; 3624 } 3625 3626 // rewind streams in case we already wrote out the request 3627 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream); 3628 if (seekable) { 3629 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); 3630 } 3631 3632 UpdateConnectionInfo(aFallbackConnInfo); 3633 (void)gHttpHandler->ConnMgr()->ProcessNewTransaction(this); 3634 } 3635 3636 NS_IMETHODIMP 3637 nsHttpTransaction::Notify(nsITimer* aTimer) { 3638 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread"); 3639 3640 if (!gHttpHandler || !gHttpHandler->ConnMgr()) { 3641 return NS_OK; 3642 } 3643 3644 if (aTimer == mFastFallbackTimer) { 3645 OnFastFallbackTimer(); 3646 } else if (aTimer == mHttp3BackupTimer) { 3647 OnHttp3BackupTimer(); 3648 } else if (aTimer == mHttp3TunnelFallbackTimer) { 3649 OnHttp3TunnelFallbackTimer(); 3650 } 3651 3652 return NS_OK; 3653 } 3654 3655 NS_IMETHODIMP 3656 nsHttpTransaction::GetName(nsACString& aName) { 3657 aName.AssignLiteral("nsHttpTransaction"); 3658 return NS_OK; 3659 } 3660 3661 bool nsHttpTransaction::GetSupportsHTTP3() { return mSupportsHTTP3; } 3662 3663 void nsHttpTransaction::CollectTelemetryForUploads() { 3664 if ((mRequestSize < TELEMETRY_REQUEST_SIZE_1M) || 3665 mTimings.requestStart.IsNull() || mTimings.responseStart.IsNull()) { 3666 return; 3667 } 3668 3669 nsAutoCString protocolVersion(nsHttp::GetProtocolVersion(mHttpVersion)); 3670 TimeDuration sendTime = mTimings.responseStart - mTimings.requestStart; 3671 double megabits = static_cast<double>(mRequestSize) * 8.0 / 1000000.0; 3672 uint32_t mpbs = static_cast<uint32_t>(megabits / sendTime.ToSeconds()); 3673 3674 if (mRequestSize <= TELEMETRY_REQUEST_SIZE_10M) { 3675 if (mHttpVersion == HttpVersion::v3_0) { 3676 glean::networking::http_3_upload_throughput_1_10.AccumulateSingleSample( 3677 mpbs); 3678 } 3679 return; 3680 } 3681 3682 switch (mHttpVersion) { 3683 case HttpVersion::v1_0: 3684 case HttpVersion::v1_1: 3685 glean::networking::http_1_upload_throughput.AccumulateSingleSample(mpbs); 3686 if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) { 3687 glean::networking::http_1_upload_throughput_10_50 3688 .AccumulateSingleSample(mpbs); 3689 } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) { 3690 glean::networking::http_1_upload_throughput_50_100 3691 .AccumulateSingleSample(mpbs); 3692 } else { 3693 glean::networking::http_1_upload_throughput_100.AccumulateSingleSample( 3694 mpbs); 3695 } 3696 break; 3697 case HttpVersion::v2_0: 3698 glean::networking::http_2_upload_throughput.AccumulateSingleSample(mpbs); 3699 if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) { 3700 glean::networking::http_2_upload_throughput_10_50 3701 .AccumulateSingleSample(mpbs); 3702 } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) { 3703 glean::networking::http_2_upload_throughput_50_100 3704 .AccumulateSingleSample(mpbs); 3705 } else { 3706 glean::networking::http_2_upload_throughput_100.AccumulateSingleSample( 3707 mpbs); 3708 } 3709 break; 3710 case HttpVersion::v3_0: 3711 glean::networking::http_3_upload_throughput.AccumulateSingleSample(mpbs); 3712 if (mRequestSize <= TELEMETRY_REQUEST_SIZE_50M) { 3713 glean::networking::http_3_upload_throughput_10_50 3714 .AccumulateSingleSample(mpbs); 3715 } else if (mRequestSize <= TELEMETRY_REQUEST_SIZE_100M) { 3716 glean::networking::http_3_upload_throughput_50_100 3717 .AccumulateSingleSample(mpbs); 3718 } else { 3719 glean::networking::http_3_upload_throughput_100.AccumulateSingleSample( 3720 mpbs); 3721 } 3722 break; 3723 default: 3724 break; 3725 } 3726 } 3727 3728 void nsHttpTransaction::GetHashKeyOfConnectionEntry(nsACString& aResult) { 3729 MutexAutoLock lock(mLock); 3730 aResult.Assign(mHashKeyOfConnectionEntry); 3731 } 3732 3733 void nsHttpTransaction::SetIsForWebTransport(bool aIsForWebTransport) { 3734 mIsForWebTransport = aIsForWebTransport; 3735 } 3736 3737 void nsHttpTransaction::RemoveConnection() { 3738 MutexAutoLock lock(mLock); 3739 mConnection = nullptr; 3740 } 3741 3742 nsILoadInfo::IPAddressSpace nsHttpTransaction::GetTargetIPAddressSpace() { 3743 nsILoadInfo::IPAddressSpace retVal; 3744 { 3745 MutexAutoLock lock(mLock); 3746 retVal = mTargetIpAddressSpace; 3747 } 3748 3749 return retVal; 3750 } 3751 3752 bool nsHttpTransaction::AllowedToConnectToIpAddressSpace( 3753 nsILoadInfo::IPAddressSpace aTargetIpAddressSpace) { 3754 // skip checks if LNA feature is disabled 3755 3756 if (!StaticPrefs::network_lna_enabled()) { 3757 return true; 3758 } 3759 3760 if (mConnection) { 3761 // update peer address required for LNA telemetry and console logging 3762 MutexAutoLock lock(mLock); 3763 mConnection->GetPeerAddr(&mPeerAddr); 3764 } 3765 // Skip LNA checks if domain is in skip list 3766 if (mConnInfo && gIOService && 3767 gIOService->ShouldSkipDomainForLNA(mConnInfo->GetOrigin())) { 3768 return true; 3769 } 3770 3771 // Skip LNA checks entirely for WebSocket connections if websocket LNA is 3772 // disabled 3773 if (!StaticPrefs::network_lna_websocket_enabled() && IsWebsocketUpgrade()) { 3774 return true; // Allow all WebSocket connections 3775 } 3776 3777 // store targetIpAddress space which is required later by nsHttpChannel for 3778 // permission prompts 3779 { 3780 mozilla::MutexAutoLock lock(mLock); 3781 if (mTargetIpAddressSpace == nsILoadInfo::Unknown) { 3782 mTargetIpAddressSpace = aTargetIpAddressSpace; 3783 } 3784 } 3785 3786 // Deny access to a request moving to a more private addresspsace. 3787 // Specifically, 3788 // 1. local host resources cannot be accessed from Private or Public 3789 // network 3790 // 2. private network resources cannot be accessed from Public 3791 // network 3792 // Refer 3793 // https://wicg.github.io/private-network-access/#private-network-request-heading 3794 // for private network access 3795 // XXX add link to LNA spec once it is published 3796 3797 // Skip LNA checks for private network to localhost if preference is enabled 3798 if (StaticPrefs::network_lna_local_network_to_localhost_skip_checks() && 3799 mParentIPAddressSpace == nsILoadInfo::IPAddressSpace::Private && 3800 aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Local) { 3801 return true; // Allow private->localhost access 3802 } 3803 3804 if (mozilla::net::IsLocalOrPrivateNetworkAccess(mParentIPAddressSpace, 3805 aTargetIpAddressSpace)) { 3806 if (aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Local && 3807 mLnaPermissionStatus.mLocalHostPermission == LNAPermission::Denied) { 3808 return false; 3809 } 3810 3811 if (aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Private && 3812 mLnaPermissionStatus.mLocalNetworkPermission == LNAPermission::Denied) { 3813 return false; 3814 } 3815 3816 if ((StaticPrefs::network_lna_blocking() || 3817 StaticPrefs::network_lna_block_trackers()) && 3818 ((aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Local && 3819 mLnaPermissionStatus.mLocalHostPermission == 3820 LNAPermission::Pending) || 3821 (aTargetIpAddressSpace == nsILoadInfo::IPAddressSpace::Private && 3822 mLnaPermissionStatus.mLocalNetworkPermission == 3823 LNAPermission::Pending))) { 3824 // If LNA prompts are enabled or tracker blocking is enabled we disallow 3825 // requests 3826 return false; 3827 } 3828 } 3829 3830 return true; 3831 } 3832 3833 } // namespace mozilla::net