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