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/. */
7 // HttpLog.h should generally be included first
8 #include "nsHttpTransaction.h"
14 #include "HTTPSRecordResolver.h"
15 #include "NSSErrorsService.h"
16 #include "base/basictypes.h"
17 #include "mozilla/Components.h"
18 #include "mozilla/glean/GleanMetrics.h"
19 #include "mozilla/net/SSLTokensCache.h"
20 #include "mozilla/ScopeExit.h"
21 #include "mozilla/Tokenizer.h"
22 #include "mozilla/StaticPrefs_network.h"
23 #include "MockHttpAuth.h"
25 #include "nsComponentManagerUtils.h" // do_CreateInstance
26 #include "nsHttpBasicAuth.h"
27 #include "nsHttpChannel.h"
28 #include "nsHttpChunkedDecoder.h"
29 #include "nsHttpDigestAuth.h"
30 #include "nsHttpHandler.h"
31 #include "nsHttpNTLMAuth.h"
32 #ifdef MOZ_AUTH_EXTENSION
33 # include "nsHttpNegotiateAuth.h"
35 #include "nsHttpRequestHead.h"
36 #include "nsHttpResponseHead.h"
37 #include "nsICancelable.h"
38 #include "nsIClassOfService.h"
39 #include "nsIDNSByTypeRecord.h"
40 #include "nsIDNSRecord.h"
41 #include "nsIDNSService.h"
42 #include "nsIEventTarget.h"
43 #include "nsIHttpActivityObserver.h"
44 #include "nsIHttpAuthenticator.h"
45 #include "nsIInputStream.h"
46 #include "nsIInputStreamPriority.h"
47 #include "nsIMultiplexInputStream.h"
48 #include "nsIOService.h"
50 #include "nsIRequestContext.h"
51 #include "nsISeekableStream.h"
52 #include "nsITLSSocketControl.h"
53 #include "nsIThrottledInputChannel.h"
54 #include "nsITransport.h"
55 #include "nsMultiplexInputStream.h"
57 #include "nsNetUtil.h"
58 #include "nsQueryObject.h"
59 #include "nsSocketTransportService2.h"
60 #include "nsStringStream.h"
61 #include "nsTransportUtils.h"
63 #include "SpeculativeTransaction.h"
65 //-----------------------------------------------------------------------------
67 // Place a limit on how much non-compliant HTTP can be skipped while
68 // looking for a response header
69 #define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
71 using namespace mozilla::net
;
73 namespace mozilla::net
{
75 //-----------------------------------------------------------------------------
76 // nsHttpTransaction <public>
77 //-----------------------------------------------------------------------------
79 nsHttpTransaction::nsHttpTransaction() {
80 LOG(("Creating nsHttpTransaction @%p\n", this));
83 memset(&mSelfAddr
, 0, sizeof(NetAddr
));
84 memset(&mPeerAddr
, 0, sizeof(NetAddr
));
86 mSelfAddr
.raw
.family
= PR_AF_UNSPEC
;
87 mPeerAddr
.raw
.family
= PR_AF_UNSPEC
;
89 mThroughCaptivePortal
= gHttpHandler
->GetThroughCaptivePortal();
92 void nsHttpTransaction::ResumeReading() {
93 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
95 if (!mReadingStopped
) {
99 LOG(("nsHttpTransaction::ResumeReading %p", this));
101 mReadingStopped
= false;
103 // This with either reengage the limit when still throttled in WriteSegments
104 // or simply reset to allow unlimeted reading again.
105 mThrottlingReadAllowance
= THROTTLE_NO_LIMIT
;
108 mConnection
->TransactionHasDataToRecv(this);
109 nsresult rv
= mConnection
->ResumeRecv();
111 LOG((" resume failed with rv=%" PRIx32
, static_cast<uint32_t>(rv
)));
116 bool nsHttpTransaction::EligibleForThrottling() const {
117 return (mClassOfServiceFlags
&
118 (nsIClassOfService::Throttleable
| nsIClassOfService::DontThrottle
|
119 nsIClassOfService::Leader
| nsIClassOfService::Unblocked
)) ==
120 nsIClassOfService::Throttleable
;
123 void nsHttpTransaction::SetClassOfService(ClassOfService cos
) {
128 bool wasThrottling
= EligibleForThrottling();
129 mClassOfServiceFlags
= cos
.Flags();
130 mClassOfServiceIncremental
= cos
.Incremental();
131 bool isThrottling
= EligibleForThrottling();
133 if (mConnection
&& wasThrottling
!= isThrottling
) {
134 // Do nothing until we are actually activated. For now
135 // only remember the throttle flag. Call to UpdateActiveTransaction
136 // would add this transaction to the list too early.
137 gHttpHandler
->ConnMgr()->UpdateActiveTransaction(this);
139 if (mReadingStopped
&& !isThrottling
) {
145 class ReleaseOnSocketThread final
: public mozilla::Runnable
{
147 explicit ReleaseOnSocketThread(nsTArray
<nsCOMPtr
<nsISupports
>>&& aDoomed
)
148 : Runnable("ReleaseOnSocketThread"), mDoomed(std::move(aDoomed
)) {}
157 nsCOMPtr
<nsIEventTarget
> sts
=
158 do_GetService("@mozilla.org/network/socket-transport-service;1");
159 Unused
<< sts
->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL
);
163 virtual ~ReleaseOnSocketThread() = default;
165 nsTArray
<nsCOMPtr
<nsISupports
>> mDoomed
;
168 nsHttpTransaction::~nsHttpTransaction() {
169 LOG(("Destroying nsHttpTransaction @%p\n", this));
172 mPushedStream
->OnPushFailed();
173 mPushedStream
= nullptr;
176 if (mTokenBucketCancel
) {
177 mTokenBucketCancel
->Cancel(NS_ERROR_ABORT
);
178 mTokenBucketCancel
= nullptr;
181 // Force the callbacks and connection to be released right now
182 mCallbacks
= nullptr;
184 mEarlyHintObserver
= nullptr;
186 delete mResponseHead
;
187 delete mChunkedDecoder
;
188 ReleaseBlockingTransaction();
190 nsTArray
<nsCOMPtr
<nsISupports
>> arrayToRelease
;
192 arrayToRelease
.AppendElement(mConnection
.forget());
195 if (!arrayToRelease
.IsEmpty()) {
196 RefPtr
<ReleaseOnSocketThread
> r
=
197 new ReleaseOnSocketThread(std::move(arrayToRelease
));
202 nsresult
nsHttpTransaction::Init(
203 uint32_t caps
, nsHttpConnectionInfo
* cinfo
, nsHttpRequestHead
* requestHead
,
204 nsIInputStream
* requestBody
, uint64_t requestContentLength
,
205 bool requestBodyHasHeaders
, nsIEventTarget
* target
,
206 nsIInterfaceRequestor
* callbacks
, nsITransportEventSink
* eventsink
,
207 uint64_t browserId
, HttpTrafficCategory trafficCategory
,
208 nsIRequestContext
* requestContext
, ClassOfService classOfService
,
209 uint32_t initialRwin
, bool responseTimeoutEnabled
, uint64_t channelId
,
210 TransactionObserverFunc
&& transactionObserver
,
211 OnPushCallback
&& aOnPushCallback
,
212 HttpTransactionShell
* transWithPushedStream
, uint32_t aPushedStreamId
) {
215 LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps
));
218 MOZ_ASSERT(requestHead
);
220 MOZ_ASSERT(target
->IsOnCurrentThread());
222 mChannelId
= channelId
;
223 mTransactionObserver
= std::move(transactionObserver
);
224 mOnPushCallback
= std::move(aOnPushCallback
);
225 mBrowserId
= browserId
;
227 mTrafficCategory
= trafficCategory
;
229 LOG1(("nsHttpTransaction %p SetRequestContext %p\n", this, requestContext
));
230 mRequestContext
= requestContext
;
232 SetClassOfService(classOfService
);
233 mResponseTimeoutEnabled
= responseTimeoutEnabled
;
234 mInitialRwin
= initialRwin
;
236 // create transport event sink proxy. it coalesces consecutive
237 // events of the same status type.
238 rv
= net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink
), eventsink
,
241 if (NS_FAILED(rv
)) return rv
;
244 mCallbacks
= callbacks
;
245 mConsumerTarget
= target
;
247 // eventsink is a nsHttpChannel when we expect "103 Early Hints" responses.
248 // We expect it in document requests and not e.g. in TRR requests.
249 mEarlyHintObserver
= do_QueryInterface(eventsink
);
251 if (requestHead
->IsHead()) {
255 // grab a weak reference to the request head
256 mRequestHead
= requestHead
;
258 mReqHeaderBuf
= nsHttp::ConvertRequestHeadToString(
259 *requestHead
, !!requestBody
, requestBodyHasHeaders
,
260 cinfo
->UsingConnect());
262 if (LOG1_ENABLED()) {
263 LOG1(("http request [\n"));
264 LogHeaders(mReqHeaderBuf
.get());
268 // report the request header
269 if (gHttpHandler
->HttpActivityDistributorActivated()) {
270 nsCString
requestBuf(mReqHeaderBuf
);
271 NS_DispatchToMainThread(NS_NewRunnableFunction(
272 "ObserveHttpActivityWithArgs", [channelId(mChannelId
), requestBuf
]() {
276 gHttpHandler
->ObserveHttpActivityWithArgs(
277 HttpActivityArgs(channelId
),
278 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
,
279 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER
, PR_Now(), 0, requestBuf
);
283 // Create a string stream for the request header buf (the stream holds
284 // a non-owning reference to the request header data, so we MUST keep
285 // mReqHeaderBuf around).
286 nsCOMPtr
<nsIInputStream
> headers
;
287 rv
= NS_NewByteInputStream(getter_AddRefs(headers
), mReqHeaderBuf
,
288 NS_ASSIGNMENT_DEPEND
);
289 if (NS_FAILED(rv
)) return rv
;
291 mHasRequestBody
= !!requestBody
;
292 if (mHasRequestBody
&& !requestContentLength
) {
293 mHasRequestBody
= false;
296 requestContentLength
+= mReqHeaderBuf
.Length();
298 if (mHasRequestBody
) {
299 // wrap the headers and request body in a multiplexed input stream.
300 nsCOMPtr
<nsIMultiplexInputStream
> multi
;
301 rv
= nsMultiplexInputStreamConstructor(NS_GET_IID(nsIMultiplexInputStream
),
302 getter_AddRefs(multi
));
303 if (NS_FAILED(rv
)) return rv
;
305 rv
= multi
->AppendStream(headers
);
306 if (NS_FAILED(rv
)) return rv
;
308 rv
= multi
->AppendStream(requestBody
);
309 if (NS_FAILED(rv
)) return rv
;
311 // wrap the multiplexed input stream with a buffered input stream, so
312 // that we write data in the largest chunks possible. this is actually
313 // necessary to workaround some common server bugs (see bug 137155).
314 nsCOMPtr
<nsIInputStream
> stream(do_QueryInterface(multi
));
315 rv
= NS_NewBufferedInputStream(getter_AddRefs(mRequestStream
),
317 nsIOService::gDefaultSegmentSize
);
318 if (NS_FAILED(rv
)) return rv
;
320 mRequestStream
= headers
;
323 nsCOMPtr
<nsIThrottledInputChannel
> throttled
= do_QueryInterface(eventsink
);
325 nsCOMPtr
<nsIInputChannelThrottleQueue
> queue
;
326 rv
= throttled
->GetThrottleQueue(getter_AddRefs(queue
));
327 // In case of failure, just carry on without throttling.
328 if (NS_SUCCEEDED(rv
) && queue
) {
329 nsCOMPtr
<nsIAsyncInputStream
> wrappedStream
;
330 rv
= queue
->WrapStream(mRequestStream
, getter_AddRefs(wrappedStream
));
331 // Failure to throttle isn't sufficient reason to fail
333 if (NS_SUCCEEDED(rv
)) {
334 MOZ_ASSERT(wrappedStream
!= nullptr);
336 ("nsHttpTransaction::Init %p wrapping input stream using throttle "
339 mRequestStream
= wrappedStream
;
344 // make sure request content-length fits within js MAX_SAFE_INTEGER
345 mRequestSize
= InScriptableRange(requestContentLength
)
346 ? static_cast<int64_t>(requestContentLength
)
349 // create pipe for response stream
350 NS_NewPipe2(getter_AddRefs(mPipeIn
), getter_AddRefs(mPipeOut
), true, true,
351 nsIOService::gDefaultSegmentSize
,
352 nsIOService::gDefaultSegmentCount
);
354 if (transWithPushedStream
&& aPushedStreamId
) {
355 RefPtr
<nsHttpTransaction
> trans
=
356 transWithPushedStream
->AsHttpTransaction();
358 mPushedStream
= trans
->TakePushedStreamById(aPushedStreamId
);
361 bool forceUseHTTPSRR
= StaticPrefs::network_dns_force_use_https_rr();
362 if ((gHttpHandler
->UseHTTPSRRAsAltSvcEnabled() &&
363 !(mCaps
& NS_HTTP_DISALLOW_HTTPS_RR
)) ||
365 nsCOMPtr
<nsIEventTarget
> target
;
366 Unused
<< gHttpHandler
->GetSocketThreadTarget(getter_AddRefs(target
));
368 if (StaticPrefs::network_dns_force_waiting_https_rr() ||
369 StaticPrefs::network_dns_echconfig_enabled() || forceUseHTTPSRR
) {
370 mCaps
|= NS_HTTP_FORCE_WAIT_HTTP_RR
;
373 mResolver
= new HTTPSRecordResolver(this);
374 nsCOMPtr
<nsICancelable
> dnsRequest
;
375 rv
= mResolver
->FetchHTTPSRRInternal(target
, getter_AddRefs(dnsRequest
));
376 if (NS_SUCCEEDED(rv
)) {
377 mHTTPSSVCReceivedStage
= HTTPSSVC_NOT_PRESENT
;
381 MutexAutoLock
lock(mLock
);
382 mDNSRequest
.swap(dnsRequest
);
384 MakeDontWaitHTTPSRR();
390 RefPtr
<nsHttpChannel
> httpChannel
= do_QueryObject(eventsink
);
391 RefPtr
<WebTransportSessionEventListener
> listener
=
392 httpChannel
? httpChannel
->GetWebTransportSessionEventListener()
395 mWebTransportSessionEventListener
= std::move(listener
);
401 static inline void CreateAndStartTimer(nsCOMPtr
<nsITimer
>& aTimer
,
402 nsITimerCallback
* aCallback
,
404 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
411 NS_NewTimerWithCallback(getter_AddRefs(aTimer
), aCallback
, aTimeout
,
412 nsITimer::TYPE_ONE_SHOT
);
415 void nsHttpTransaction::OnPendingQueueInserted(
416 const nsACString
& aConnectionHashKey
) {
417 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
420 MutexAutoLock
lock(mLock
);
421 mHashKeyOfConnectionEntry
.Assign(aConnectionHashKey
);
424 // Don't create mHttp3BackupTimer if HTTPS RR is in play.
425 if (mConnInfo
->IsHttp3() && !mOrigConnInfo
&& !mConnInfo
->GetWebTransport()) {
426 // Backup timer should only be created once.
427 if (!mHttp3BackupTimerCreated
) {
428 CreateAndStartTimer(mHttp3BackupTimer
, this,
429 StaticPrefs::network_http_http3_backup_timer_delay());
430 mHttp3BackupTimerCreated
= true;
435 nsresult
nsHttpTransaction::AsyncRead(nsIStreamListener
* listener
,
437 RefPtr
<nsInputStreamPump
> transactionPump
;
439 nsInputStreamPump::Create(getter_AddRefs(transactionPump
), mPipeIn
);
440 NS_ENSURE_SUCCESS(rv
, rv
);
442 rv
= transactionPump
->AsyncRead(listener
);
443 NS_ENSURE_SUCCESS(rv
, rv
);
445 transactionPump
.forget(pump
);
449 // This method should only be used on the socket thread
450 nsAHttpConnection
* nsHttpTransaction::Connection() {
451 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
452 return mConnection
.get();
455 void nsHttpTransaction::SetH2WSConnRefTaken() {
456 if (!OnSocketThread()) {
457 nsCOMPtr
<nsIRunnable
> event
=
458 NewRunnableMethod("nsHttpTransaction::SetH2WSConnRefTaken", this,
459 &nsHttpTransaction::SetH2WSConnRefTaken
);
460 gSocketTransportService
->Dispatch(event
, NS_DISPATCH_NORMAL
);
465 UniquePtr
<nsHttpResponseHead
> nsHttpTransaction::TakeResponseHead() {
466 MOZ_ASSERT(!mResponseHeadTaken
, "TakeResponseHead called 2x");
468 // Lock TakeResponseHead() against main thread
469 MutexAutoLock
lock(mLock
);
471 mResponseHeadTaken
= true;
473 // Even in OnStartRequest() the headers won't be available if we were
475 if (!mHaveAllHeaders
) {
476 NS_WARNING("response headers not available or incomplete");
480 return WrapUnique(std::exchange(mResponseHead
, nullptr));
483 UniquePtr
<nsHttpHeaderArray
> nsHttpTransaction::TakeResponseTrailers() {
484 MOZ_ASSERT(!mResponseTrailersTaken
, "TakeResponseTrailers called 2x");
486 // Lock TakeResponseTrailers() against main thread
487 MutexAutoLock
lock(mLock
);
489 mResponseTrailersTaken
= true;
490 return std::move(mForTakeResponseTrailers
);
493 void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed
= true; }
495 nsHttpRequestHead
* nsHttpTransaction::RequestHead() { return mRequestHead
; }
497 uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; }
499 nsresult
nsHttpTransaction::TakeSubTransactions(
500 nsTArray
<RefPtr
<nsAHttpTransaction
>>& outTransactions
) {
501 return NS_ERROR_NOT_IMPLEMENTED
;
504 //----------------------------------------------------------------------------
505 // nsHttpTransaction::nsAHttpTransaction
506 //----------------------------------------------------------------------------
508 void nsHttpTransaction::SetConnection(nsAHttpConnection
* conn
) {
510 MutexAutoLock
lock(mLock
);
513 mIsHttp3Used
= mConnection
->Version() == HttpVersion::v3_0
;
518 void nsHttpTransaction::OnActivated() {
519 MOZ_ASSERT(OnSocketThread());
525 if (mTrafficCategory
!= HttpTrafficCategory::eInvalid
) {
526 HttpTrafficAnalyzer
* hta
= gHttpHandler
->GetHttpTrafficAnalyzer();
528 hta
->IncrementHttpTransaction(mTrafficCategory
);
531 mConnection
->SetTrafficCategory(mTrafficCategory
);
535 if (mConnection
&& mRequestHead
&&
536 mConnection
->Version() >= HttpVersion::v2_0
) {
537 // So this is fun. On http/2, we want to send TE: trailers, to be
538 // spec-compliant. So we add it to the request head here. The fun part
539 // is that adding a header to the request head at this point has no
540 // effect on what we send on the wire, as the headers are already
541 // flattened (in Init()) by the time we get here. So the *real* adding
542 // of the header happens in the h2 compression code. We still have to
543 // add the header to the request head here, though, so that devtools can
544 // show that we sent the header. FUN!
545 Unused
<< mRequestHead
->SetHeader(nsHttp::TE
, "trailers"_ns
);
549 gHttpHandler
->ConnMgr()->AddActiveTransaction(this);
552 void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor
** cb
) {
553 MutexAutoLock
lock(mLock
);
554 nsCOMPtr
<nsIInterfaceRequestor
> tmp(mCallbacks
);
558 void nsHttpTransaction::SetSecurityCallbacks(
559 nsIInterfaceRequestor
* aCallbacks
) {
561 MutexAutoLock
lock(mLock
);
562 mCallbacks
= aCallbacks
;
565 if (gSocketTransportService
) {
566 RefPtr
<UpdateSecurityCallbacks
> event
=
567 new UpdateSecurityCallbacks(this, aCallbacks
);
568 gSocketTransportService
->Dispatch(event
, nsIEventTarget::DISPATCH_NORMAL
);
572 void nsHttpTransaction::OnTransportStatus(nsITransport
* transport
,
573 nsresult status
, int64_t progress
) {
574 LOG1(("nsHttpTransaction::OnSocketStatus [this=%p status=%" PRIx32
575 " progress=%" PRId64
"]\n",
576 this, static_cast<uint32_t>(status
), progress
));
578 if (status
== NS_NET_STATUS_CONNECTED_TO
||
579 status
== NS_NET_STATUS_WAITING_FOR
) {
581 MutexAutoLock
lock(mLock
);
582 mConnection
->GetSelfAddr(&mSelfAddr
);
583 mConnection
->GetPeerAddr(&mPeerAddr
);
584 mResolvedByTRR
= mConnection
->ResolvedByTRR();
585 mEffectiveTRRMode
= mConnection
->EffectiveTRRMode();
586 mTRRSkipReason
= mConnection
->TRRSkipReason();
587 mEchConfigUsed
= mConnection
->GetEchConfigUsed();
591 // If the timing is enabled, and we are not using a persistent connection
592 // then the requestStart timestamp will be null, so we mark the timestamps
593 // for domainLookupStart/End and connectStart/End
594 // If we are using a persistent connection they will remain null,
595 // and the correct value will be returned in Performance.
596 if (TimingEnabled() && GetRequestStart().IsNull()) {
597 if (status
== NS_NET_STATUS_RESOLVING_HOST
) {
598 SetDomainLookupStart(TimeStamp::Now(), true);
599 } else if (status
== NS_NET_STATUS_RESOLVED_HOST
) {
600 SetDomainLookupEnd(TimeStamp::Now());
601 } else if (status
== NS_NET_STATUS_CONNECTING_TO
) {
602 SetConnectStart(TimeStamp::Now());
603 } else if (status
== NS_NET_STATUS_CONNECTED_TO
) {
604 TimeStamp tnow
= TimeStamp::Now();
605 SetConnectEnd(tnow
, true);
607 MutexAutoLock
lock(mLock
);
608 mTimings
.tcpConnectEnd
= tnow
;
610 } else if (status
== NS_NET_STATUS_TLS_HANDSHAKE_STARTING
) {
612 MutexAutoLock
lock(mLock
);
613 mTimings
.secureConnectionStart
= TimeStamp::Now();
615 } else if (status
== NS_NET_STATUS_TLS_HANDSHAKE_ENDED
) {
616 SetConnectEnd(TimeStamp::Now(), false);
617 } else if (status
== NS_NET_STATUS_SENDING_TO
) {
618 // Set the timestamp to Now(), only if it null
619 SetRequestStart(TimeStamp::Now(), true);
623 if (!mTransportSink
) return;
625 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
627 // Need to do this before the STATUS_RECEIVING_FROM check below, to make
628 // sure that the activity distributor gets told about all status events.
630 // upon STATUS_WAITING_FOR; report request body sent
631 if ((mHasRequestBody
) && (status
== NS_NET_STATUS_WAITING_FOR
)) {
632 gHttpHandler
->ObserveHttpActivityWithArgs(
633 HttpActivityArgs(mChannelId
), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
,
634 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT
, PR_Now(), 0, ""_ns
);
637 // report the status and progress
638 gHttpHandler
->ObserveHttpActivityWithArgs(
639 HttpActivityArgs(mChannelId
), NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT
,
640 static_cast<uint32_t>(status
), PR_Now(), progress
, ""_ns
);
642 // nsHttpChannel synthesizes progress events in OnDataAvailable
643 if (status
== NS_NET_STATUS_RECEIVING_FROM
) return;
647 if (status
== NS_NET_STATUS_SENDING_TO
) {
648 // suppress progress when only writing request headers
649 if (!mHasRequestBody
) {
651 ("nsHttpTransaction::OnTransportStatus %p "
652 "SENDING_TO without request body\n",
658 // A mRequestStream method is on the stack - wait.
660 ("nsHttpTransaction::OnSocketStatus [this=%p] "
661 "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",
663 // its ok to coalesce several of these into one deferred event
664 mDeferredSendProgress
= true;
668 nsCOMPtr
<nsITellableStream
> tellable
= do_QueryInterface(mRequestStream
);
671 ("nsHttpTransaction::OnTransportStatus %p "
672 "SENDING_TO without tellable request stream\n",
676 "mRequestStream should be tellable as it was wrapped in "
677 "nsBufferedInputStream, which provides the tellable interface even "
678 "when wrapping non-tellable streams.");
682 tellable
->Tell(&prog
);
686 // when uploading, we include the request headers in the progress
688 progressMax
= mRequestSize
;
694 mTransportSink
->OnTransportStatus(transport
, status
, progress
, progressMax
);
697 bool nsHttpTransaction::IsDone() { return mTransactionDone
; }
699 nsresult
nsHttpTransaction::Status() { return mStatus
; }
701 uint32_t nsHttpTransaction::Caps() { return mCaps
& ~mCapsToClear
; }
703 void nsHttpTransaction::SetDNSWasRefreshed() {
704 MOZ_ASSERT(mConsumerTarget
->IsOnCurrentThread(),
705 "SetDNSWasRefreshed on target thread only!");
706 mCapsToClear
|= NS_HTTP_REFRESH_DNS
;
709 nsresult
nsHttpTransaction::ReadRequestSegment(nsIInputStream
* stream
,
710 void* closure
, const char* buf
,
711 uint32_t offset
, uint32_t count
,
712 uint32_t* countRead
) {
713 // For the tracking of sent bytes that we used to do for the networkstats
714 // API, please see bug 1318883 where it was removed.
716 nsHttpTransaction
* trans
= (nsHttpTransaction
*)closure
;
717 nsresult rv
= trans
->mReader
->OnReadSegment(buf
, count
, countRead
);
719 trans
->MaybeRefreshSecurityInfo();
723 LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans
, *countRead
));
725 trans
->mSentData
= true;
729 nsresult
nsHttpTransaction::ReadSegments(nsAHttpSegmentReader
* reader
,
730 uint32_t count
, uint32_t* countRead
) {
731 LOG(("nsHttpTransaction::ReadSegments %p", this));
733 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
735 if (mTransactionDone
) {
740 if (!m0RTTInProgress
) {
741 MaybeCancelFallbackTimer();
744 if (!mConnected
&& !m0RTTInProgress
) {
746 MaybeRefreshSecurityInfo();
749 mDeferredSendProgress
= false;
752 mRequestStream
->ReadSegments(ReadRequestSegment
, this, count
, countRead
);
755 if (m0RTTInProgress
&& (mEarlyDataDisposition
== EARLY_NONE
) &&
756 NS_SUCCEEDED(rv
) && (*countRead
> 0)) {
757 LOG(("mEarlyDataDisposition = EARLY_SENT"));
758 mEarlyDataDisposition
= EARLY_SENT
;
761 if (mDeferredSendProgress
&& mConnection
) {
762 // to avoid using mRequestStream concurrently, OnTransportStatus()
763 // did not report upload status off the ReadSegments() stack from
764 // nsSocketTransport do it now.
765 OnTransportStatus(mConnection
->Transport(), NS_NET_STATUS_SENDING_TO
, 0);
767 mDeferredSendProgress
= false;
770 // The forceRestart condition was dealt with on the stack, but it did not
771 // clear the flag because nsPipe in the readsegment stack clears out
772 // return codes, so we need to use the flag here as a cue to return
774 if (NS_SUCCEEDED(rv
)) {
775 rv
= NS_BINDING_RETARGETED
;
777 mForceRestart
= false;
780 // if read would block then we need to AsyncWait on the request stream.
781 // have callback occur on socket thread so we stay synchronized.
782 if (rv
== NS_BASE_STREAM_WOULD_BLOCK
) {
783 nsCOMPtr
<nsIAsyncInputStream
> asyncIn
= do_QueryInterface(mRequestStream
);
785 nsCOMPtr
<nsIEventTarget
> target
;
786 Unused
<< gHttpHandler
->GetSocketThreadTarget(getter_AddRefs(target
));
788 asyncIn
->AsyncWait(this, 0, 0, target
);
790 NS_ERROR("no socket thread event target");
791 rv
= NS_ERROR_UNEXPECTED
;
799 nsresult
nsHttpTransaction::WritePipeSegment(nsIOutputStream
* stream
,
800 void* closure
, char* buf
,
801 uint32_t offset
, uint32_t count
,
802 uint32_t* countWritten
) {
803 nsHttpTransaction
* trans
= (nsHttpTransaction
*)closure
;
805 if (trans
->mTransactionDone
) return NS_BASE_STREAM_CLOSED
; // stop iterating
807 if (trans
->TimingEnabled()) {
808 // Set the timestamp to Now(), only if it null
809 trans
->SetResponseStart(TimeStamp::Now(), true);
812 // Bug 1153929 - add checks to fix windows crash
813 MOZ_ASSERT(trans
->mWriter
);
814 if (!trans
->mWriter
) {
815 return NS_ERROR_UNEXPECTED
;
820 // OK, now let the caller fill this segment with data.
822 rv
= trans
->mWriter
->OnWriteSegment(buf
, count
, countWritten
);
824 trans
->MaybeRefreshSecurityInfo();
825 return rv
; // caller didn't want to write anything
828 LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans
,
831 MOZ_ASSERT(*countWritten
> 0, "bad writer");
832 trans
->mReceivedData
= true;
833 trans
->mTransferSize
+= *countWritten
;
835 // Let the transaction "play" with the buffer. It is free to modify
836 // the contents of the buffer and/or modify countWritten.
837 // - Bytes in HTTP headers don't count towards countWritten, so the input
838 // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
839 // OnInputStreamReady until all headers have been parsed.
841 rv
= trans
->ProcessData(buf
, *countWritten
, countWritten
);
842 if (NS_FAILED(rv
)) trans
->Close(rv
);
844 return rv
; // failure code only stops WriteSegments; it is not propagated.
847 bool nsHttpTransaction::ShouldThrottle() {
848 if (mClassOfServiceFlags
& nsIClassOfService::DontThrottle
) {
849 // We deliberately don't touch the throttling window here since
850 // DontThrottle requests are expected to be long-standing media
851 // streams and would just unnecessarily block running downloads.
852 // If we want to ballance bandwidth for media responses against
853 // running downloads, we need to find something smarter like
854 // changing the suspend/resume throttling intervals at-runtime.
858 if (!gHttpHandler
->ConnMgr()->ShouldThrottle(this)) {
859 // We are not obligated to throttle
863 if (mContentRead
< 16000) {
864 // Let the first bytes go, it may also well be all the content we get
865 LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64
867 mContentRead
, this));
871 if (!(mClassOfServiceFlags
& nsIClassOfService::Throttleable
) &&
872 gHttpHandler
->ConnMgr()->IsConnEntryUnderPressure(mConnInfo
)) {
873 LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this));
874 // This is expensive to check (two hashtable lookups) but may help
875 // freeing connections for active tab transactions.
876 // Checking this only for transactions that are not explicitly marked
877 // as throttleable because trackers and (specially) downloads should
878 // keep throttling even under pressure.
885 void nsHttpTransaction::DontReuseConnection() {
886 LOG(("nsHttpTransaction::DontReuseConnection %p\n", this));
887 if (!OnSocketThread()) {
888 LOG(("DontReuseConnection %p not on socket thread\n", this));
889 nsCOMPtr
<nsIRunnable
> event
=
890 NewRunnableMethod("nsHttpTransaction::DontReuseConnection", this,
891 &nsHttpTransaction::DontReuseConnection
);
892 gSocketTransportService
->Dispatch(event
, NS_DISPATCH_NORMAL
);
897 mConnection
->DontReuse();
901 nsresult
nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter
* writer
,
903 uint32_t* countWritten
) {
904 LOG(("nsHttpTransaction::WriteSegments %p", this));
906 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
908 if (mTransactionDone
) {
909 return NS_SUCCEEDED(mStatus
) ? NS_BASE_STREAM_CLOSED
: mStatus
;
912 if (ShouldThrottle()) {
913 if (mThrottlingReadAllowance
== THROTTLE_NO_LIMIT
) { // no limit set
914 // V1: ThrottlingReadLimit() returns 0
915 mThrottlingReadAllowance
= gHttpHandler
->ThrottlingReadLimit();
918 mThrottlingReadAllowance
= THROTTLE_NO_LIMIT
; // don't limit
921 if (mThrottlingReadAllowance
== 0) { // depleted
922 if (gHttpHandler
->ConnMgr()->CurrentBrowserId() != mBrowserId
) {
923 nsHttp::NotifyActiveTabLoadOptimization();
926 // Must remember that we have to call ResumeRecv() on our connection when
927 // called back by the conn manager to resume reading.
928 LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
929 mReadingStopped
= true;
930 // This makes the underlaying connection or stream wait for explicit resume.
931 // For h1 this means we stop reading from the socket.
932 // For h2 this means we stop updating recv window for the stream.
933 return NS_BASE_STREAM_WOULD_BLOCK
;
939 return NS_ERROR_UNEXPECTED
;
942 if (mThrottlingReadAllowance
> 0) {
943 LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d",
944 this, count
, mThrottlingReadAllowance
));
945 count
= std::min(count
, static_cast<uint32_t>(mThrottlingReadAllowance
));
949 mPipeOut
->WriteSegments(WritePipeSegment
, this, count
, countWritten
);
954 // The forceRestart condition was dealt with on the stack, but it did not
955 // clear the flag because nsPipe in the writesegment stack clears out
956 // return codes, so we need to use the flag here as a cue to return
958 if (NS_SUCCEEDED(rv
)) {
959 rv
= NS_BINDING_RETARGETED
;
961 mForceRestart
= false;
964 // if pipe would block then we need to AsyncWait on it. have callback
965 // occur on socket thread so we stay synchronized.
966 if (rv
== NS_BASE_STREAM_WOULD_BLOCK
) {
967 nsCOMPtr
<nsIEventTarget
> target
;
968 Unused
<< gHttpHandler
->GetSocketThreadTarget(getter_AddRefs(target
));
970 mPipeOut
->AsyncWait(this, 0, 0, target
);
971 mWaitingOnPipeOut
= true;
973 NS_ERROR("no socket thread event target");
974 rv
= NS_ERROR_UNEXPECTED
;
976 } else if (mThrottlingReadAllowance
> 0 && NS_SUCCEEDED(rv
)) {
977 MOZ_ASSERT(count
>= *countWritten
);
978 mThrottlingReadAllowance
-= *countWritten
;
984 bool nsHttpTransaction::ProxyConnectFailed() { return mProxyConnectFailed
; }
986 bool nsHttpTransaction::DataSentToChildProcess() { return false; }
988 already_AddRefed
<nsITransportSecurityInfo
> nsHttpTransaction::SecurityInfo() {
989 MutexAutoLock
lock(mLock
);
990 return do_AddRef(mSecurityInfo
);
993 bool nsHttpTransaction::HasStickyConnection() const {
994 return mCaps
& NS_HTTP_STICKY_CONNECTION
;
997 bool nsHttpTransaction::ResponseIsComplete() { return mResponseIsComplete
; }
999 int64_t nsHttpTransaction::GetTransferSize() { return mTransferSize
; }
1001 int64_t nsHttpTransaction::GetRequestSize() { return mRequestSize
; }
1003 bool nsHttpTransaction::IsHttp3Used() { return mIsHttp3Used
; }
1005 bool nsHttpTransaction::Http2Disabled() const {
1006 return mCaps
& NS_HTTP_DISALLOW_SPDY
;
1009 bool nsHttpTransaction::Http3Disabled() const {
1010 return mCaps
& NS_HTTP_DISALLOW_HTTP3
;
1013 already_AddRefed
<nsHttpConnectionInfo
> nsHttpTransaction::GetConnInfo() const {
1014 RefPtr
<nsHttpConnectionInfo
> connInfo
= mConnInfo
->Clone();
1015 return connInfo
.forget();
1018 already_AddRefed
<Http2PushedStreamWrapper
>
1019 nsHttpTransaction::TakePushedStreamById(uint32_t aStreamId
) {
1020 MOZ_ASSERT(mConsumerTarget
->IsOnCurrentThread());
1021 MOZ_ASSERT(aStreamId
);
1023 auto entry
= mIDToStreamMap
.Lookup(aStreamId
);
1025 RefPtr
<Http2PushedStreamWrapper
> stream
= entry
.Data();
1027 return stream
.forget();
1033 void nsHttpTransaction::OnPush(Http2PushedStreamWrapper
* aStream
) {
1034 LOG(("nsHttpTransaction::OnPush %p aStream=%p", this, aStream
));
1035 MOZ_ASSERT(aStream
);
1036 MOZ_ASSERT(mOnPushCallback
);
1037 MOZ_ASSERT(mConsumerTarget
);
1039 RefPtr
<Http2PushedStreamWrapper
> stream
= aStream
;
1040 if (!mConsumerTarget
->IsOnCurrentThread()) {
1041 RefPtr
<nsHttpTransaction
> self
= this;
1042 if (NS_FAILED(mConsumerTarget
->Dispatch(
1043 NS_NewRunnableFunction("nsHttpTransaction::OnPush",
1044 [self
, stream
]() { self
->OnPush(stream
); }),
1045 NS_DISPATCH_NORMAL
))) {
1046 stream
->OnPushFailed();
1051 mIDToStreamMap
.WithEntryHandle(stream
->StreamID(), [&](auto&& entry
) {
1053 entry
.OrInsert(stream
);
1056 if (NS_FAILED(mOnPushCallback(stream
->StreamID(), stream
->GetResourceUrl(),
1057 stream
->GetRequestString(), this))) {
1058 stream
->OnPushFailed();
1059 mIDToStreamMap
.Remove(stream
->StreamID());
1063 nsHttpTransaction
* nsHttpTransaction::AsHttpTransaction() { return this; }
1065 HttpTransactionParent
* nsHttpTransaction::AsHttpTransactionParent() {
1069 nsHttpTransaction::HTTPSSVC_CONNECTION_FAILED_REASON
1070 nsHttpTransaction::ErrorCodeToFailedReason(nsresult aErrorCode
) {
1071 HTTPSSVC_CONNECTION_FAILED_REASON reason
= HTTPSSVC_CONNECTION_OTHERS
;
1072 switch (aErrorCode
) {
1073 case NS_ERROR_UNKNOWN_HOST
:
1074 reason
= HTTPSSVC_CONNECTION_UNKNOWN_HOST
;
1076 case NS_ERROR_CONNECTION_REFUSED
:
1077 reason
= HTTPSSVC_CONNECTION_UNREACHABLE
;
1081 reason
= HTTPSSVC_CONNECTION_421_RECEIVED
;
1082 } else if (NS_ERROR_GET_MODULE(aErrorCode
) == NS_ERROR_MODULE_SECURITY
) {
1083 reason
= HTTPSSVC_CONNECTION_SECURITY_ERROR
;
1090 bool nsHttpTransaction::PrepareSVCBRecordsForRetry(
1091 const nsACString
& aFailedDomainName
, const nsACString
& aFailedAlpn
,
1092 bool& aAllRecordsHaveEchConfig
) {
1093 MOZ_ASSERT(mRecordsForRetry
.IsEmpty());
1094 if (!mHTTPSSVCRecord
) {
1098 // If we already failed to connect with h3, don't select records that supports
1100 bool noHttp3
= mCaps
& NS_HTTP_DISALLOW_HTTP3
;
1103 nsTArray
<RefPtr
<nsISVCBRecord
>> records
;
1104 Unused
<< mHTTPSSVCRecord
->GetAllRecordsWithEchConfig(
1105 mCaps
& NS_HTTP_DISALLOW_SPDY
, noHttp3
, &aAllRecordsHaveEchConfig
,
1108 // Note that it's possible that we can't get any usable record here. For
1109 // example, when http3 connection is failed, we won't select records with
1112 // If not all records have echConfig, we'll directly fallback to the origin
1114 if (!aAllRecordsHaveEchConfig
) {
1118 // Take the records behind the failed one and put them into mRecordsForRetry.
1119 for (const auto& record
: records
) {
1121 record
->GetName(name
);
1123 nsresult rv
= record
->GetSelectedAlpn(alpn
);
1125 if (name
== aFailedDomainName
) {
1126 // If the record has no alpn or the alpn is already tried, we skip this
1128 if (NS_FAILED(rv
) || alpn
== aFailedAlpn
) {
1133 mRecordsForRetry
.InsertElementAt(0, record
);
1136 // Set mHTTPSSVCRecord to null to avoid this function being executed twice.
1137 mHTTPSSVCRecord
= nullptr;
1138 return !mRecordsForRetry
.IsEmpty();
1141 already_AddRefed
<nsHttpConnectionInfo
>
1142 nsHttpTransaction::PrepareFastFallbackConnInfo(bool aEchConfigUsed
) {
1143 MOZ_ASSERT(mHTTPSSVCRecord
&& mOrigConnInfo
);
1145 RefPtr
<nsHttpConnectionInfo
> fallbackConnInfo
;
1146 nsCOMPtr
<nsISVCBRecord
> fastFallbackRecord
;
1147 Unused
<< mHTTPSSVCRecord
->GetServiceModeRecord(
1148 mCaps
& NS_HTTP_DISALLOW_SPDY
, true, getter_AddRefs(fastFallbackRecord
));
1150 if (fastFallbackRecord
&& aEchConfigUsed
) {
1151 nsAutoCString echConfig
;
1152 Unused
<< fastFallbackRecord
->GetEchConfig(echConfig
);
1153 if (echConfig
.IsEmpty()) {
1154 fastFallbackRecord
= nullptr;
1158 if (!fastFallbackRecord
) {
1159 if (aEchConfigUsed
) {
1161 ("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record "
1167 if (mOrigConnInfo
->IsHttp3()) {
1168 mOrigConnInfo
->CloneAsDirectRoute(getter_AddRefs(fallbackConnInfo
));
1170 fallbackConnInfo
= mOrigConnInfo
;
1172 return fallbackConnInfo
.forget();
1176 mOrigConnInfo
->CloneAndAdoptHTTPSSVCRecord(fastFallbackRecord
);
1177 return fallbackConnInfo
.forget();
1180 void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason
) {
1181 LOG(("nsHttpTransaction::PrepareConnInfoForRetry [this=%p reason=%" PRIx32
1183 this, static_cast<uint32_t>(aReason
)));
1184 RefPtr
<nsHttpConnectionInfo
> failedConnInfo
= mConnInfo
->Clone();
1185 mConnInfo
= nullptr;
1186 bool echConfigUsed
=
1187 gHttpHandler
->EchConfigEnabled(failedConnInfo
->IsHttp3()) &&
1188 !failedConnInfo
->GetEchConfig().IsEmpty();
1190 if (mFastFallbackTriggered
) {
1191 mFastFallbackTriggered
= false;
1192 MOZ_ASSERT(mBackupConnInfo
);
1193 mConnInfo
.swap(mBackupConnInfo
);
1197 auto useOrigConnInfoToRetry
= [&]() {
1198 mOrigConnInfo
.swap(mConnInfo
);
1199 if (mConnInfo
->IsHttp3() &&
1200 ((mCaps
& NS_HTTP_DISALLOW_HTTP3
) ||
1201 gHttpHandler
->IsHttp3Excluded(mConnInfo
->GetRoutedHost().IsEmpty()
1202 ? mConnInfo
->GetOrigin()
1203 : mConnInfo
->GetRoutedHost()))) {
1204 RefPtr
<nsHttpConnectionInfo
> ci
;
1205 mConnInfo
->CloneAsDirectRoute(getter_AddRefs(ci
));
1210 if (!echConfigUsed
) {
1211 LOG((" echConfig is not used, fallback to origin conn info"));
1212 useOrigConnInfoToRetry();
1216 Telemetry::HistogramID id
= Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT
;
1217 auto updateCount
= MakeScopeExit([&] {
1218 auto entry
= mEchRetryCounterMap
.Lookup(id
);
1219 MOZ_ASSERT(entry
, "table not initialized");
1225 if (aReason
== psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH
)) {
1226 LOG((" Got SSL_ERROR_ECH_RETRY_WITHOUT_ECH, use empty echConfig to retry"));
1227 failedConnInfo
->SetEchConfig(EmptyCString());
1228 failedConnInfo
.swap(mConnInfo
);
1229 id
= Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT
;
1233 if (aReason
== psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH
)) {
1234 LOG((" Got SSL_ERROR_ECH_RETRY_WITH_ECH, use retry echConfig"));
1235 MOZ_ASSERT(mConnection
);
1237 nsCOMPtr
<nsITLSSocketControl
> socketControl
;
1239 mConnection
->GetTLSSocketControl(getter_AddRefs(socketControl
));
1241 MOZ_ASSERT(socketControl
);
1243 nsAutoCString retryEchConfig
;
1244 if (socketControl
&&
1245 NS_SUCCEEDED(socketControl
->GetRetryEchConfig(retryEchConfig
))) {
1246 MOZ_ASSERT(!retryEchConfig
.IsEmpty());
1248 failedConnInfo
->SetEchConfig(retryEchConfig
);
1249 failedConnInfo
.swap(mConnInfo
);
1251 id
= Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT
;
1255 // Note that we retry the connection not only for SSL_ERROR_ECH_FAILED, but
1256 // also for all failure cases.
1257 if (aReason
== psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED
) ||
1258 NS_FAILED(aReason
)) {
1259 LOG((" Got SSL_ERROR_ECH_FAILED, try other records"));
1260 if (aReason
== psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_FAILED
)) {
1261 id
= Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT
;
1263 if (mRecordsForRetry
.IsEmpty()) {
1264 if (mHTTPSSVCRecord
) {
1265 bool allRecordsHaveEchConfig
= true;
1266 if (!PrepareSVCBRecordsForRetry(failedConnInfo
->GetRoutedHost(),
1267 failedConnInfo
->GetNPNToken(),
1268 allRecordsHaveEchConfig
)) {
1270 (" Can't find other records with echConfig, "
1271 "allRecordsHaveEchConfig=%d",
1272 allRecordsHaveEchConfig
));
1273 if (gHttpHandler
->FallbackToOriginIfConfigsAreECHAndAllFailed() ||
1274 !allRecordsHaveEchConfig
) {
1275 useOrigConnInfoToRetry();
1280 LOG((" No available records to retry"));
1281 if (gHttpHandler
->FallbackToOriginIfConfigsAreECHAndAllFailed()) {
1282 useOrigConnInfoToRetry();
1288 if (LOG5_ENABLED()) {
1289 LOG(("SvcDomainName to retry: ["));
1290 for (const auto& r
: mRecordsForRetry
) {
1294 r
->GetSelectedAlpn(alpn
);
1295 LOG((" name=%s alpn=%s", name
.get(), alpn
.get()));
1300 RefPtr
<nsISVCBRecord
> recordsForRetry
=
1301 mRecordsForRetry
.PopLastElement().forget();
1302 mConnInfo
= mOrigConnInfo
->CloneAndAdoptHTTPSSVCRecord(recordsForRetry
);
1306 void nsHttpTransaction::MaybeReportFailedSVCDomain(
1307 nsresult aReason
, nsHttpConnectionInfo
* aFailedConnInfo
) {
1308 if (aReason
== psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITHOUT_ECH
) ||
1309 aReason
!= psm::GetXPCOMFromNSSError(SSL_ERROR_ECH_RETRY_WITH_ECH
)) {
1313 Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON
,
1314 ErrorCodeToFailedReason(aReason
));
1316 nsCOMPtr
<nsIDNSService
> dns
= do_GetService(NS_DNSSERVICE_CONTRACTID
);
1318 const nsCString
& failedHost
= aFailedConnInfo
->GetRoutedHost().IsEmpty()
1319 ? aFailedConnInfo
->GetOrigin()
1320 : aFailedConnInfo
->GetRoutedHost();
1321 LOG(("add failed domain name [%s] -> [%s] to exclusion list",
1322 aFailedConnInfo
->GetOrigin().get(), failedHost
.get()));
1323 Unused
<< dns
->ReportFailedSVCDomainName(aFailedConnInfo
->GetOrigin(),
1328 bool nsHttpTransaction::ShouldRestartOn0RttError(nsresult reason
) {
1330 ("nsHttpTransaction::ShouldRestartOn0RttError [this=%p, "
1331 "mEarlyDataWasAvailable=%d error=%" PRIx32
"]\n",
1332 this, mEarlyDataWasAvailable
, static_cast<uint32_t>(reason
)));
1333 return StaticPrefs::network_http_early_data_disable_on_error() &&
1334 mEarlyDataWasAvailable
&& SecurityErrorThatMayNeedRestart(reason
);
1337 static void MaybeRemoveSSLToken(nsITransportSecurityInfo
* aSecurityInfo
) {
1339 network_http_remove_resumption_token_when_early_data_failed()) {
1342 if (!aSecurityInfo
) {
1346 aSecurityInfo
->GetPeerId(key
);
1347 nsresult rv
= SSLTokensCache::RemoveAll(key
);
1348 LOG(("RemoveSSLToken [key=%s, rv=%" PRIx32
"]", key
.get(),
1349 static_cast<uint32_t>(rv
)));
1352 void nsHttpTransaction::Close(nsresult reason
) {
1353 LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32
"]\n", this,
1354 static_cast<uint32_t>(reason
)));
1357 MutexAutoLock
lock(mLock
);
1358 mEarlyHintObserver
= nullptr;
1359 mWebTransportSessionEventListener
= nullptr;
1363 gHttpHandler
->ConnMgr()->RemoveActiveTransaction(this);
1368 mDNSRequest
->Cancel(NS_ERROR_ABORT
);
1369 mDNSRequest
= nullptr;
1372 MaybeCancelFallbackTimer();
1374 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1375 if (reason
== NS_BINDING_RETARGETED
) {
1376 LOG((" close %p skipped due to ERETARGETED\n", this));
1381 LOG((" already closed\n"));
1385 NotifyTransactionObserver(reason
);
1387 if (mTokenBucketCancel
) {
1388 mTokenBucketCancel
->Cancel(reason
);
1389 mTokenBucketCancel
= nullptr;
1392 // report the reponse is complete if not already reported
1393 if (!mResponseIsComplete
) {
1394 gHttpHandler
->ObserveHttpActivityWithArgs(
1395 HttpActivityArgs(mChannelId
), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
,
1396 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
, PR_Now(),
1397 static_cast<uint64_t>(mContentRead
), ""_ns
);
1400 // report that this transaction is closing
1401 gHttpHandler
->ObserveHttpActivityWithArgs(
1402 HttpActivityArgs(mChannelId
), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
,
1403 NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
, PR_Now(), 0, ""_ns
);
1405 // we must no longer reference the connection! find out if the
1406 // connection was being reused before letting it go.
1407 bool connReused
= false;
1408 bool isHttp2or3
= false;
1410 connReused
= mConnection
->IsReused();
1411 isHttp2or3
= mConnection
->Version() >= HttpVersion::v2_0
;
1413 MaybeRefreshSecurityInfo();
1418 bool shouldRestartTransactionForHTTPSRR
=
1419 mOrigConnInfo
&& AllowedErrorForHTTPSRRFallback(reason
);
1422 // if the connection was reset or closed before we wrote any part of the
1423 // request or if we wrote the request but didn't receive any part of the
1424 // response and the connection was being reused, then we can (and really
1425 // should) assume that we wrote to a stale connection and we must therefore
1426 // repeat the request over a new connection.
1428 // We have decided to retry not only in case of the reused connections, but
1429 // all safe methods(bug 1236277).
1431 // NOTE: the conditions under which we will automatically retry the HTTP
1432 // request have to be carefully selected to avoid duplication of the
1433 // request from the point-of-view of the server. such duplication could
1434 // have dire consequences including repeated purchases, etc.
1436 // NOTE: because of the way SSL proxy CONNECT is implemented, it is
1437 // possible that the transaction may have received data without having
1438 // sent any data. for this reason, mSendData == FALSE does not imply
1439 // mReceivedData == FALSE. (see bug 203057 for more info.)
1441 // Never restart transactions that are marked as sticky to their conenction.
1442 // We use that capability to identify transactions bound to connection based
1443 // authentication. Reissuing them on a different connections will break
1444 // this bondage. Major issue may arise when there is an NTLM message auth
1445 // header on the transaction and we send it to a different NTLM authenticated
1446 // connection. It will break that connection and also confuse the channel's
1447 // auth provider, beliving the cached credentials are wrong and asking for
1448 // the password mistakenly again from the user.
1449 if ((reason
== NS_ERROR_NET_RESET
|| reason
== NS_OK
||
1451 psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA
) ||
1452 ShouldRestartOn0RttError(reason
) ||
1453 shouldRestartTransactionForHTTPSRR
) &&
1454 (!(mCaps
& NS_HTTP_STICKY_CONNECTION
) ||
1455 (mCaps
& NS_HTTP_CONNECTION_RESTARTABLE
) ||
1456 (mEarlyDataDisposition
== EARLY_425
))) {
1457 if (mForceRestart
) {
1458 SetRestartReason(TRANSACTION_RESTART_FORCED
);
1459 if (NS_SUCCEEDED(Restart())) {
1460 if (mResponseHead
) {
1461 mResponseHead
->Reset();
1464 mContentLength
= -1;
1465 delete mChunkedDecoder
;
1466 mChunkedDecoder
= nullptr;
1467 mHaveStatusLine
= false;
1468 mHaveAllHeaders
= false;
1469 mHttpResponseMatched
= false;
1470 mResponseIsComplete
= false;
1471 mDidContentStart
= false;
1474 mReceivedData
= false;
1475 mSupportsHTTP3
= false;
1476 LOG(("transaction force restarted\n"));
1481 mDoNotTryEarlyData
= true;
1483 // reallySentData is meant to separate the instances where data has
1484 // been sent by this transaction but buffered at a higher level while
1485 // a TLS session (perhaps via a tunnel) is setup.
1486 bool reallySentData
=
1487 mSentData
&& (!mConnection
|| mConnection
->BytesWritten());
1489 // If this is true, it means we failed to use the HTTPSSVC connection info
1490 // to connect to the server. We need to retry with the original connection
1492 shouldRestartTransactionForHTTPSRR
&= !reallySentData
;
1495 psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA
) ||
1496 reason
== psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT
) ||
1497 (!mReceivedData
&& ((mRequestHead
&& mRequestHead
->IsSafeMethod()) ||
1498 !reallySentData
|| connReused
)) ||
1499 shouldRestartTransactionForHTTPSRR
) {
1500 if (shouldRestartTransactionForHTTPSRR
) {
1501 MaybeReportFailedSVCDomain(reason
, mConnInfo
);
1502 PrepareConnInfoForRetry(reason
);
1503 mDontRetryWithDirectRoute
= true;
1505 ("transaction will be restarted with the fallback connection info "
1507 mConnInfo
? mConnInfo
->HashKey().get() : "None"));
1510 if (shouldRestartTransactionForHTTPSRR
) {
1511 auto toRestartReason
=
1512 [](nsresult aStatus
) -> TRANSACTION_RESTART_REASON
{
1513 if (aStatus
== NS_ERROR_NET_RESET
) {
1514 return TRANSACTION_RESTART_HTTPS_RR_NET_RESET
;
1516 if (aStatus
== NS_ERROR_CONNECTION_REFUSED
) {
1517 return TRANSACTION_RESTART_HTTPS_RR_CONNECTION_REFUSED
;
1519 if (aStatus
== NS_ERROR_UNKNOWN_HOST
) {
1520 return TRANSACTION_RESTART_HTTPS_RR_UNKNOWN_HOST
;
1522 if (aStatus
== NS_ERROR_NET_TIMEOUT
) {
1523 return TRANSACTION_RESTART_HTTPS_RR_NET_TIMEOUT
;
1525 if (psm::IsNSSErrorCode(-1 * NS_ERROR_GET_CODE(aStatus
))) {
1526 return TRANSACTION_RESTART_HTTPS_RR_SEC_ERROR
;
1528 MOZ_ASSERT_UNREACHABLE("Unexpected reason");
1529 return TRANSACTION_RESTART_OTHERS
;
1531 SetRestartReason(toRestartReason(reason
));
1532 } else if (!reallySentData
) {
1533 SetRestartReason(TRANSACTION_RESTART_NO_DATA_SENT
);
1534 } else if (reason
== psm::GetXPCOMFromNSSError(
1535 SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA
)) {
1536 SetRestartReason(TRANSACTION_RESTART_DOWNGRADE_WITH_EARLY_DATA
);
1537 } else if (reason
==
1538 psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT
)) {
1539 SetRestartReason(TRANSACTION_RESTART_PROTOCOL_VERSION_ALERT
);
1541 // if restarting fails, then we must proceed to close the pipe,
1542 // which will notify the channel that the transaction failed.
1543 // Note that when echConfig is enabled, it's possible that we don't have a
1544 // usable connection info to retry.
1545 if (mConnInfo
&& NS_SUCCEEDED(Restart())) {
1548 // mConnInfo could be set to null in PrepareConnInfoForRetry() when we
1549 // can't find an available https rr to retry. We have to set mConnInfo
1550 // back to mOrigConnInfo to make sure no crash when mConnInfo being
1553 mConnInfo
.swap(mOrigConnInfo
);
1554 MOZ_ASSERT(mConnInfo
);
1559 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON
,
1562 if (!mResponseIsComplete
&& NS_SUCCEEDED(reason
) && isHttp2or3
) {
1563 // Responses without content-length header field are still complete if
1564 // they are transfered over http2 or http3 and the stream is properly
1566 mResponseIsComplete
= true;
1569 if ((mChunkedDecoder
|| (mContentLength
>= int64_t(0))) &&
1570 (NS_SUCCEEDED(reason
) && !mResponseIsComplete
)) {
1571 NS_WARNING("Partial transfer, incomplete HTTP response received");
1573 if ((mHttpResponseCode
/ 100 == 2) && (mHttpVersion
>= HttpVersion::v1_1
)) {
1574 FrameCheckLevel clevel
= gHttpHandler
->GetEnforceH1Framing();
1575 if (clevel
>= FRAMECHECK_BARELY
) {
1576 // If clevel == FRAMECHECK_STRICT mark any incomplete response as
1578 // if clevel == FRAMECHECK_BARELY: 1) mark a chunked-encoded response
1579 // that do not ends on exactly a chunk boundary as partial; We are not
1580 // strict about the last 0-size chunk and do not mark as parial
1581 // responses that do not have the last 0-size chunk but do end on a
1582 // chunk boundary. (check mChunkedDecoder->GetChunkRemaining() != 0)
1583 // 2) mark a transfer that is partial and it is not chunk-encoded or
1584 // gzip-encoded or other content-encoding as partial. (check
1585 // !mChunkedDecoder && !mContentDecoding && mContentDecodingCheck))
1586 // if clevel == FRAMECHECK_STRICT_CHUNKED mark a chunked-encoded
1587 // response that ends on exactly a chunk boundary also as partial.
1588 // Here a response must have the last 0-size chunk.
1589 if ((clevel
== FRAMECHECK_STRICT
) ||
1590 (mChunkedDecoder
&& (mChunkedDecoder
->GetChunkRemaining() ||
1591 (clevel
== FRAMECHECK_STRICT_CHUNKED
))) ||
1592 (!mChunkedDecoder
&& !mContentDecoding
&& mContentDecodingCheck
)) {
1593 reason
= NS_ERROR_NET_PARTIAL_TRANSFER
;
1594 LOG(("Partial transfer, incomplete HTTP response received: %s",
1595 mChunkedDecoder
? "broken chunk" : "c-l underrun"));
1601 // whether or not we generate an error for the transaction
1602 // bad framing means we don't want a pconn
1603 mConnection
->DontReuse();
1607 bool relConn
= true;
1608 if (NS_SUCCEEDED(reason
)) {
1609 // the server has not sent the final \r\n terminating the header
1610 // section, and there may still be a header line unparsed. let's make
1611 // sure we parse the remaining header line, and then hopefully, the
1612 // response will be usable (see bug 88792).
1613 if (!mHaveAllHeaders
) {
1614 char data
[] = "\n\n";
1615 uint32_t unused
= 0;
1616 // If we have a partial line already, we actually need two \ns to finish
1617 // the headers section.
1618 Unused
<< ParseHead(data
, mLineBuf
.IsEmpty() ? 1 : 2, &unused
);
1620 if (mResponseHead
->Version() == HttpVersion::v0_9
) {
1621 // Reject 0 byte HTTP/0.9 Responses - bug 423506
1622 LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
1623 reason
= NS_ERROR_NET_RESET
;
1627 // honor the sticky connection flag...
1628 if (mCaps
& NS_HTTP_STICKY_CONNECTION
) {
1629 LOG((" keeping the connection because of STICKY_CONNECTION flag"));
1633 // if the proxy connection has failed, we want the connection be held
1634 // to allow the upper layers (think nsHttpChannel) to close it when
1635 // the failure is unrecoverable.
1636 // we can't just close it here, because mProxyConnectFailed is to a general
1637 // flag and is also set for e.g. 407 which doesn't mean to kill the
1638 // connection, specifically when connection oriented auth may be involved.
1639 if (mProxyConnectFailed
) {
1640 LOG((" keeping the connection because of mProxyConnectFailed"));
1644 // Use mOrigConnInfo as an indicator that this transaction is completed
1645 // successfully with an HTTPSSVC record.
1646 if (mOrigConnInfo
) {
1647 Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON
,
1648 HTTPSSVC_CONNECTION_OK
);
1652 // mTimings.responseEnd is normally recorded based on the end of a
1653 // HTTP delimiter such as chunked-encodings or content-length. However,
1654 // EOF or an error still require an end time be recorded.
1655 if (TimingEnabled()) {
1656 const TimingStruct timings
= Timings();
1657 if (timings
.responseEnd
.IsNull() && !timings
.responseStart
.IsNull()) {
1658 SetResponseEnd(TimeStamp::Now());
1661 // Accumulate download throughput telemetry
1662 const int64_t TELEMETRY_DOWNLOAD_SIZE_GREATER_THAN_10MB
=
1663 (int64_t)10 * (int64_t)(1 << 20);
1664 if ((mContentRead
> TELEMETRY_DOWNLOAD_SIZE_GREATER_THAN_10MB
) &&
1665 !timings
.requestStart
.IsNull() && !timings
.responseEnd
.IsNull()) {
1666 TimeDuration elapsed
= timings
.responseEnd
- timings
.requestStart
;
1667 double megabits
= static_cast<double>(mContentRead
) * 8.0 / 1000000.0;
1668 uint32_t mpbs
= static_cast<uint32_t>(megabits
/ elapsed
.ToSeconds());
1670 switch (mHttpVersion
) {
1671 case HttpVersion::v1_0
:
1672 case HttpVersion::v1_1
:
1673 glean::networking::http_1_download_throughput
.AccumulateSamples(
1676 case HttpVersion::v2_0
:
1677 glean::networking::http_2_download_throughput
.AccumulateSamples(
1680 case HttpVersion::v3_0
:
1681 glean::networking::http_3_download_throughput
.AccumulateSamples(
1690 if (mTrafficCategory
!= HttpTrafficCategory::eInvalid
) {
1691 HttpTrafficAnalyzer
* hta
= gHttpHandler
->GetHttpTrafficAnalyzer();
1693 hta
->AccumulateHttpTransferredSize(mTrafficCategory
, mTransferSize
,
1698 if (mThroughCaptivePortal
) {
1699 Telemetry::ScalarAdd(
1700 Telemetry::ScalarID::NETWORKING_HTTP_TRANSACTIONS_CAPTIVE_PORTAL
, 1);
1703 if (relConn
&& mConnection
) {
1704 MutexAutoLock
lock(mLock
);
1705 mConnection
= nullptr;
1709 reason
== psm::GetXPCOMFromNSSError(SSL_ERROR_PROTOCOL_VERSION_ALERT
)) {
1710 // Change reason to NS_ERROR_ABORT, so we avoid showing a missleading
1711 // error page tthat TLS1.0 is disabled. H2 or H3 is used here so the
1712 // TLS version is not a problem.
1713 reason
= NS_ERROR_ABORT
;
1716 mTransactionDone
= true; // forcibly flag the transaction as complete
1720 mResolver
= nullptr;
1722 ReleaseBlockingTransaction();
1724 // release some resources that we no longer need
1725 mRequestStream
= nullptr;
1726 mReqHeaderBuf
.Truncate();
1727 mLineBuf
.Truncate();
1728 if (mChunkedDecoder
) {
1729 delete mChunkedDecoder
;
1730 mChunkedDecoder
= nullptr;
1733 for (const auto& entry
: mEchRetryCounterMap
) {
1734 Telemetry::Accumulate(static_cast<Telemetry::HistogramID
>(entry
.GetKey()),
1738 // closing this pipe triggers the channel's OnStopRequest method.
1739 mPipeOut
->CloseWithStatus(reason
);
1742 nsHttpConnectionInfo
* nsHttpTransaction::ConnectionInfo() {
1743 return mConnInfo
.get();
1746 bool // NOTE BASE CLASS
1747 nsAHttpTransaction::ResponseTimeoutEnabled() const {
1751 PRIntervalTime
// NOTE BASE CLASS
1752 nsAHttpTransaction::ResponseTimeout() {
1753 return gHttpHandler
->ResponseTimeout();
1756 bool nsHttpTransaction::ResponseTimeoutEnabled() const {
1757 return mResponseTimeoutEnabled
;
1760 //-----------------------------------------------------------------------------
1761 // nsHttpTransaction <private>
1762 //-----------------------------------------------------------------------------
1764 static inline void RemoveAlternateServiceUsedHeader(
1765 nsHttpRequestHead
* aRequestHead
) {
1767 DebugOnly
<nsresult
> rv
=
1768 aRequestHead
->SetHeader(nsHttp::Alternate_Service_Used
, "0"_ns
);
1769 MOZ_ASSERT(NS_SUCCEEDED(rv
));
1773 void nsHttpTransaction::SetRestartReason(TRANSACTION_RESTART_REASON aReason
) {
1774 if (mRestartReason
== TRANSACTION_RESTART_NONE
) {
1775 mRestartReason
= aReason
;
1779 nsresult
nsHttpTransaction::Restart() {
1780 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1782 // limit the number of restart attempts - bug 92224
1783 if (++mRestartCount
>= gHttpHandler
->MaxRequestAttempts()) {
1784 LOG(("reached max request attempts, failing transaction @%p\n", this));
1785 return NS_ERROR_NET_RESET
;
1788 LOG(("restarting transaction @%p\n", this));
1791 // Dispatching on a new connection better w/o an ambient connection proxy
1792 // auth request header to not confuse the proxy authenticator.
1793 nsAutoCString proxyAuth
;
1795 mRequestHead
->GetHeader(nsHttp::Proxy_Authorization
, proxyAuth
)) &&
1796 IsStickyAuthSchemeAt(proxyAuth
)) {
1797 Unused
<< mRequestHead
->ClearHeader(nsHttp::Proxy_Authorization
);
1801 // rewind streams in case we already wrote out the request
1802 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(mRequestStream
);
1803 if (seekable
) seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, 0);
1805 if (mDoNotTryEarlyData
) {
1806 MutexAutoLock
lock(mLock
);
1807 MaybeRemoveSSLToken(mSecurityInfo
);
1810 // clear old connection state...
1812 MutexAutoLock
lock(mLock
);
1813 mSecurityInfo
= nullptr;
1817 if (!mReuseOnRestart
) {
1818 mConnection
->DontReuse();
1820 MutexAutoLock
lock(mLock
);
1821 mConnection
= nullptr;
1824 // Reset this to our default state, since this may change from one restart
1826 mReuseOnRestart
= false;
1828 if (!mDoNotRemoveAltSvc
&&
1829 (!mConnInfo
->GetRoutedHost().IsEmpty() || mConnInfo
->IsHttp3()) &&
1830 !mDontRetryWithDirectRoute
) {
1831 RefPtr
<nsHttpConnectionInfo
> ci
;
1832 mConnInfo
->CloneAsDirectRoute(getter_AddRefs(ci
));
1834 RemoveAlternateServiceUsedHeader(mRequestHead
);
1837 // Reset mDoNotRemoveAltSvc for the next try.
1838 mDoNotRemoveAltSvc
= false;
1839 mEarlyDataWasAvailable
= false;
1842 // If we weren't trying to do 'proper' ECH, disable ECH GREASE when retrying.
1843 if (mConnInfo
->GetEchConfig().IsEmpty() &&
1844 StaticPrefs::security_tls_ech_disable_grease_on_fallback()) {
1845 mCaps
|= NS_HTTP_DISALLOW_ECH
;
1848 mCaps
|= NS_HTTP_IS_RETRY
;
1850 // Use TRANSACTION_RESTART_OTHERS as a catch-all.
1851 SetRestartReason(TRANSACTION_RESTART_OTHERS
);
1853 // Reset the IP family preferences, so the new connection can try to use
1854 // another IPv4 or IPv6 address.
1855 gHttpHandler
->ConnMgr()->ResetIPFamilyPreference(mConnInfo
);
1857 return gHttpHandler
->InitiateTransaction(this, mPriority
);
1860 bool nsHttpTransaction::TakeRestartedState() {
1861 // This return true if the transaction has been restarted internally. Used to
1862 // let the consuming nsHttpChannel reset proxy authentication. The flag is
1863 // reset to false by this method.
1864 return mRestarted
.exchange(false);
1867 char* nsHttpTransaction::LocateHttpStart(char* buf
, uint32_t len
,
1868 bool aAllowPartialMatch
) {
1869 MOZ_ASSERT(!aAllowPartialMatch
|| mLineBuf
.IsEmpty());
1871 static const char HTTPHeader
[] = "HTTP/1.";
1872 static const uint32_t HTTPHeaderLen
= sizeof(HTTPHeader
) - 1;
1873 static const char HTTP2Header
[] = "HTTP/2";
1874 static const uint32_t HTTP2HeaderLen
= sizeof(HTTP2Header
) - 1;
1875 static const char HTTP3Header
[] = "HTTP/3";
1876 static const uint32_t HTTP3HeaderLen
= sizeof(HTTP3Header
) - 1;
1877 // ShoutCast ICY is treated as HTTP/1.0
1878 static const char ICYHeader
[] = "ICY ";
1879 static const uint32_t ICYHeaderLen
= sizeof(ICYHeader
) - 1;
1881 if (aAllowPartialMatch
&& (len
< HTTPHeaderLen
)) {
1882 return (nsCRT::strncasecmp(buf
, HTTPHeader
, len
) == 0) ? buf
: nullptr;
1885 // mLineBuf can contain partial match from previous search
1886 if (!mLineBuf
.IsEmpty()) {
1887 MOZ_ASSERT(mLineBuf
.Length() < HTTPHeaderLen
);
1888 int32_t checkChars
=
1889 std::min
<uint32_t>(len
, HTTPHeaderLen
- mLineBuf
.Length());
1890 if (nsCRT::strncasecmp(buf
, HTTPHeader
+ mLineBuf
.Length(), checkChars
) ==
1892 mLineBuf
.Append(buf
, checkChars
);
1893 if (mLineBuf
.Length() == HTTPHeaderLen
) {
1894 // We've found whole HTTPHeader sequence. Return pointer at the
1895 // end of matched sequence since it is stored in mLineBuf.
1896 return (buf
+ checkChars
);
1898 // Response matches pattern but is still incomplete.
1901 // Previous partial match together with new data doesn't match the
1902 // pattern. Start the search again.
1903 mLineBuf
.Truncate();
1906 bool firstByte
= true;
1908 if (nsCRT::strncasecmp(buf
, HTTPHeader
,
1909 std::min
<uint32_t>(len
, HTTPHeaderLen
)) == 0) {
1910 if (len
< HTTPHeaderLen
) {
1911 // partial HTTPHeader sequence found
1912 // save partial match to mLineBuf
1913 mLineBuf
.Assign(buf
, len
);
1917 // whole HTTPHeader sequence found
1921 // At least "SmarterTools/2.0.3974.16813" generates nonsensical
1922 // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
1923 // it as HTTP/1.1 to be compatible with old versions of ourselves and
1926 if (firstByte
&& !mInvalidResponseBytesRead
&& len
>= HTTP2HeaderLen
&&
1927 (nsCRT::strncasecmp(buf
, HTTP2Header
, HTTP2HeaderLen
) == 0)) {
1928 LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
1932 // HTTP/3.0 responses to our HTTP/1 requests. Treat the minimal case of
1933 // it as HTTP/1.1 to be compatible with old versions of ourselves and
1936 if (firstByte
&& !mInvalidResponseBytesRead
&& len
>= HTTP3HeaderLen
&&
1937 (nsCRT::strncasecmp(buf
, HTTP3Header
, HTTP3HeaderLen
) == 0)) {
1938 LOG(("nsHttpTransaction:: Identified HTTP/3.0 treating as 1.x\n"));
1942 // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion
1943 // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted
1944 // as HTTP/1.0 in nsHttpResponseHead::ParseVersion
1946 if (firstByte
&& !mInvalidResponseBytesRead
&& len
>= ICYHeaderLen
&&
1947 (nsCRT::strncasecmp(buf
, ICYHeader
, ICYHeaderLen
) == 0)) {
1948 LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));
1952 if (!nsCRT::IsAsciiSpace(*buf
)) firstByte
= false;
1959 nsresult
nsHttpTransaction::ParseLine(nsACString
& line
) {
1960 LOG1(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line
).get()));
1961 nsresult rv
= NS_OK
;
1963 if (!mHaveStatusLine
) {
1964 rv
= mResponseHead
->ParseStatusLine(line
);
1965 if (NS_SUCCEEDED(rv
)) {
1966 mHaveStatusLine
= true;
1968 // XXX this should probably never happen
1969 if (mResponseHead
->Version() == HttpVersion::v0_9
) mHaveAllHeaders
= true;
1971 rv
= mResponseHead
->ParseHeaderLine(line
);
1976 nsresult
nsHttpTransaction::ParseLineSegment(char* segment
, uint32_t len
) {
1977 MOZ_ASSERT(!mHaveAllHeaders
, "already have all headers");
1979 if (!mLineBuf
.IsEmpty() && mLineBuf
.Last() == '\n') {
1980 // trim off the new line char, and if this segment is
1981 // not a continuation of the previous or if we haven't
1982 // parsed the status line yet, then parse the contents
1984 mLineBuf
.Truncate(mLineBuf
.Length() - 1);
1985 if (!mHaveStatusLine
|| (*segment
!= ' ' && *segment
!= '\t')) {
1986 nsresult rv
= ParseLine(mLineBuf
);
1987 mLineBuf
.Truncate();
1988 if (NS_FAILED(rv
)) {
1994 // append segment to mLineBuf...
1995 mLineBuf
.Append(segment
, len
);
1997 // a line buf with only a new line char signifies the end of headers.
1998 if (mLineBuf
.First() == '\n') {
1999 mLineBuf
.Truncate();
2000 // discard this response if it is a 100 continue or other 1xx status.
2001 uint16_t status
= mResponseHead
->Status();
2002 if (status
== 103) {
2003 nsCString linkHeader
;
2004 nsresult rv
= mResponseHead
->GetHeader(nsHttp::Link
, linkHeader
);
2006 nsCString referrerPolicy
;
2007 Unused
<< mResponseHead
->GetHeader(nsHttp::Referrer_Policy
,
2010 if (NS_SUCCEEDED(rv
) && !linkHeader
.IsEmpty()) {
2011 nsCString cspHeader
;
2012 Unused
<< mResponseHead
->GetHeader(nsHttp::Content_Security_Policy
,
2015 nsCOMPtr
<nsIEarlyHintObserver
> earlyHint
;
2017 MutexAutoLock
lock(mLock
);
2018 earlyHint
= mEarlyHintObserver
;
2021 DebugOnly
<nsresult
> rv
= NS_DispatchToMainThread(
2022 NS_NewRunnableFunction(
2023 "nsIEarlyHintObserver->EarlyHint",
2024 [obs
{std::move(earlyHint
)}, header
{std::move(linkHeader
)},
2025 referrerPolicy
{std::move(referrerPolicy
)},
2026 cspHeader
{std::move(cspHeader
)}]() {
2027 obs
->EarlyHint(header
, referrerPolicy
, cspHeader
);
2029 NS_DISPATCH_NORMAL
);
2030 MOZ_ASSERT(NS_SUCCEEDED(rv
));
2034 if ((status
!= 101) && (status
/ 100 == 1)) {
2035 LOG(("ignoring 1xx response except 101 and 103\n"));
2036 mHaveStatusLine
= false;
2037 mHttpResponseMatched
= false;
2038 mConnection
->SetLastTransactionExpectedNoContent(true);
2039 mResponseHead
->Reset();
2042 if (!mConnection
->IsProxyConnectInProgress()) {
2043 MutexAutoLock
lock(mLock
);
2044 mEarlyHintObserver
= nullptr;
2046 mHaveAllHeaders
= true;
2051 nsresult
nsHttpTransaction::ParseHead(char* buf
, uint32_t count
,
2052 uint32_t* countRead
) {
2057 LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count
));
2061 MOZ_ASSERT(!mHaveAllHeaders
, "oops");
2063 // allocate the response head object if necessary
2064 if (!mResponseHead
) {
2065 mResponseHead
= new nsHttpResponseHead();
2066 if (!mResponseHead
) return NS_ERROR_OUT_OF_MEMORY
;
2068 // report that we have a least some of the response
2069 if (!mReportedStart
) {
2070 mReportedStart
= true;
2071 gHttpHandler
->ObserveHttpActivityWithArgs(
2072 HttpActivityArgs(mChannelId
), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
,
2073 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START
, PR_Now(), 0, ""_ns
);
2077 if (!mHttpResponseMatched
) {
2078 // Normally we insist on seeing HTTP/1.x in the first few bytes,
2079 // but if we are on a persistent connection and the previous transaction
2080 // was not supposed to have any content then we need to be prepared
2081 // to skip over a response body that the server may have sent even
2082 // though it wasn't allowed.
2083 if (!mConnection
|| !mConnection
->LastTransactionExpectedNoContent()) {
2084 // tolerate only minor junk before the status line
2085 mHttpResponseMatched
= true;
2086 char* p
= LocateHttpStart(buf
, std::min
<uint32_t>(count
, 11), true);
2088 // Treat any 0.9 style response of a put as a failure.
2089 if (mRequestHead
->IsPut()) return NS_ERROR_ABORT
;
2091 if (NS_FAILED(mResponseHead
->ParseStatusLine(""_ns
))) {
2092 return NS_ERROR_FAILURE
;
2094 mHaveStatusLine
= true;
2095 mHaveAllHeaders
= true;
2099 // skip over the junk
2100 mInvalidResponseBytesRead
+= p
- buf
;
2101 *countRead
= p
- buf
;
2105 char* p
= LocateHttpStart(buf
, count
, false);
2107 mInvalidResponseBytesRead
+= p
- buf
;
2108 *countRead
= p
- buf
;
2110 mHttpResponseMatched
= true;
2112 mInvalidResponseBytesRead
+= count
;
2114 if (mInvalidResponseBytesRead
> MAX_INVALID_RESPONSE_BODY_SIZE
) {
2116 ("nsHttpTransaction::ParseHead() "
2117 "Cannot find Response Header\n"));
2118 // cannot go back and call this 0.9 anymore as we
2119 // have thrown away a lot of the leading junk
2120 return NS_ERROR_ABORT
;
2126 // otherwise we can assume that we don't have a HTTP/0.9 response.
2128 MOZ_ASSERT(mHttpResponseMatched
);
2129 while ((eol
= static_cast<char*>(memchr(buf
, '\n', count
- *countRead
))) !=
2131 // found line in range [buf:eol]
2132 len
= eol
- buf
+ 1;
2136 // actually, the line is in the range [buf:eol-1]
2137 if ((eol
> buf
) && (*(eol
- 1) == '\r')) len
--;
2139 buf
[len
- 1] = '\n';
2140 rv
= ParseLineSegment(buf
, len
);
2141 if (NS_FAILED(rv
)) return rv
;
2143 if (mHaveAllHeaders
) return NS_OK
;
2148 if (!mHttpResponseMatched
) {
2149 // a 100 class response has caused us to throw away that set of
2150 // response headers and look for the next response
2151 return NS_ERROR_NET_INTERRUPT
;
2155 // do something about a partial header line
2156 if (!mHaveAllHeaders
&& (len
= count
- *countRead
)) {
2158 // ignore a trailing carriage return, and don't bother calling
2159 // ParseLineSegment if buf only contains a carriage return.
2160 if ((buf
[len
- 1] == '\r') && (--len
== 0)) return NS_OK
;
2161 rv
= ParseLineSegment(buf
, len
);
2162 if (NS_FAILED(rv
)) return rv
;
2167 bool nsHttpTransaction::HandleWebTransportResponse(uint16_t aStatus
) {
2168 MOZ_ASSERT(mIsForWebTransport
);
2169 if (!(aStatus
>= 200 && aStatus
< 300)) {
2173 RefPtr
<Http3WebTransportSession
> wtSession
=
2174 mConnection
->GetWebTransportSession(this);
2179 nsCOMPtr
<WebTransportSessionEventListener
> webTransportListener
;
2181 MutexAutoLock
lock(mLock
);
2182 webTransportListener
= mWebTransportSessionEventListener
;
2183 mWebTransportSessionEventListener
= nullptr;
2185 if (webTransportListener
) {
2186 webTransportListener
->OnSessionReadyInternal(wtSession
);
2187 wtSession
->SetWebTransportSessionEventListener(webTransportListener
);
2193 nsresult
nsHttpTransaction::HandleContentStart() {
2194 LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
2195 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2197 if (mResponseHead
) {
2198 if (mEarlyDataDisposition
== EARLY_ACCEPTED
) {
2199 if (mResponseHead
->Status() == 425) {
2200 // We will report this state when the final responce arrives.
2201 mEarlyDataDisposition
= EARLY_425
;
2203 Unused
<< mResponseHead
->SetHeader(nsHttp::X_Firefox_Early_Data
,
2206 } else if (mEarlyDataDisposition
== EARLY_SENT
) {
2207 Unused
<< mResponseHead
->SetHeader(nsHttp::X_Firefox_Early_Data
,
2209 } else if (mEarlyDataDisposition
== EARLY_425
) {
2210 Unused
<< mResponseHead
->SetHeader(nsHttp::X_Firefox_Early_Data
,
2212 mEarlyDataDisposition
= EARLY_NONE
;
2213 } // no header on NONE case
2215 if (LOG3_ENABLED()) {
2216 LOG3(("http response [\n"));
2217 nsAutoCString headers
;
2218 mResponseHead
->Flatten(headers
, false);
2219 headers
.AppendLiteral(" OriginalHeaders");
2220 headers
.AppendLiteral("\r\n");
2221 mResponseHead
->FlattenNetworkOriginalHeaders(headers
);
2222 LogHeaders(headers
.get());
2226 CheckForStickyAuthScheme();
2228 // Save http version, mResponseHead isn't available anymore after
2229 // TakeResponseHead() is called
2230 mHttpVersion
= mResponseHead
->Version();
2231 mHttpResponseCode
= mResponseHead
->Status();
2233 // notify the connection, give it a chance to cause a reset.
2235 nsresult rv
= mConnection
->OnHeadersAvailable(this, mRequestHead
,
2236 mResponseHead
, &reset
);
2237 NS_ENSURE_SUCCESS(rv
, rv
);
2239 // looks like we should ignore this response, resetting...
2241 LOG(("resetting transaction's response head\n"));
2242 mHaveAllHeaders
= false;
2243 mHaveStatusLine
= false;
2244 mReceivedData
= false;
2246 mHttpResponseMatched
= false;
2247 mResponseHead
->Reset();
2248 // wait to be called again...
2252 bool responseChecked
= false;
2253 if (mIsForWebTransport
) {
2254 responseChecked
= HandleWebTransportResponse(mResponseHead
->Status());
2255 LOG(("HandleWebTransportResponse res=%d", responseChecked
));
2256 if (responseChecked
) {
2258 mPreserveStream
= true;
2262 if (!responseChecked
) {
2263 // check if this is a no-content response
2264 switch (mResponseHead
->Status()) {
2266 mPreserveStream
= true;
2267 [[fallthrough
]]; // to other no content cases:
2272 LOG(("this response should not contain a body.\n"));
2275 LOG(("408 Server Timeouts"));
2277 if (mConnection
->Version() >= HttpVersion::v2_0
) {
2278 mForceRestart
= true;
2279 return NS_ERROR_NET_RESET
;
2282 // If this error could be due to a persistent connection
2283 // reuse then we pass an error code of NS_ERROR_NET_RESET
2284 // to trigger the transaction 'restart' mechanism. We
2285 // tell it to reset its response headers so that it will
2286 // be ready to receive the new response.
2287 LOG(("408 Server Timeouts now=%d lastWrite=%d", PR_IntervalNow(),
2288 mConnection
->LastWriteTime()));
2289 if ((PR_IntervalNow() - mConnection
->LastWriteTime()) >=
2290 PR_MillisecondsToInterval(1000)) {
2291 mForceRestart
= true;
2292 return NS_ERROR_NET_RESET
;
2296 LOG(("Misdirected Request.\n"));
2297 gHttpHandler
->ClearHostMapping(mConnInfo
);
2299 m421Received
= true;
2300 mCaps
|= NS_HTTP_REFRESH_DNS
;
2302 // retry on a new connection - just in case
2303 // See bug 1609410, we can't restart the transaction when
2304 // NS_HTTP_STICKY_CONNECTION is set. In the case that a connection
2305 // already passed NTLM authentication, restarting the transaction will
2306 // cause the connection to be closed.
2307 if (!mRestartCount
&& !(mCaps
& NS_HTTP_STICKY_CONNECTION
)) {
2308 mCaps
&= ~NS_HTTP_ALLOW_KEEPALIVE
;
2309 mForceRestart
= true; // force restart has built in loop protection
2310 return NS_ERROR_NET_RESET
;
2314 LOG(("Too Early."));
2315 if ((mEarlyDataDisposition
== EARLY_425
) && !mDoNotTryEarlyData
) {
2316 mDoNotTryEarlyData
= true;
2317 mForceRestart
= true; // force restart has built in loop protection
2318 if (mConnection
->Version() >= HttpVersion::v2_0
) {
2319 mReuseOnRestart
= true;
2321 return NS_ERROR_NET_RESET
;
2327 // Remember whether HTTP3 is supported
2328 mSupportsHTTP3
= nsHttpHandler::IsHttp3SupportedByServer(mResponseHead
);
2330 CollectTelemetryForUploads();
2333 if (mSupportsHTTP3
) {
2334 Accumulate(Telemetry::TRANSACTION_WAIT_TIME_HTTP2_SUP_HTTP3
,
2335 mPendingDurationTime
.ToMilliseconds());
2338 // If we're only connecting then we're going to be upgrading this
2339 // connection since we were successful. Any data from now on belongs to
2340 // the upgrade handler. If we're not successful the content body doesn't
2341 // matter. Proxy http errors are treated as network errors. This
2342 // connection won't be reused since it's marked sticky and no
2344 if (mCaps
& NS_HTTP_CONNECT_ONLY
) {
2345 MOZ_ASSERT(!(mCaps
& NS_HTTP_ALLOW_KEEPALIVE
) &&
2346 (mCaps
& NS_HTTP_STICKY_CONNECTION
),
2347 "connection should be sticky and no keep-alive");
2348 // The transaction will expect the server to close the socket if
2349 // there's no content length instead of doing the upgrade.
2353 // preserve connection for tunnel setup - h2 websocket upgrade only
2354 if (mIsHttp2Websocket
&& mResponseHead
->Status() == 200) {
2355 LOG(("nsHttpTransaction::HandleContentStart websocket upgrade resp 200"));
2359 if (mResponseHead
->Status() == 200 &&
2360 mConnection
->IsProxyConnectInProgress()) {
2361 // successful CONNECTs do not have response bodies
2364 mConnection
->SetLastTransactionExpectedNoContent(mNoContent
);
2369 // grab the content-length from the response headers
2370 mContentLength
= mResponseHead
->ContentLength();
2372 // handle chunked encoding here, so we'll know immediately when
2373 // we're done with the socket. please note that _all_ other
2374 // decoding is done when the channel receives the content data
2375 // so as not to block the socket transport thread too much.
2376 if (mResponseHead
->Version() >= HttpVersion::v1_0
&&
2377 mResponseHead
->HasHeaderValue(nsHttp::Transfer_Encoding
, "chunked")) {
2378 // we only support the "chunked" transfer encoding right now.
2379 mChunkedDecoder
= new nsHttpChunkedDecoder();
2380 LOG(("nsHttpTransaction %p chunked decoder created\n", this));
2381 // Ignore server specified Content-Length.
2382 if (mContentLength
!= int64_t(-1)) {
2383 LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this));
2384 mContentLength
= -1;
2386 mConnection
->DontReuse();
2389 } else if (mContentLength
== int64_t(-1)) {
2390 LOG(("waiting for the server to close the connection.\n"));
2395 mDidContentStart
= true;
2399 // called on the socket thread
2400 nsresult
nsHttpTransaction::HandleContent(char* buf
, uint32_t count
,
2401 uint32_t* contentRead
,
2402 uint32_t* contentRemaining
) {
2405 LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count
));
2408 *contentRemaining
= 0;
2410 MOZ_ASSERT(mConnection
);
2412 if (!mDidContentStart
) {
2413 rv
= HandleContentStart();
2414 if (NS_FAILED(rv
)) return rv
;
2415 // Do not write content to the pipe if we haven't started streaming yet
2416 if (!mDidContentStart
) return NS_OK
;
2419 if (mChunkedDecoder
) {
2420 // give the buf over to the chunked decoder so it can reformat the
2421 // data and tell us how much is really there.
2422 rv
= mChunkedDecoder
->HandleChunkedContent(buf
, count
, contentRead
,
2424 if (NS_FAILED(rv
)) return rv
;
2425 } else if (mContentLength
>= int64_t(0)) {
2426 // HTTP/1.0 servers have been known to send erroneous Content-Length
2427 // headers. So, unless the connection is persistent, we must make
2428 // allowances for a possibly invalid Content-Length header. Thus, if
2429 // NOT persistent, we simply accept everything in |buf|.
2430 if (mConnection
->IsPersistent() || mPreserveStream
||
2431 mHttpVersion
>= HttpVersion::v1_1
) {
2432 int64_t remaining
= mContentLength
- mContentRead
;
2433 *contentRead
= uint32_t(std::min
<int64_t>(count
, remaining
));
2434 *contentRemaining
= count
- *contentRead
;
2436 *contentRead
= count
;
2437 // mContentLength might need to be increased...
2438 int64_t position
= mContentRead
+ int64_t(count
);
2439 if (position
> mContentLength
) {
2440 mContentLength
= position
;
2441 // mResponseHead->SetContentLength(mContentLength);
2445 // when we are just waiting for the server to close the connection...
2446 // (no explicit content-length given)
2447 *contentRead
= count
;
2451 // update count of content bytes read and report progress...
2452 mContentRead
+= *contentRead
;
2456 ("nsHttpTransaction::HandleContent [this=%p count=%u read=%u "
2457 "mContentRead=%" PRId64
" mContentLength=%" PRId64
"]\n",
2458 this, count
, *contentRead
, mContentRead
, mContentLength
));
2460 // check for end-of-file
2461 if ((mContentRead
== mContentLength
) ||
2462 (mChunkedDecoder
&& mChunkedDecoder
->ReachedEOF())) {
2464 MutexAutoLock
lock(mLock
);
2465 if (mChunkedDecoder
) {
2466 mForTakeResponseTrailers
= mChunkedDecoder
->TakeTrailers();
2469 // the transaction is done with a complete response.
2470 mTransactionDone
= true;
2471 mResponseIsComplete
= true;
2473 ReleaseBlockingTransaction();
2475 if (TimingEnabled()) {
2476 SetResponseEnd(TimeStamp::Now());
2479 // report the entire response has arrived
2480 gHttpHandler
->ObserveHttpActivityWithArgs(
2481 HttpActivityArgs(mChannelId
), NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
,
2482 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE
, PR_Now(),
2483 static_cast<uint64_t>(mContentRead
), ""_ns
);
2489 nsresult
nsHttpTransaction::ProcessData(char* buf
, uint32_t count
,
2490 uint32_t* countRead
) {
2493 LOG1(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count
));
2497 // we may not have read all of the headers yet...
2498 if (!mHaveAllHeaders
) {
2499 uint32_t bytesConsumed
= 0;
2502 uint32_t localBytesConsumed
= 0;
2503 char* localBuf
= buf
+ bytesConsumed
;
2504 uint32_t localCount
= count
- bytesConsumed
;
2506 rv
= ParseHead(localBuf
, localCount
, &localBytesConsumed
);
2507 if (NS_FAILED(rv
) && rv
!= NS_ERROR_NET_INTERRUPT
) return rv
;
2508 bytesConsumed
+= localBytesConsumed
;
2509 } while (rv
== NS_ERROR_NET_INTERRUPT
);
2511 mCurrentHttpResponseHeaderSize
+= bytesConsumed
;
2512 if (mCurrentHttpResponseHeaderSize
>
2513 gHttpHandler
->MaxHttpResponseHeaderSize()) {
2514 LOG(("nsHttpTransaction %p The response header exceeds the limit.\n",
2516 return NS_ERROR_FILE_TOO_BIG
;
2518 count
-= bytesConsumed
;
2520 // if buf has some content in it, shift bytes to top of buf.
2521 if (count
&& bytesConsumed
) memmove(buf
, buf
+ bytesConsumed
, count
);
2523 if (mResponseHead
&& mHaveAllHeaders
) {
2524 auto reportResponseHeader
= [&](uint32_t aSubType
) {
2525 nsAutoCString completeResponseHeaders
;
2526 mResponseHead
->Flatten(completeResponseHeaders
, false);
2527 completeResponseHeaders
.AppendLiteral("\r\n");
2528 gHttpHandler
->ObserveHttpActivityWithArgs(
2529 HttpActivityArgs(mChannelId
),
2530 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION
, aSubType
, PR_Now(), 0,
2531 completeResponseHeaders
);
2534 if (mConnection
->IsProxyConnectInProgress()) {
2535 reportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_PROXY_RESPONSE_HEADER
);
2536 } else if (!mReportedResponseHeader
) {
2537 mReportedResponseHeader
= true;
2538 reportResponseHeader(NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER
);
2543 // even though count may be 0, we still want to call HandleContent
2544 // so it can complete the transaction if this is a "no-content" response.
2545 if (mHaveAllHeaders
) {
2546 uint32_t countRemaining
= 0;
2550 // +--------------------------------------+----------------+-----+
2551 // | countRead | countRemaining | |
2552 // +--------------------------------------+----------------+-----+
2554 // count : bytes read from the socket
2555 // countRead : bytes corresponding to this transaction
2556 // countRemaining : bytes corresponding to next transaction on conn
2559 // count > countRead + countRemaining <==> chunked transfer encoding
2561 rv
= HandleContent(buf
, count
, countRead
, &countRemaining
);
2562 if (NS_FAILED(rv
)) return rv
;
2563 // we may have read more than our share, in which case we must give
2564 // the excess bytes back to the connection
2565 if (mResponseIsComplete
&& countRemaining
&&
2566 (mConnection
->Version() != HttpVersion::v3_0
)) {
2567 MOZ_ASSERT(mConnection
);
2568 rv
= mConnection
->PushBack(buf
+ *countRead
, countRemaining
);
2569 NS_ENSURE_SUCCESS(rv
, rv
);
2572 if (!mContentDecodingCheck
&& mResponseHead
) {
2573 mContentDecoding
= mResponseHead
->HasHeader(nsHttp::Content_Encoding
);
2574 mContentDecodingCheck
= true;
2581 // Called when the transaction marked for blocking is associated with a
2582 // connection (i.e. added to a new h1 conn, an idle http connection, etc..) It
2583 // is safe to call this multiple times with it only having an effect once.
2584 void nsHttpTransaction::DispatchedAsBlocking() {
2585 if (mDispatchedAsBlocking
) return;
2587 LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
2589 if (!mRequestContext
) return;
2592 ("nsHttpTransaction adding blocking transaction %p from "
2593 "request context %p\n",
2594 this, mRequestContext
.get()));
2596 mRequestContext
->AddBlockingTransaction();
2597 mDispatchedAsBlocking
= true;
2600 void nsHttpTransaction::RemoveDispatchedAsBlocking() {
2601 if (!mRequestContext
|| !mDispatchedAsBlocking
) {
2602 LOG(("nsHttpTransaction::RemoveDispatchedAsBlocking this=%p not blocking",
2607 uint32_t blockers
= 0;
2608 nsresult rv
= mRequestContext
->RemoveBlockingTransaction(&blockers
);
2611 ("nsHttpTransaction removing blocking transaction %p from "
2612 "request context %p. %d blockers remain.\n",
2613 this, mRequestContext
.get(), blockers
));
2615 if (NS_SUCCEEDED(rv
) && !blockers
) {
2617 ("nsHttpTransaction %p triggering release of blocked channels "
2618 " with request context=%p\n",
2619 this, mRequestContext
.get()));
2620 rv
= gHttpHandler
->ConnMgr()->ProcessPendingQ();
2621 if (NS_FAILED(rv
)) {
2623 ("nsHttpTransaction::RemoveDispatchedAsBlocking\n"
2624 " failed to process pending queue\n"));
2628 mDispatchedAsBlocking
= false;
2631 void nsHttpTransaction::ReleaseBlockingTransaction() {
2632 RemoveDispatchedAsBlocking();
2634 ("nsHttpTransaction %p request context set to null "
2635 "in ReleaseBlockingTransaction() - was %p\n",
2636 this, mRequestContext
.get()));
2637 mRequestContext
= nullptr;
2640 void nsHttpTransaction::DisableSpdy() {
2641 mCaps
|= NS_HTTP_DISALLOW_SPDY
;
2643 // This is our clone of the connection info, not the persistent one that
2644 // is owned by the connection manager, so we're safe to change this here
2645 mConnInfo
->SetNoSpdy(true);
2649 void nsHttpTransaction::DisableHttp2ForProxy() {
2650 mCaps
|= NS_HTTP_DISALLOW_HTTP2_PROXY
;
2653 void nsHttpTransaction::DisableHttp3(bool aAllowRetryHTTPSRR
) {
2654 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2656 // mOrigConnInfo is an indicator that HTTPS RR is used, so don't mess up the
2658 // When HTTPS RR is used, PrepareConnInfoForRetry() could select other h3
2659 // record to connect.
2660 if (mOrigConnInfo
) {
2662 ("nsHttpTransaction::DisableHttp3 this=%p mOrigConnInfo=%s "
2663 "aAllowRetryHTTPSRR=%d",
2664 this, mOrigConnInfo
->HashKey().get(), aAllowRetryHTTPSRR
));
2665 if (!aAllowRetryHTTPSRR
) {
2666 mCaps
|= NS_HTTP_DISALLOW_HTTP3
;
2671 mCaps
|= NS_HTTP_DISALLOW_HTTP3
;
2673 MOZ_ASSERT(mConnInfo
);
2675 // After CloneAsDirectRoute(), http3 will be disabled.
2676 RefPtr
<nsHttpConnectionInfo
> connInfo
;
2677 mConnInfo
->CloneAsDirectRoute(getter_AddRefs(connInfo
));
2678 RemoveAlternateServiceUsedHeader(mRequestHead
);
2679 MOZ_ASSERT(!connInfo
->IsHttp3());
2680 mConnInfo
.swap(connInfo
);
2684 void nsHttpTransaction::CheckForStickyAuthScheme() {
2685 LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p", this));
2687 MOZ_ASSERT(mHaveAllHeaders
);
2688 MOZ_ASSERT(mResponseHead
);
2689 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2691 CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate
);
2692 CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate
);
2695 void nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom
const& header
) {
2696 if (mCaps
& NS_HTTP_STICKY_CONNECTION
) {
2697 LOG((" already sticky"));
2702 if (NS_FAILED(mResponseHead
->GetHeader(header
, auth
))) {
2706 if (IsStickyAuthSchemeAt(auth
)) {
2707 LOG((" connection made sticky"));
2708 // This is enough to make this transaction keep it's current connection,
2709 // prevents the connection from being released back to the pool.
2710 mCaps
|= NS_HTTP_STICKY_CONNECTION
;
2714 bool nsHttpTransaction::IsStickyAuthSchemeAt(nsACString
const& auth
) {
2716 nsAutoCString schema
;
2717 while (p
.ReadWord(schema
)) {
2718 ToLowerCase(schema
);
2720 // using a new instance because of thread safety of auth modules refcnt
2721 nsCOMPtr
<nsIHttpAuthenticator
> authenticator
;
2722 if (schema
.EqualsLiteral("negotiate")) {
2723 #ifdef MOZ_AUTH_EXTENSION
2724 authenticator
= new nsHttpNegotiateAuth();
2726 } else if (schema
.EqualsLiteral("basic")) {
2727 authenticator
= new nsHttpBasicAuth();
2728 } else if (schema
.EqualsLiteral("digest")) {
2729 authenticator
= new nsHttpDigestAuth();
2730 } else if (schema
.EqualsLiteral("ntlm")) {
2731 authenticator
= new nsHttpNTLMAuth();
2732 } else if (schema
.EqualsLiteral("mock_auth") &&
2733 PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
2734 authenticator
= new MockHttpAuth();
2736 if (authenticator
) {
2738 nsresult rv
= authenticator
->GetAuthFlags(&flags
);
2739 if (NS_SUCCEEDED(rv
) &&
2740 (flags
& nsIHttpAuthenticator::CONNECTION_BASED
)) {
2745 // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
2746 p
.SkipUntil(Tokenizer::Token::NewLine());
2747 p
.SkipWhites(Tokenizer::INCLUDE_NEW_LINE
);
2753 TimingStruct
nsHttpTransaction::Timings() {
2754 mozilla::MutexAutoLock
lock(mLock
);
2755 TimingStruct timings
= mTimings
;
2759 void nsHttpTransaction::BootstrapTimings(TimingStruct times
) {
2760 mozilla::MutexAutoLock
lock(mLock
);
2764 void nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp
,
2766 mozilla::MutexAutoLock
lock(mLock
);
2767 if (onlyIfNull
&& !mTimings
.domainLookupStart
.IsNull()) {
2768 return; // We only set the timestamp if it was previously null
2770 mTimings
.domainLookupStart
= timeStamp
;
2773 void nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp
,
2775 mozilla::MutexAutoLock
lock(mLock
);
2776 if (onlyIfNull
&& !mTimings
.domainLookupEnd
.IsNull()) {
2777 return; // We only set the timestamp if it was previously null
2779 mTimings
.domainLookupEnd
= timeStamp
;
2782 void nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp
,
2784 mozilla::MutexAutoLock
lock(mLock
);
2785 if (onlyIfNull
&& !mTimings
.connectStart
.IsNull()) {
2786 return; // We only set the timestamp if it was previously null
2788 mTimings
.connectStart
= timeStamp
;
2791 void nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp
,
2793 mozilla::MutexAutoLock
lock(mLock
);
2794 if (onlyIfNull
&& !mTimings
.connectEnd
.IsNull()) {
2795 return; // We only set the timestamp if it was previously null
2797 mTimings
.connectEnd
= timeStamp
;
2800 void nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp
,
2802 mozilla::MutexAutoLock
lock(mLock
);
2803 if (onlyIfNull
&& !mTimings
.requestStart
.IsNull()) {
2804 return; // We only set the timestamp if it was previously null
2806 mTimings
.requestStart
= timeStamp
;
2809 void nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp
,
2811 mozilla::MutexAutoLock
lock(mLock
);
2812 if (onlyIfNull
&& !mTimings
.responseStart
.IsNull()) {
2813 return; // We only set the timestamp if it was previously null
2815 mTimings
.responseStart
= timeStamp
;
2818 void nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp
,
2820 mozilla::MutexAutoLock
lock(mLock
);
2821 if (onlyIfNull
&& !mTimings
.responseEnd
.IsNull()) {
2822 return; // We only set the timestamp if it was previously null
2824 mTimings
.responseEnd
= timeStamp
;
2827 mozilla::TimeStamp
nsHttpTransaction::GetDomainLookupStart() {
2828 mozilla::MutexAutoLock
lock(mLock
);
2829 return mTimings
.domainLookupStart
;
2832 mozilla::TimeStamp
nsHttpTransaction::GetDomainLookupEnd() {
2833 mozilla::MutexAutoLock
lock(mLock
);
2834 return mTimings
.domainLookupEnd
;
2837 mozilla::TimeStamp
nsHttpTransaction::GetConnectStart() {
2838 mozilla::MutexAutoLock
lock(mLock
);
2839 return mTimings
.connectStart
;
2842 mozilla::TimeStamp
nsHttpTransaction::GetTcpConnectEnd() {
2843 mozilla::MutexAutoLock
lock(mLock
);
2844 return mTimings
.tcpConnectEnd
;
2847 mozilla::TimeStamp
nsHttpTransaction::GetSecureConnectionStart() {
2848 mozilla::MutexAutoLock
lock(mLock
);
2849 return mTimings
.secureConnectionStart
;
2852 mozilla::TimeStamp
nsHttpTransaction::GetConnectEnd() {
2853 mozilla::MutexAutoLock
lock(mLock
);
2854 return mTimings
.connectEnd
;
2857 mozilla::TimeStamp
nsHttpTransaction::GetRequestStart() {
2858 mozilla::MutexAutoLock
lock(mLock
);
2859 return mTimings
.requestStart
;
2862 mozilla::TimeStamp
nsHttpTransaction::GetResponseStart() {
2863 mozilla::MutexAutoLock
lock(mLock
);
2864 return mTimings
.responseStart
;
2867 mozilla::TimeStamp
nsHttpTransaction::GetResponseEnd() {
2868 mozilla::MutexAutoLock
lock(mLock
);
2869 return mTimings
.responseEnd
;
2872 //-----------------------------------------------------------------------------
2873 // nsHttpTransaction deletion event
2874 //-----------------------------------------------------------------------------
2876 class DeleteHttpTransaction
: public Runnable
{
2878 explicit DeleteHttpTransaction(nsHttpTransaction
* trans
)
2879 : Runnable("net::DeleteHttpTransaction"), mTrans(trans
) {}
2881 NS_IMETHOD
Run() override
{
2887 nsHttpTransaction
* mTrans
;
2890 void nsHttpTransaction::DeleteSelfOnConsumerThread() {
2891 LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
2894 if (!mConsumerTarget
||
2895 (NS_SUCCEEDED(mConsumerTarget
->IsOnCurrentThread(&val
)) && val
)) {
2898 LOG(("proxying delete to consumer thread...\n"));
2899 nsCOMPtr
<nsIRunnable
> event
= new DeleteHttpTransaction(this);
2900 if (NS_FAILED(mConsumerTarget
->Dispatch(event
, NS_DISPATCH_NORMAL
))) {
2901 NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
2906 bool nsHttpTransaction::TryToRunPacedRequest() {
2907 if (mSubmittedRatePacing
) return mPassedRatePacing
;
2909 mSubmittedRatePacing
= true;
2910 mSynchronousRatePaceRequest
= true;
2911 Unused
<< gHttpHandler
->SubmitPacedRequest(
2912 this, getter_AddRefs(mTokenBucketCancel
));
2913 mSynchronousRatePaceRequest
= false;
2914 return mPassedRatePacing
;
2917 void nsHttpTransaction::OnTokenBucketAdmitted() {
2918 mPassedRatePacing
= true;
2919 mTokenBucketCancel
= nullptr;
2921 if (!mSynchronousRatePaceRequest
) {
2922 nsresult rv
= gHttpHandler
->ConnMgr()->ProcessPendingQ(mConnInfo
);
2923 if (NS_FAILED(rv
)) {
2925 ("nsHttpTransaction::OnTokenBucketAdmitted\n"
2926 " failed to process pending queue\n"));
2931 void nsHttpTransaction::CancelPacing(nsresult reason
) {
2932 if (mTokenBucketCancel
) {
2933 mTokenBucketCancel
->Cancel(reason
);
2934 mTokenBucketCancel
= nullptr;
2938 //-----------------------------------------------------------------------------
2939 // nsHttpTransaction::nsISupports
2940 //-----------------------------------------------------------------------------
2942 NS_IMPL_ADDREF(nsHttpTransaction
)
2944 NS_IMETHODIMP_(MozExternalRefCountType
)
2945 nsHttpTransaction::Release() {
2947 MOZ_ASSERT(0 != mRefCnt
, "dup release");
2949 NS_LOG_RELEASE(this, count
, "nsHttpTransaction");
2951 mRefCnt
= 1; /* stablize */
2952 // it is essential that the transaction be destroyed on the consumer
2953 // thread (we could be holding the last reference to our consumer).
2954 DeleteSelfOnConsumerThread();
2960 NS_IMPL_QUERY_INTERFACE(nsHttpTransaction
, nsIInputStreamCallback
,
2961 nsIOutputStreamCallback
, nsITimerCallback
, nsINamed
)
2963 //-----------------------------------------------------------------------------
2964 // nsHttpTransaction::nsIInputStreamCallback
2965 //-----------------------------------------------------------------------------
2967 // called on the socket thread
2969 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream
* out
) {
2970 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2972 mConnection
->TransactionHasDataToWrite(this);
2973 nsresult rv
= mConnection
->ResumeSend();
2974 if (NS_FAILED(rv
)) NS_ERROR("ResumeSend failed");
2979 //-----------------------------------------------------------------------------
2980 // nsHttpTransaction::nsIOutputStreamCallback
2981 //-----------------------------------------------------------------------------
2983 // called on the socket thread
2985 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream
* out
) {
2986 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2987 mWaitingOnPipeOut
= false;
2989 mConnection
->TransactionHasDataToRecv(this);
2990 nsresult rv
= mConnection
->ResumeRecv();
2991 if (NS_FAILED(rv
) && rv
!= NS_BASE_STREAM_WOULD_BLOCK
) {
2992 NS_ERROR("ResumeRecv failed");
2998 void nsHttpTransaction::GetNetworkAddresses(
2999 NetAddr
& self
, NetAddr
& peer
, bool& aResolvedByTRR
,
3000 nsIRequest::TRRMode
& aEffectiveTRRMode
, TRRSkippedReason
& aSkipReason
,
3001 bool& aEchConfigUsed
) {
3002 MutexAutoLock
lock(mLock
);
3005 aResolvedByTRR
= mResolvedByTRR
;
3006 aEffectiveTRRMode
= mEffectiveTRRMode
;
3007 aSkipReason
= mTRRSkipReason
;
3008 aEchConfigUsed
= mEchConfigUsed
;
3011 bool nsHttpTransaction::Do0RTT() {
3012 LOG(("nsHttpTransaction::Do0RTT"));
3013 mEarlyDataWasAvailable
= true;
3014 if (mRequestHead
->IsSafeMethod() && !mDoNotTryEarlyData
&&
3015 (!mConnection
|| !mConnection
->IsProxyConnectInProgress())) {
3016 m0RTTInProgress
= true;
3018 return m0RTTInProgress
;
3021 nsresult
nsHttpTransaction::Finish0RTT(bool aRestart
,
3022 bool aAlpnChanged
/* ignored */) {
3023 LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart
,
3025 MOZ_ASSERT(m0RTTInProgress
);
3026 m0RTTInProgress
= false;
3028 MaybeCancelFallbackTimer();
3030 if (!aRestart
&& (mEarlyDataDisposition
== EARLY_SENT
)) {
3031 // note that if this is invoked by a 3 param version of finish0rtt this
3032 // disposition might be reverted
3033 mEarlyDataDisposition
= EARLY_ACCEPTED
;
3036 // Not to use 0RTT when this transaction is restarted next time.
3037 mDoNotTryEarlyData
= true;
3039 // Reset request headers to be sent again.
3040 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(mRequestStream
);
3042 seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, 0);
3044 return NS_ERROR_FAILURE
;
3046 } else if (!mConnected
) {
3047 // this is code that was skipped in ::ReadSegments while in 0RTT
3049 MaybeRefreshSecurityInfo();
3054 void nsHttpTransaction::Refused0RTT() {
3055 LOG(("nsHttpTransaction::Refused0RTT %p\n", this));
3056 if (mEarlyDataDisposition
== EARLY_ACCEPTED
) {
3057 mEarlyDataDisposition
= EARLY_SENT
; // undo accepted state
3061 void nsHttpTransaction::SetHttpTrailers(nsCString
& aTrailers
) {
3062 LOG(("nsHttpTransaction::SetHttpTrailers %p", this));
3063 LOG(("[\n %s\n]", aTrailers
.BeginReading()));
3065 // Introduce a local variable to minimize the critical section.
3066 UniquePtr
<nsHttpHeaderArray
> httpTrailers(new nsHttpHeaderArray());
3067 // Given it's usually null, use double-check locking for performance.
3068 if (mForTakeResponseTrailers
) {
3069 MutexAutoLock
lock(mLock
);
3070 if (mForTakeResponseTrailers
) {
3071 // Copy the trailer. |TakeResponseTrailers| gets the original trailer
3072 // until the final swap.
3073 *httpTrailers
= *mForTakeResponseTrailers
;
3078 int32_t len
= aTrailers
.Length();
3080 int32_t newline
= aTrailers
.FindCharInSet("\n", cur
);
3081 if (newline
== -1) {
3086 (newline
&& aTrailers
[newline
- 1] == '\r') ? newline
- 1 : newline
;
3087 nsDependentCSubstring
line(aTrailers
, cur
, end
);
3089 nsAutoCString hdrNameOriginal
;
3091 if (NS_SUCCEEDED(httpTrailers
->ParseHeaderLine(line
, &hdr
, &hdrNameOriginal
,
3093 if (hdr
== nsHttp::Server_Timing
) {
3094 Unused
<< httpTrailers
->SetHeaderFromNet(hdr
, hdrNameOriginal
, val
,
3102 if (httpTrailers
->Count() == 0) {
3103 // Didn't find a Server-Timing header, so get rid of this.
3104 httpTrailers
= nullptr;
3107 MutexAutoLock
lock(mLock
);
3108 std::swap(mForTakeResponseTrailers
, httpTrailers
);
3111 bool nsHttpTransaction::IsWebsocketUpgrade() {
3113 nsAutoCString upgradeHeader
;
3114 if (NS_SUCCEEDED(mRequestHead
->GetHeader(nsHttp::Upgrade
, upgradeHeader
)) &&
3115 upgradeHeader
.LowerCaseEqualsLiteral("websocket")) {
3122 void nsHttpTransaction::OnProxyConnectComplete(int32_t aResponseCode
) {
3123 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3124 MOZ_ASSERT(mConnInfo
->UsingConnect());
3126 LOG(("nsHttpTransaction::OnProxyConnectComplete %p aResponseCode=%d", this,
3129 mProxyConnectResponseCode
= aResponseCode
;
3132 int32_t nsHttpTransaction::GetProxyConnectResponseCode() {
3133 return mProxyConnectResponseCode
;
3136 void nsHttpTransaction::SetFlat407Headers(const nsACString
& aHeaders
) {
3137 MOZ_ASSERT(mProxyConnectResponseCode
== 407);
3138 MOZ_ASSERT(!mResponseHead
);
3140 LOG(("nsHttpTransaction::SetFlat407Headers %p", this));
3141 mFlat407Headers
= aHeaders
;
3144 void nsHttpTransaction::NotifyTransactionObserver(nsresult reason
) {
3145 MOZ_ASSERT(OnSocketThread());
3147 if (!mTransactionObserver
) {
3151 bool versionOk
= false;
3152 bool authOk
= false;
3154 LOG(("nsHttpTransaction::NotifyTransactionObserver %p reason %" PRIx32
3156 this, static_cast<uint32_t>(reason
), mConnection
.get()));
3159 HttpVersion version
= mConnection
->Version();
3160 versionOk
= (((reason
== NS_BASE_STREAM_CLOSED
) || (reason
== NS_OK
)) &&
3161 ((mConnection
->Version() == HttpVersion::v2_0
) ||
3162 (mConnection
->Version() == HttpVersion::v3_0
)));
3164 nsCOMPtr
<nsITLSSocketControl
> socketControl
;
3165 mConnection
->GetTLSSocketControl(getter_AddRefs(socketControl
));
3167 ("nsHttpTransaction::NotifyTransactionObserver"
3168 " version %u socketControl %p\n",
3169 static_cast<int32_t>(version
), socketControl
.get()));
3170 if (socketControl
) {
3171 authOk
= !socketControl
->GetFailedVerification();
3175 TransactionObserverResult result
;
3176 result
.versionOk() = versionOk
;
3177 result
.authOk() = authOk
;
3178 result
.closeReason() = reason
;
3180 TransactionObserverFunc obs
= nullptr;
3181 std::swap(obs
, mTransactionObserver
);
3182 obs(std::move(result
));
3185 void nsHttpTransaction::UpdateConnectionInfo(nsHttpConnectionInfo
* aConnInfo
) {
3186 MOZ_ASSERT(aConnInfo
);
3189 MOZ_ASSERT(false, "Should not update conn info after activated");
3193 mOrigConnInfo
= mConnInfo
->Clone();
3194 mConnInfo
= aConnInfo
;
3197 nsresult
nsHttpTransaction::OnHTTPSRRAvailable(
3198 nsIDNSHTTPSSVCRecord
* aHTTPSSVCRecord
,
3199 nsISVCBRecord
* aHighestPriorityRecord
) {
3200 LOG(("nsHttpTransaction::OnHTTPSRRAvailable [this=%p] mActivated=%d", this,
3202 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3205 MutexAutoLock
lock(mLock
);
3206 MakeDontWaitHTTPSRR();
3207 mDNSRequest
= nullptr;
3211 LOG(("The transaction is not interested in HTTPS record anymore."));
3215 RefPtr
<nsHttpTransaction
> deleteProtector(this);
3217 uint32_t receivedStage
= HTTPSSVC_NO_USABLE_RECORD
;
3218 // Make sure we set the correct value to |mHTTPSSVCReceivedStage|, since we
3219 // also use this value to indicate whether HTTPS RR is used or not.
3220 auto updateHTTPSSVCReceivedStage
= MakeScopeExit([&] {
3221 mHTTPSSVCReceivedStage
= receivedStage
;
3223 // In the case that an HTTPS RR is unavailable, we should call
3224 // ProcessPendingQ to make sure this transition to be processed soon.
3225 if (!mHTTPSSVCRecord
) {
3226 gHttpHandler
->ConnMgr()->ProcessPendingQ(mConnInfo
);
3230 nsCOMPtr
<nsIDNSHTTPSSVCRecord
> record
= aHTTPSSVCRecord
;
3232 return NS_ERROR_FAILURE
;
3235 bool hasIPAddress
= false;
3236 Unused
<< record
->GetHasIPAddresses(&hasIPAddress
);
3239 receivedStage
= hasIPAddress
? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2
3240 : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_2
;
3244 receivedStage
= hasIPAddress
? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_1
3245 : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_1
;
3247 nsCOMPtr
<nsISVCBRecord
> svcbRecord
= aHighestPriorityRecord
;
3249 LOG((" no usable record!"));
3250 nsCOMPtr
<nsIDNSService
> dns
= do_GetService(NS_DNSSERVICE_CONTRACTID
);
3251 bool allRecordsExcluded
= false;
3252 Unused
<< record
->GetAllRecordsExcluded(&allRecordsExcluded
);
3253 Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON
,
3255 ? HTTPSSVC_CONNECTION_ALL_RECORDS_EXCLUDED
3256 : HTTPSSVC_CONNECTION_NO_USABLE_RECORD
);
3257 if (allRecordsExcluded
&&
3258 StaticPrefs::network_dns_httpssvc_reset_exclustion_list() && dns
) {
3259 Unused
<< dns
->ResetExcludedSVCDomainName(mConnInfo
->GetOrigin());
3260 if (NS_FAILED(record
->GetServiceModeRecord(mCaps
& NS_HTTP_DISALLOW_SPDY
,
3261 mCaps
& NS_HTTP_DISALLOW_HTTP3
,
3262 getter_AddRefs(svcbRecord
)))) {
3263 return NS_ERROR_FAILURE
;
3266 return NS_ERROR_FAILURE
;
3270 // Remember this RR set. In the case that the connection establishment failed,
3271 // we will use other records to retry.
3272 mHTTPSSVCRecord
= record
;
3274 RefPtr
<nsHttpConnectionInfo
> newInfo
=
3275 mConnInfo
->CloneAndAdoptHTTPSSVCRecord(svcbRecord
);
3276 bool needFastFallback
= newInfo
->IsHttp3();
3277 bool foundInPendingQ
= gHttpHandler
->ConnMgr()->RemoveTransFromConnEntry(
3278 this, mHashKeyOfConnectionEntry
);
3280 // Adopt the new connection info, so this transaction will be added into the
3281 // new connection entry.
3282 UpdateConnectionInfo(newInfo
);
3284 // If this transaction is sucessfully removed from a connection entry, we call
3285 // ProcessNewTransaction to process it immediately.
3286 // If not, this means that nsHttpTransaction::OnHTTPSRRAvailable happens
3287 // before ProcessNewTransaction and this transaction will be processed later.
3288 if (foundInPendingQ
) {
3289 if (NS_FAILED(gHttpHandler
->ConnMgr()->ProcessNewTransaction(this))) {
3290 LOG(("Failed to process this transaction."));
3291 return NS_ERROR_FAILURE
;
3295 // In case we already have mHttp3BackupTimer, cancel it.
3296 MaybeCancelFallbackTimer();
3298 if (needFastFallback
) {
3299 CreateAndStartTimer(
3300 mFastFallbackTimer
, this,
3301 StaticPrefs::network_dns_httpssvc_http3_fast_fallback_timeout());
3304 // Prefetch the A/AAAA records of the target name.
3305 nsAutoCString targetName
;
3306 Unused
<< svcbRecord
->GetName(targetName
);
3308 mResolver
->PrefetchAddrRecord(targetName
, mCaps
& NS_HTTP_REFRESH_DNS
);
3311 // echConfig is used, so initialize the retry counters to 0.
3312 if (!mConnInfo
->GetEchConfig().IsEmpty()) {
3313 mEchRetryCounterMap
.InsertOrUpdate(
3314 Telemetry::TRANSACTION_ECH_RETRY_WITH_ECH_COUNT
, 0);
3315 mEchRetryCounterMap
.InsertOrUpdate(
3316 Telemetry::TRANSACTION_ECH_RETRY_WITHOUT_ECH_COUNT
, 0);
3317 mEchRetryCounterMap
.InsertOrUpdate(
3318 Telemetry::TRANSACTION_ECH_RETRY_ECH_FAILED_COUNT
, 0);
3319 mEchRetryCounterMap
.InsertOrUpdate(
3320 Telemetry::TRANSACTION_ECH_RETRY_OTHERS_COUNT
, 0);
3326 uint32_t nsHttpTransaction::HTTPSSVCReceivedStage() {
3327 return mHTTPSSVCReceivedStage
;
3330 void nsHttpTransaction::MaybeCancelFallbackTimer() {
3331 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
3333 if (mFastFallbackTimer
) {
3334 mFastFallbackTimer
->Cancel();
3335 mFastFallbackTimer
= nullptr;
3338 if (mHttp3BackupTimer
) {
3339 mHttp3BackupTimer
->Cancel();
3340 mHttp3BackupTimer
= nullptr;
3344 void nsHttpTransaction::OnBackupConnectionReady(bool aTriggeredByHTTPSRR
) {
3346 ("nsHttpTransaction::OnBackupConnectionReady [%p] mConnected=%d "
3347 "aTriggeredByHTTPSRR=%d",
3348 this, mConnected
, aTriggeredByHTTPSRR
));
3349 if (mConnected
|| mClosed
|| mRestarted
) {
3353 // If HTTPS RR is in play, don't mess up the fallback mechansim of HTTPS RR.
3354 if (!aTriggeredByHTTPSRR
&& mOrigConnInfo
) {
3359 // The transaction will only be restarted when we already have a connection.
3360 // When there is no connection, this transaction will be moved to another
3361 // connection entry.
3362 SetRestartReason(aTriggeredByHTTPSRR
3363 ? TRANSACTION_RESTART_HTTPS_RR_FAST_FALLBACK
3364 : TRANSACTION_RESTART_HTTP3_FAST_FALLBACK
);
3367 mCaps
|= NS_HTTP_DISALLOW_HTTP3
;
3369 // Need to backup the origin conn info, since UpdateConnectionInfo() will be
3370 // called in HandleFallback() and mOrigConnInfo will be
3372 RefPtr
<nsHttpConnectionInfo
> backup
= mOrigConnInfo
;
3373 HandleFallback(mBackupConnInfo
);
3374 mOrigConnInfo
.swap(backup
);
3376 RemoveAlternateServiceUsedHeader(mRequestHead
);
3379 if (mBackupConnInfo
) {
3380 const nsCString
& host
= mBackupConnInfo
->GetRoutedHost().IsEmpty()
3381 ? mBackupConnInfo
->GetOrigin()
3382 : mBackupConnInfo
->GetRoutedHost();
3383 mResolver
->PrefetchAddrRecord(host
, Caps() & NS_HTTP_REFRESH_DNS
);
3386 if (!aTriggeredByHTTPSRR
) {
3387 // We are about to use this backup connection. We shoud not try to use
3388 // HTTPS RR at this point.
3390 mResolver
= nullptr;
3395 static void CreateBackupConnection(
3396 nsHttpConnectionInfo
* aBackupConnInfo
, nsIInterfaceRequestor
* aCallbacks
,
3397 uint32_t aCaps
, std::function
<void(bool)>&& aResultCallback
) {
3398 aBackupConnInfo
->SetFallbackConnection(true);
3399 RefPtr
<SpeculativeTransaction
> trans
= new SpeculativeTransaction(
3400 aBackupConnInfo
, aCallbacks
, aCaps
| NS_HTTP_DISALLOW_HTTP3
,
3401 std::move(aResultCallback
));
3403 StaticPrefs::network_http_http3_parallel_fallback_conn_limit();
3405 trans
->SetParallelSpeculativeConnectLimit(limit
);
3406 trans
->SetIgnoreIdle(true);
3408 gHttpHandler
->ConnMgr()->DoFallbackConnection(trans
, false);
3411 void nsHttpTransaction::OnHttp3BackupTimer() {
3412 LOG(("nsHttpTransaction::OnHttp3BackupTimer [%p]", this));
3413 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3414 MOZ_ASSERT(mConnInfo
->IsHttp3());
3416 mHttp3BackupTimer
= nullptr;
3418 mConnInfo
->CloneAsDirectRoute(getter_AddRefs(mBackupConnInfo
));
3419 MOZ_ASSERT(!mBackupConnInfo
->IsHttp3());
3421 RefPtr
<nsHttpTransaction
> self
= this;
3422 auto callback
= [self
](bool aSucceded
) {
3424 self
->OnBackupConnectionReady(false);
3428 CreateBackupConnection(mBackupConnInfo
, mCallbacks
, mCaps
,
3429 std::move(callback
));
3432 void nsHttpTransaction::OnFastFallbackTimer() {
3433 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3434 LOG(("nsHttpTransaction::OnFastFallbackTimer [%p] mConnected=%d", this,
3437 mFastFallbackTimer
= nullptr;
3439 MOZ_ASSERT(mHTTPSSVCRecord
&& mOrigConnInfo
);
3440 if (!mHTTPSSVCRecord
|| !mOrigConnInfo
) {
3444 bool echConfigUsed
= gHttpHandler
->EchConfigEnabled(mConnInfo
->IsHttp3()) &&
3445 !mConnInfo
->GetEchConfig().IsEmpty();
3446 mBackupConnInfo
= PrepareFastFallbackConnInfo(echConfigUsed
);
3447 if (!mBackupConnInfo
) {
3451 MOZ_ASSERT(!mBackupConnInfo
->IsHttp3());
3453 RefPtr
<nsHttpTransaction
> self
= this;
3454 auto callback
= [self
](bool aSucceded
) {
3459 self
->mFastFallbackTriggered
= true;
3460 self
->OnBackupConnectionReady(true);
3463 CreateBackupConnection(mBackupConnInfo
, mCallbacks
, mCaps
,
3464 std::move(callback
));
3467 void nsHttpTransaction::HandleFallback(
3468 nsHttpConnectionInfo
* aFallbackConnInfo
) {
3470 MOZ_ASSERT(mActivated
);
3471 // Close the transaction with NS_ERROR_NET_RESET, since we know doing this
3472 // will make transaction to be restarted.
3473 mConnection
->CloseTransaction(this, NS_ERROR_NET_RESET
);
3477 if (!aFallbackConnInfo
) {
3478 // Nothing to do here.
3482 LOG(("nsHttpTransaction %p HandleFallback to connInfo[%s]", this,
3483 aFallbackConnInfo
->HashKey().get()));
3485 bool foundInPendingQ
= gHttpHandler
->ConnMgr()->RemoveTransFromConnEntry(
3486 this, mHashKeyOfConnectionEntry
);
3487 if (!foundInPendingQ
) {
3488 MOZ_ASSERT(false, "transaction not in entry");
3492 // rewind streams in case we already wrote out the request
3493 nsCOMPtr
<nsISeekableStream
> seekable
= do_QueryInterface(mRequestStream
);
3495 seekable
->Seek(nsISeekableStream::NS_SEEK_SET
, 0);
3498 UpdateConnectionInfo(aFallbackConnInfo
);
3499 Unused
<< gHttpHandler
->ConnMgr()->ProcessNewTransaction(this);
3503 nsHttpTransaction::Notify(nsITimer
* aTimer
) {
3504 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
3506 if (!gHttpHandler
|| !gHttpHandler
->ConnMgr()) {
3510 if (aTimer
== mFastFallbackTimer
) {
3511 OnFastFallbackTimer();
3512 } else if (aTimer
== mHttp3BackupTimer
) {
3513 OnHttp3BackupTimer();
3520 nsHttpTransaction::GetName(nsACString
& aName
) {
3521 aName
.AssignLiteral("nsHttpTransaction");
3525 bool nsHttpTransaction::GetSupportsHTTP3() { return mSupportsHTTP3
; }
3527 const int64_t TELEMETRY_REQUEST_SIZE_10M
= (int64_t)10 * (int64_t)(1 << 20);
3528 const int64_t TELEMETRY_REQUEST_SIZE_50M
= (int64_t)50 * (int64_t)(1 << 20);
3529 const int64_t TELEMETRY_REQUEST_SIZE_100M
= (int64_t)100 * (int64_t)(1 << 20);
3531 void nsHttpTransaction::CollectTelemetryForUploads() {
3532 if ((mRequestSize
< TELEMETRY_REQUEST_SIZE_10M
) ||
3533 mTimings
.requestStart
.IsNull() || mTimings
.responseStart
.IsNull()) {
3537 nsAutoCString
protocolVersion(nsHttp::GetProtocolVersion(mHttpVersion
));
3538 TimeDuration sendTime
= mTimings
.responseStart
- mTimings
.requestStart
;
3539 double megabits
= static_cast<double>(mRequestSize
) * 8.0 / 1000000.0;
3540 uint32_t mpbs
= static_cast<uint32_t>(megabits
/ sendTime
.ToSeconds());
3541 Telemetry::Accumulate(Telemetry::HTTP_UPLOAD_BANDWIDTH_MBPS
, protocolVersion
,
3544 if ((mHttpVersion
== HttpVersion::v3_0
) || mSupportsHTTP3
) {
3545 nsAutoCString
key((mHttpVersion
== HttpVersion::v3_0
) ? "uses_http3"
3546 : "supports_http3");
3547 auto hist
= Telemetry::HTTP3_UPLOAD_TIME_10M_100M
;
3548 if (mRequestSize
<= TELEMETRY_REQUEST_SIZE_50M
) {
3549 key
.Append("_10_50"_ns
);
3550 } else if (mRequestSize
<= TELEMETRY_REQUEST_SIZE_100M
) {
3551 key
.Append("_50_100"_ns
);
3553 hist
= Telemetry::HTTP3_UPLOAD_TIME_GT_100M
;
3556 Telemetry::AccumulateTimeDelta(hist
, key
, mTimings
.requestStart
,
3557 mTimings
.responseStart
);
3561 void nsHttpTransaction::GetHashKeyOfConnectionEntry(nsACString
& aResult
) {
3562 MutexAutoLock
lock(mLock
);
3563 aResult
.Assign(mHashKeyOfConnectionEntry
);
3566 void nsHttpTransaction::SetIsForWebTransport(bool aIsForWebTransport
) {
3567 mIsForWebTransport
= aIsForWebTransport
;
3570 void nsHttpTransaction::RemoveConnection() {
3571 MutexAutoLock
lock(mLock
);
3572 mConnection
= nullptr;
3575 } // namespace mozilla::net