tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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