Bug 1814798 - pt 2. Add a PHCManager component to control PHC r=glandium,emilio
[gecko.git] / netwerk / protocol / http / nsHttpTransaction.cpp
blob999e1de7fb772169773365565156879c5ce4341b
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"
10 #include <algorithm>
11 #include <utility>
13 #include "HttpLog.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"
24 #include "nsCRT.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"
34 #endif
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"
49 #include "nsIPipe.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"
56 #include "nsNetCID.h"
57 #include "nsNetUtil.h"
58 #include "nsQueryObject.h"
59 #include "nsSocketTransportService2.h"
60 #include "nsStringStream.h"
61 #include "nsTransportUtils.h"
62 #include "sslerr.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));
82 #ifdef MOZ_VALGRIND
83 memset(&mSelfAddr, 0, sizeof(NetAddr));
84 memset(&mPeerAddr, 0, sizeof(NetAddr));
85 #endif
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) {
96 return;
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;
107 if (mConnection) {
108 mConnection->TransactionHasDataToRecv(this);
109 nsresult rv = mConnection->ResumeRecv();
110 if (NS_FAILED(rv)) {
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) {
124 if (mClosed) {
125 return;
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) {
140 ResumeReading();
145 class ReleaseOnSocketThread final : public mozilla::Runnable {
146 public:
147 explicit ReleaseOnSocketThread(nsTArray<nsCOMPtr<nsISupports>>&& aDoomed)
148 : Runnable("ReleaseOnSocketThread"), mDoomed(std::move(aDoomed)) {}
150 NS_IMETHOD
151 Run() override {
152 mDoomed.Clear();
153 return NS_OK;
156 void Dispatch() {
157 nsCOMPtr<nsIEventTarget> sts =
158 do_GetService("@mozilla.org/network/socket-transport-service;1");
159 Unused << sts->Dispatch(this, nsIEventTarget::DISPATCH_NORMAL);
162 private:
163 virtual ~ReleaseOnSocketThread() = default;
165 nsTArray<nsCOMPtr<nsISupports>> mDoomed;
168 nsHttpTransaction::~nsHttpTransaction() {
169 LOG(("Destroying nsHttpTransaction @%p\n", this));
171 if (mPushedStream) {
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;
191 if (mConnection) {
192 arrayToRelease.AppendElement(mConnection.forget());
195 if (!arrayToRelease.IsEmpty()) {
196 RefPtr<ReleaseOnSocketThread> r =
197 new ReleaseOnSocketThread(std::move(arrayToRelease));
198 r->Dispatch();
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) {
213 nsresult rv;
215 LOG1(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
217 MOZ_ASSERT(cinfo);
218 MOZ_ASSERT(requestHead);
219 MOZ_ASSERT(target);
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,
239 target);
241 if (NS_FAILED(rv)) return rv;
243 mConnInfo = cinfo;
244 mCallbacks = callbacks;
245 mConsumerTarget = target;
246 mCaps = caps;
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()) {
252 mNoContent = true;
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());
265 LOG1(("]\n"));
268 // report the request header
269 if (gHttpHandler->HttpActivityDistributorActivated()) {
270 nsCString requestBuf(mReqHeaderBuf);
271 NS_DispatchToMainThread(NS_NewRunnableFunction(
272 "ObserveHttpActivityWithArgs", [channelId(mChannelId), requestBuf]() {
273 if (!gHttpHandler) {
274 return;
276 gHttpHandler->ObserveHttpActivityWithArgs(
277 HttpActivityArgs(channelId),
278 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
279 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, requestBuf);
280 }));
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),
316 stream.forget(),
317 nsIOService::gDefaultSegmentSize);
318 if (NS_FAILED(rv)) return rv;
319 } else {
320 mRequestStream = headers;
323 nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(eventsink);
324 if (throttled) {
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
332 // initialization
333 if (NS_SUCCEEDED(rv)) {
334 MOZ_ASSERT(wrappedStream != nullptr);
335 LOG(
336 ("nsHttpTransaction::Init %p wrapping input stream using throttle "
337 "queue %p\n",
338 this, queue.get()));
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)
347 : -1;
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();
357 MOZ_ASSERT(trans);
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)) ||
364 forceUseHTTPSRR) {
365 nsCOMPtr<nsIEventTarget> target;
366 Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
367 if (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);
383 if (NS_FAILED(rv)) {
384 MakeDontWaitHTTPSRR();
390 RefPtr<nsHttpChannel> httpChannel = do_QueryObject(eventsink);
391 RefPtr<WebTransportSessionEventListener> listener =
392 httpChannel ? httpChannel->GetWebTransportSessionEventListener()
393 : nullptr;
394 if (listener) {
395 mWebTransportSessionEventListener = std::move(listener);
398 return NS_OK;
401 static inline void CreateAndStartTimer(nsCOMPtr<nsITimer>& aTimer,
402 nsITimerCallback* aCallback,
403 uint32_t aTimeout) {
404 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
405 MOZ_ASSERT(!aTimer);
407 if (!aTimeout) {
408 return;
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,
436 nsIRequest** pump) {
437 RefPtr<nsInputStreamPump> transactionPump;
438 nsresult rv =
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);
446 return NS_OK;
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);
461 return;
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
474 // canceled
475 if (!mHaveAllHeaders) {
476 NS_WARNING("response headers not available or incomplete");
477 return nullptr;
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);
511 mConnection = conn;
512 if (mConnection) {
513 mIsHttp3Used = mConnection->Version() == HttpVersion::v3_0;
518 void nsHttpTransaction::OnActivated() {
519 MOZ_ASSERT(OnSocketThread());
521 if (mActivated) {
522 return;
525 if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
526 HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
527 if (hta) {
528 hta->IncrementHttpTransaction(mTrafficCategory);
530 if (mConnection) {
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);
548 mActivated = true;
549 gHttpHandler->ConnMgr()->AddActiveTransaction(this);
552 void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor** cb) {
553 MutexAutoLock lock(mLock);
554 nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
555 tmp.forget(cb);
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) {
580 if (mConnection) {
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;
645 int64_t progressMax;
647 if (status == NS_NET_STATUS_SENDING_TO) {
648 // suppress progress when only writing request headers
649 if (!mHasRequestBody) {
650 LOG1(
651 ("nsHttpTransaction::OnTransportStatus %p "
652 "SENDING_TO without request body\n",
653 this));
654 return;
657 if (mReader) {
658 // A mRequestStream method is on the stack - wait.
659 LOG(
660 ("nsHttpTransaction::OnSocketStatus [this=%p] "
661 "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",
662 this));
663 // its ok to coalesce several of these into one deferred event
664 mDeferredSendProgress = true;
665 return;
668 nsCOMPtr<nsITellableStream> tellable = do_QueryInterface(mRequestStream);
669 if (!tellable) {
670 LOG1(
671 ("nsHttpTransaction::OnTransportStatus %p "
672 "SENDING_TO without tellable request stream\n",
673 this));
674 MOZ_ASSERT(
675 !mRequestStream,
676 "mRequestStream should be tellable as it was wrapped in "
677 "nsBufferedInputStream, which provides the tellable interface even "
678 "when wrapping non-tellable streams.");
679 progress = 0;
680 } else {
681 int64_t prog = 0;
682 tellable->Tell(&prog);
683 progress = prog;
686 // when uploading, we include the request headers in the progress
687 // notifications.
688 progressMax = mRequestSize;
689 } else {
690 progress = 0;
691 progressMax = 0;
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);
718 if (NS_FAILED(rv)) {
719 trans->MaybeRefreshSecurityInfo();
720 return rv;
723 LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans, *countRead));
725 trans->mSentData = true;
726 return NS_OK;
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) {
736 *countRead = 0;
737 return mStatus;
740 if (!m0RTTInProgress) {
741 MaybeCancelFallbackTimer();
744 if (!mConnected && !m0RTTInProgress) {
745 mConnected = true;
746 MaybeRefreshSecurityInfo();
749 mDeferredSendProgress = false;
750 mReader = reader;
751 nsresult rv =
752 mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
753 mReader = nullptr;
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;
769 if (mForceRestart) {
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
773 // ERETARGETED
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);
784 if (asyncIn) {
785 nsCOMPtr<nsIEventTarget> target;
786 Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
787 if (target) {
788 asyncIn->AsyncWait(this, 0, 0, target);
789 } else {
790 NS_ERROR("no socket thread event target");
791 rv = NS_ERROR_UNEXPECTED;
796 return rv;
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;
818 nsresult rv;
820 // OK, now let the caller fill this segment with data.
822 rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
823 if (NS_FAILED(rv)) {
824 trans->MaybeRefreshSecurityInfo();
825 return rv; // caller didn't want to write anything
828 LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans,
829 *countWritten));
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.
855 return false;
858 if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) {
859 // We are not obligated to throttle
860 return false;
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
866 ") this=%p",
867 mContentRead, this));
868 return false;
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.
879 return false;
882 return true;
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);
893 return;
896 if (mConnection) {
897 mConnection->DontReuse();
901 nsresult nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter* writer,
902 uint32_t count,
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();
917 } else {
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;
936 mWriter = writer;
938 if (!mPipeOut) {
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));
948 nsresult rv =
949 mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
951 mWriter = nullptr;
953 if (mForceRestart) {
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
957 // ERETARGETED
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));
969 if (target) {
970 mPipeOut->AsyncWait(this, 0, 0, target);
971 mWaitingOnPipeOut = true;
972 } else {
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;
981 return rv;
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);
1024 if (entry) {
1025 RefPtr<Http2PushedStreamWrapper> stream = entry.Data();
1026 entry.Remove();
1027 return stream.forget();
1030 return nullptr;
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();
1048 return;
1051 mIDToStreamMap.WithEntryHandle(stream->StreamID(), [&](auto&& entry) {
1052 MOZ_ASSERT(!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() {
1066 return nullptr;
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;
1075 break;
1076 case NS_ERROR_CONNECTION_REFUSED:
1077 reason = HTTPSSVC_CONNECTION_UNREACHABLE;
1078 break;
1079 default:
1080 if (m421Received) {
1081 reason = HTTPSSVC_CONNECTION_421_RECEIVED;
1082 } else if (NS_ERROR_GET_MODULE(aErrorCode) == NS_ERROR_MODULE_SECURITY) {
1083 reason = HTTPSSVC_CONNECTION_SECURITY_ERROR;
1085 break;
1087 return reason;
1090 bool nsHttpTransaction::PrepareSVCBRecordsForRetry(
1091 const nsACString& aFailedDomainName, const nsACString& aFailedAlpn,
1092 bool& aAllRecordsHaveEchConfig) {
1093 MOZ_ASSERT(mRecordsForRetry.IsEmpty());
1094 if (!mHTTPSSVCRecord) {
1095 return false;
1098 // If we already failed to connect with h3, don't select records that supports
1099 // h3.
1100 bool noHttp3 = mCaps & NS_HTTP_DISALLOW_HTTP3;
1102 bool unused;
1103 nsTArray<RefPtr<nsISVCBRecord>> records;
1104 Unused << mHTTPSSVCRecord->GetAllRecordsWithEchConfig(
1105 mCaps & NS_HTTP_DISALLOW_SPDY, noHttp3, &aAllRecordsHaveEchConfig,
1106 &unused, records);
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
1110 // http3 alpn.
1112 // If not all records have echConfig, we'll directly fallback to the origin
1113 // server.
1114 if (!aAllRecordsHaveEchConfig) {
1115 return false;
1118 // Take the records behind the failed one and put them into mRecordsForRetry.
1119 for (const auto& record : records) {
1120 nsAutoCString name;
1121 record->GetName(name);
1122 nsAutoCString alpn;
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
1127 // record.
1128 if (NS_FAILED(rv) || alpn == aFailedAlpn) {
1129 continue;
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) {
1160 LOG(
1161 ("nsHttpTransaction::PrepareFastFallbackConnInfo [this=%p] no record "
1162 "can be used",
1163 this));
1164 return nullptr;
1167 if (mOrigConnInfo->IsHttp3()) {
1168 mOrigConnInfo->CloneAsDirectRoute(getter_AddRefs(fallbackConnInfo));
1169 } else {
1170 fallbackConnInfo = mOrigConnInfo;
1172 return fallbackConnInfo.forget();
1175 fallbackConnInfo =
1176 mOrigConnInfo->CloneAndAdoptHTTPSSVCRecord(fastFallbackRecord);
1177 return fallbackConnInfo.forget();
1180 void nsHttpTransaction::PrepareConnInfoForRetry(nsresult aReason) {
1181 LOG(("nsHttpTransaction::PrepareConnInfoForRetry [this=%p reason=%" PRIx32
1182 "]",
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);
1194 return;
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));
1206 mConnInfo = ci;
1210 if (!echConfigUsed) {
1211 LOG((" echConfig is not used, fallback to origin conn info"));
1212 useOrigConnInfoToRetry();
1213 return;
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");
1220 if (entry) {
1221 *entry += 1;
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;
1230 return;
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;
1238 if (mConnection) {
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;
1252 return;
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)) {
1269 LOG(
1270 (" Can't find other records with echConfig, "
1271 "allRecordsHaveEchConfig=%d",
1272 allRecordsHaveEchConfig));
1273 if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed() ||
1274 !allRecordsHaveEchConfig) {
1275 useOrigConnInfoToRetry();
1277 return;
1279 } else {
1280 LOG((" No available records to retry"));
1281 if (gHttpHandler->FallbackToOriginIfConfigsAreECHAndAllFailed()) {
1282 useOrigConnInfoToRetry();
1284 return;
1288 if (LOG5_ENABLED()) {
1289 LOG(("SvcDomainName to retry: ["));
1290 for (const auto& r : mRecordsForRetry) {
1291 nsAutoCString name;
1292 r->GetName(name);
1293 nsAutoCString alpn;
1294 r->GetSelectedAlpn(alpn);
1295 LOG((" name=%s alpn=%s", name.get(), alpn.get()));
1297 LOG(("]"));
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)) {
1310 return;
1313 Telemetry::Accumulate(Telemetry::DNS_HTTPSSVC_CONNECTION_FAILED_REASON,
1314 ErrorCodeToFailedReason(aReason));
1316 nsCOMPtr<nsIDNSService> dns = do_GetService(NS_DNSSERVICE_CONTRACTID);
1317 if (dns) {
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(),
1324 failedHost);
1328 bool nsHttpTransaction::ShouldRestartOn0RttError(nsresult reason) {
1329 LOG(
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) {
1338 if (!StaticPrefs::
1339 network_http_remove_resumption_token_when_early_data_failed()) {
1340 return;
1342 if (!aSecurityInfo) {
1343 return;
1345 nsAutoCString key;
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;
1362 if (!mClosed) {
1363 gHttpHandler->ConnMgr()->RemoveActiveTransaction(this);
1364 mActivated = false;
1367 if (mDNSRequest) {
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));
1377 return;
1380 if (mClosed) {
1381 LOG((" already closed\n"));
1382 return;
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;
1409 if (mConnection) {
1410 connReused = mConnection->IsReused();
1411 isHttp2or3 = mConnection->Version() >= HttpVersion::v2_0;
1412 if (!mConnected) {
1413 MaybeRefreshSecurityInfo();
1416 mConnected = false;
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 ||
1450 reason ==
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();
1463 mContentRead = 0;
1464 mContentLength = -1;
1465 delete mChunkedDecoder;
1466 mChunkedDecoder = nullptr;
1467 mHaveStatusLine = false;
1468 mHaveAllHeaders = false;
1469 mHttpResponseMatched = false;
1470 mResponseIsComplete = false;
1471 mDidContentStart = false;
1472 mNoContent = false;
1473 mSentData = false;
1474 mReceivedData = false;
1475 mSupportsHTTP3 = false;
1476 LOG(("transaction force restarted\n"));
1477 return;
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
1491 // info.
1492 shouldRestartTransactionForHTTPSRR &= !reallySentData;
1494 if (reason ==
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;
1504 LOG(
1505 ("transaction will be restarted with the fallback connection info "
1506 "key=%s",
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())) {
1546 return;
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
1551 // accessed again.
1552 if (!mConnInfo) {
1553 mConnInfo.swap(mOrigConnInfo);
1554 MOZ_ASSERT(mConnInfo);
1559 Telemetry::Accumulate(Telemetry::HTTP_TRANSACTION_RESTART_REASON,
1560 mRestartReason);
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
1565 // closed.
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
1577 // partial.
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"));
1600 if (mConnection) {
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"));
1630 relConn = false;
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"));
1641 relConn = false;
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(
1674 {mpbs});
1675 break;
1676 case HttpVersion::v2_0:
1677 glean::networking::http_2_download_throughput.AccumulateSamples(
1678 {mpbs});
1679 break;
1680 case HttpVersion::v3_0:
1681 glean::networking::http_3_download_throughput.AccumulateSamples(
1682 {mpbs});
1683 break;
1684 default:
1685 break;
1690 if (mTrafficCategory != HttpTrafficCategory::eInvalid) {
1691 HttpTrafficAnalyzer* hta = gHttpHandler->GetHttpTrafficAnalyzer();
1692 if (hta) {
1693 hta->AccumulateHttpTransferredSize(mTrafficCategory, mTransferSize,
1694 mContentRead);
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;
1708 if (isHttp2or3 &&
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;
1715 mStatus = reason;
1716 mTransactionDone = true; // forcibly flag the transaction as complete
1717 mClosed = true;
1718 if (mResolver) {
1719 mResolver->Close();
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()),
1735 entry.GetData());
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 {
1748 return false;
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) {
1766 if (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));
1790 if (mRequestHead) {
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;
1794 if (NS_SUCCEEDED(
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;
1816 if (mConnection) {
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
1825 // to the next
1826 mReuseOnRestart = false;
1828 if (!mDoNotRemoveAltSvc &&
1829 (!mConnInfo->GetRoutedHost().IsEmpty() || mConnInfo->IsHttp3()) &&
1830 !mDontRetryWithDirectRoute) {
1831 RefPtr<nsHttpConnectionInfo> ci;
1832 mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
1833 mConnInfo = ci;
1834 RemoveAlternateServiceUsedHeader(mRequestHead);
1837 // Reset mDoNotRemoveAltSvc for the next try.
1838 mDoNotRemoveAltSvc = false;
1839 mEarlyDataWasAvailable = false;
1840 mRestarted = true;
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) ==
1891 0) {
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.
1899 return nullptr;
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;
1907 while (len > 0) {
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);
1914 return nullptr;
1917 // whole HTTPHeader sequence found
1918 return buf;
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
1924 // other browsers
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"));
1929 return buf;
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
1934 // other browsers
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"));
1939 return buf;
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"));
1949 return buf;
1952 if (!nsCRT::IsAsciiSpace(*buf)) firstByte = false;
1953 buf++;
1954 len--;
1956 return nullptr;
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;
1970 } else {
1971 rv = mResponseHead->ParseHeaderLine(line);
1973 return rv;
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
1983 // of mLineBuf.
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)) {
1989 return 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,
2008 referrerPolicy);
2010 if (NS_SUCCEEDED(rv) && !linkHeader.IsEmpty()) {
2011 nsCString cspHeader;
2012 Unused << mResponseHead->GetHeader(nsHttp::Content_Security_Policy,
2013 cspHeader);
2015 nsCOMPtr<nsIEarlyHintObserver> earlyHint;
2017 MutexAutoLock lock(mLock);
2018 earlyHint = mEarlyHintObserver;
2020 if (earlyHint) {
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();
2040 return NS_OK;
2042 if (!mConnection->IsProxyConnectInProgress()) {
2043 MutexAutoLock lock(mLock);
2044 mEarlyHintObserver = nullptr;
2046 mHaveAllHeaders = true;
2048 return NS_OK;
2051 nsresult nsHttpTransaction::ParseHead(char* buf, uint32_t count,
2052 uint32_t* countRead) {
2053 nsresult rv;
2054 uint32_t len;
2055 char* eol;
2057 LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
2059 *countRead = 0;
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);
2087 if (!p) {
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;
2096 return NS_OK;
2098 if (p > buf) {
2099 // skip over the junk
2100 mInvalidResponseBytesRead += p - buf;
2101 *countRead = p - buf;
2102 buf = p;
2104 } else {
2105 char* p = LocateHttpStart(buf, count, false);
2106 if (p) {
2107 mInvalidResponseBytesRead += p - buf;
2108 *countRead = p - buf;
2109 buf = p;
2110 mHttpResponseMatched = true;
2111 } else {
2112 mInvalidResponseBytesRead += count;
2113 *countRead = count;
2114 if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
2115 LOG(
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;
2122 return NS_OK;
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))) !=
2130 nullptr) {
2131 // found line in range [buf:eol]
2132 len = eol - buf + 1;
2134 *countRead += len;
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;
2145 // skip over line
2146 buf = eol + 1;
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)) {
2157 *countRead = count;
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;
2164 return NS_OK;
2167 bool nsHttpTransaction::HandleWebTransportResponse(uint16_t aStatus) {
2168 MOZ_ASSERT(mIsForWebTransport);
2169 if (!(aStatus >= 200 && aStatus < 300)) {
2170 return false;
2173 RefPtr<Http3WebTransportSession> wtSession =
2174 mConnection->GetWebTransportSession(this);
2175 if (!wtSession) {
2176 return false;
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);
2190 return true;
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;
2202 } else {
2203 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
2204 "accepted"_ns);
2206 } else if (mEarlyDataDisposition == EARLY_SENT) {
2207 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
2208 "sent"_ns);
2209 } else if (mEarlyDataDisposition == EARLY_425) {
2210 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
2211 "received 425"_ns);
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());
2223 LOG3(("]\n"));
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.
2234 bool reset = false;
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...
2240 if (reset) {
2241 LOG(("resetting transaction's response head\n"));
2242 mHaveAllHeaders = false;
2243 mHaveStatusLine = false;
2244 mReceivedData = false;
2245 mSentData = false;
2246 mHttpResponseMatched = false;
2247 mResponseHead->Reset();
2248 // wait to be called again...
2249 return NS_OK;
2252 bool responseChecked = false;
2253 if (mIsForWebTransport) {
2254 responseChecked = HandleWebTransportResponse(mResponseHead->Status());
2255 LOG(("HandleWebTransportResponse res=%d", responseChecked));
2256 if (responseChecked) {
2257 mNoContent = true;
2258 mPreserveStream = true;
2262 if (!responseChecked) {
2263 // check if this is a no-content response
2264 switch (mResponseHead->Status()) {
2265 case 101:
2266 mPreserveStream = true;
2267 [[fallthrough]]; // to other no content cases:
2268 case 204:
2269 case 205:
2270 case 304:
2271 mNoContent = true;
2272 LOG(("this response should not contain a body.\n"));
2273 break;
2274 case 408:
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;
2294 break;
2295 case 421:
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;
2312 break;
2313 case 425:
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;
2323 break;
2327 // Remember whether HTTP3 is supported
2328 mSupportsHTTP3 = nsHttpHandler::IsHttp3SupportedByServer(mResponseHead);
2330 CollectTelemetryForUploads();
2332 // Report telemetry
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
2343 // keep-alive.
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.
2350 mNoContent = true;
2353 // preserve connection for tunnel setup - h2 websocket upgrade only
2354 if (mIsHttp2Websocket && mResponseHead->Status() == 200) {
2355 LOG(("nsHttpTransaction::HandleContentStart websocket upgrade resp 200"));
2356 mNoContent = true;
2359 if (mResponseHead->Status() == 200 &&
2360 mConnection->IsProxyConnectInProgress()) {
2361 // successful CONNECTs do not have response bodies
2362 mNoContent = true;
2364 mConnection->SetLastTransactionExpectedNoContent(mNoContent);
2366 if (mNoContent) {
2367 mContentLength = 0;
2368 } else {
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;
2385 if (mConnection) {
2386 mConnection->DontReuse();
2389 } else if (mContentLength == int64_t(-1)) {
2390 LOG(("waiting for the server to close the connection.\n"));
2395 mDidContentStart = true;
2396 return NS_OK;
2399 // called on the socket thread
2400 nsresult nsHttpTransaction::HandleContent(char* buf, uint32_t count,
2401 uint32_t* contentRead,
2402 uint32_t* contentRemaining) {
2403 nsresult rv;
2405 LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count));
2407 *contentRead = 0;
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,
2423 contentRemaining);
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;
2435 } else {
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);
2444 } else {
2445 // when we are just waiting for the server to close the connection...
2446 // (no explicit content-length given)
2447 *contentRead = count;
2450 if (*contentRead) {
2451 // update count of content bytes read and report progress...
2452 mContentRead += *contentRead;
2455 LOG1(
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);
2486 return NS_OK;
2489 nsresult nsHttpTransaction::ProcessData(char* buf, uint32_t count,
2490 uint32_t* countRead) {
2491 nsresult rv;
2493 LOG1(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count));
2495 *countRead = 0;
2497 // we may not have read all of the headers yet...
2498 if (!mHaveAllHeaders) {
2499 uint32_t bytesConsumed = 0;
2501 do {
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",
2515 this));
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;
2548 // buf layout:
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
2558 // NOTE:
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;
2578 return NS_OK;
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;
2591 LOG(
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",
2603 this));
2604 return;
2607 uint32_t blockers = 0;
2608 nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers);
2610 LOG(
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) {
2616 LOG(
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)) {
2622 LOG(
2623 ("nsHttpTransaction::RemoveDispatchedAsBlocking\n"
2624 " failed to process pending queue\n"));
2628 mDispatchedAsBlocking = false;
2631 void nsHttpTransaction::ReleaseBlockingTransaction() {
2632 RemoveDispatchedAsBlocking();
2633 LOG(
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;
2642 if (mConnInfo) {
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
2657 // connection info.
2658 // When HTTPS RR is used, PrepareConnInfoForRetry() could select other h3
2659 // record to connect.
2660 if (mOrigConnInfo) {
2661 LOG(
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;
2668 return;
2671 mCaps |= NS_HTTP_DISALLOW_HTTP3;
2673 MOZ_ASSERT(mConnInfo);
2674 if (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"));
2698 return;
2701 nsAutoCString auth;
2702 if (NS_FAILED(mResponseHead->GetHeader(header, auth))) {
2703 return;
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) {
2715 Tokenizer p(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();
2725 #endif
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) {
2737 uint32_t flags;
2738 nsresult rv = authenticator->GetAuthFlags(&flags);
2739 if (NS_SUCCEEDED(rv) &&
2740 (flags & nsIHttpAuthenticator::CONNECTION_BASED)) {
2741 return true;
2745 // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
2746 p.SkipUntil(Tokenizer::Token::NewLine());
2747 p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
2750 return false;
2753 TimingStruct nsHttpTransaction::Timings() {
2754 mozilla::MutexAutoLock lock(mLock);
2755 TimingStruct timings = mTimings;
2756 return timings;
2759 void nsHttpTransaction::BootstrapTimings(TimingStruct times) {
2760 mozilla::MutexAutoLock lock(mLock);
2761 mTimings = times;
2764 void nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp,
2765 bool onlyIfNull) {
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,
2774 bool onlyIfNull) {
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,
2783 bool onlyIfNull) {
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,
2792 bool onlyIfNull) {
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,
2801 bool onlyIfNull) {
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,
2810 bool onlyIfNull) {
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,
2819 bool onlyIfNull) {
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 {
2877 public:
2878 explicit DeleteHttpTransaction(nsHttpTransaction* trans)
2879 : Runnable("net::DeleteHttpTransaction"), mTrans(trans) {}
2881 NS_IMETHOD Run() override {
2882 delete mTrans;
2883 return NS_OK;
2886 private:
2887 nsHttpTransaction* mTrans;
2890 void nsHttpTransaction::DeleteSelfOnConsumerThread() {
2891 LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
2893 bool val;
2894 if (!mConsumerTarget ||
2895 (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
2896 delete this;
2897 } else {
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)) {
2924 LOG(
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() {
2946 nsrefcnt count;
2947 MOZ_ASSERT(0 != mRefCnt, "dup release");
2948 count = --mRefCnt;
2949 NS_LOG_RELEASE(this, count, "nsHttpTransaction");
2950 if (0 == count) {
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();
2955 return 0;
2957 return count;
2960 NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, nsIInputStreamCallback,
2961 nsIOutputStreamCallback, nsITimerCallback, nsINamed)
2963 //-----------------------------------------------------------------------------
2964 // nsHttpTransaction::nsIInputStreamCallback
2965 //-----------------------------------------------------------------------------
2967 // called on the socket thread
2968 NS_IMETHODIMP
2969 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream* out) {
2970 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2971 if (mConnection) {
2972 mConnection->TransactionHasDataToWrite(this);
2973 nsresult rv = mConnection->ResumeSend();
2974 if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed");
2976 return NS_OK;
2979 //-----------------------------------------------------------------------------
2980 // nsHttpTransaction::nsIOutputStreamCallback
2981 //-----------------------------------------------------------------------------
2983 // called on the socket thread
2984 NS_IMETHODIMP
2985 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream* out) {
2986 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2987 mWaitingOnPipeOut = false;
2988 if (mConnection) {
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");
2995 return NS_OK;
2998 void nsHttpTransaction::GetNetworkAddresses(
2999 NetAddr& self, NetAddr& peer, bool& aResolvedByTRR,
3000 nsIRequest::TRRMode& aEffectiveTRRMode, TRRSkippedReason& aSkipReason,
3001 bool& aEchConfigUsed) {
3002 MutexAutoLock lock(mLock);
3003 self = mSelfAddr;
3004 peer = mPeerAddr;
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,
3024 aAlpnChanged));
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;
3035 if (aRestart) {
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);
3041 if (seekable) {
3042 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
3043 } else {
3044 return NS_ERROR_FAILURE;
3046 } else if (!mConnected) {
3047 // this is code that was skipped in ::ReadSegments while in 0RTT
3048 mConnected = true;
3049 MaybeRefreshSecurityInfo();
3051 return NS_OK;
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;
3077 int32_t cur = 0;
3078 int32_t len = aTrailers.Length();
3079 while (cur < len) {
3080 int32_t newline = aTrailers.FindCharInSet("\n", cur);
3081 if (newline == -1) {
3082 newline = len;
3085 int32_t end =
3086 (newline && aTrailers[newline - 1] == '\r') ? newline - 1 : newline;
3087 nsDependentCSubstring line(aTrailers, cur, end);
3088 nsHttpAtom hdr;
3089 nsAutoCString hdrNameOriginal;
3090 nsAutoCString val;
3091 if (NS_SUCCEEDED(httpTrailers->ParseHeaderLine(line, &hdr, &hdrNameOriginal,
3092 &val))) {
3093 if (hdr == nsHttp::Server_Timing) {
3094 Unused << httpTrailers->SetHeaderFromNet(hdr, hdrNameOriginal, val,
3095 true);
3099 cur = newline + 1;
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() {
3112 if (mRequestHead) {
3113 nsAutoCString upgradeHeader;
3114 if (NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Upgrade, upgradeHeader)) &&
3115 upgradeHeader.LowerCaseEqualsLiteral("websocket")) {
3116 return true;
3119 return false;
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,
3127 aResponseCode));
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) {
3148 return;
3151 bool versionOk = false;
3152 bool authOk = false;
3154 LOG(("nsHttpTransaction::NotifyTransactionObserver %p reason %" PRIx32
3155 " conn %p\n",
3156 this, static_cast<uint32_t>(reason), mConnection.get()));
3158 if (mConnection) {
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));
3166 LOG(
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);
3188 if (mActivated) {
3189 MOZ_ASSERT(false, "Should not update conn info after activated");
3190 return;
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,
3201 mActivated));
3202 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
3205 MutexAutoLock lock(mLock);
3206 MakeDontWaitHTTPSRR();
3207 mDNSRequest = nullptr;
3210 if (!mResolver) {
3211 LOG(("The transaction is not interested in HTTPS record anymore."));
3212 return NS_OK;
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;
3231 if (!record) {
3232 return NS_ERROR_FAILURE;
3235 bool hasIPAddress = false;
3236 Unused << record->GetHasIPAddresses(&hasIPAddress);
3238 if (mActivated) {
3239 receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_2
3240 : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_2;
3241 return NS_OK;
3244 receivedStage = hasIPAddress ? HTTPSSVC_WITH_IPHINT_RECEIVED_STAGE_1
3245 : HTTPSSVC_WITHOUT_IPHINT_RECEIVED_STAGE_1;
3247 nsCOMPtr<nsISVCBRecord> svcbRecord = aHighestPriorityRecord;
3248 if (!svcbRecord) {
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,
3254 allRecordsExcluded
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;
3265 } else {
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);
3307 if (mResolver) {
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);
3323 return NS_OK;
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) {
3345 LOG(
3346 ("nsHttpTransaction::OnBackupConnectionReady [%p] mConnected=%d "
3347 "aTriggeredByHTTPSRR=%d",
3348 this, mConnected, aTriggeredByHTTPSRR));
3349 if (mConnected || mClosed || mRestarted) {
3350 return;
3353 // If HTTPS RR is in play, don't mess up the fallback mechansim of HTTPS RR.
3354 if (!aTriggeredByHTTPSRR && mOrigConnInfo) {
3355 return;
3358 if (mConnection) {
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
3371 // replaced.
3372 RefPtr<nsHttpConnectionInfo> backup = mOrigConnInfo;
3373 HandleFallback(mBackupConnInfo);
3374 mOrigConnInfo.swap(backup);
3376 RemoveAlternateServiceUsedHeader(mRequestHead);
3378 if (mResolver) {
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.
3389 mResolver->Close();
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));
3402 uint32_t limit =
3403 StaticPrefs::network_http_http3_parallel_fallback_conn_limit();
3404 if (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) {
3423 if (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,
3435 mConnected));
3437 mFastFallbackTimer = nullptr;
3439 MOZ_ASSERT(mHTTPSSVCRecord && mOrigConnInfo);
3440 if (!mHTTPSSVCRecord || !mOrigConnInfo) {
3441 return;
3444 bool echConfigUsed = gHttpHandler->EchConfigEnabled(mConnInfo->IsHttp3()) &&
3445 !mConnInfo->GetEchConfig().IsEmpty();
3446 mBackupConnInfo = PrepareFastFallbackConnInfo(echConfigUsed);
3447 if (!mBackupConnInfo) {
3448 return;
3451 MOZ_ASSERT(!mBackupConnInfo->IsHttp3());
3453 RefPtr<nsHttpTransaction> self = this;
3454 auto callback = [self](bool aSucceded) {
3455 if (!aSucceded) {
3456 return;
3459 self->mFastFallbackTriggered = true;
3460 self->OnBackupConnectionReady(true);
3463 CreateBackupConnection(mBackupConnInfo, mCallbacks, mCaps,
3464 std::move(callback));
3467 void nsHttpTransaction::HandleFallback(
3468 nsHttpConnectionInfo* aFallbackConnInfo) {
3469 if (mConnection) {
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);
3474 return;
3477 if (!aFallbackConnInfo) {
3478 // Nothing to do here.
3479 return;
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");
3489 return;
3492 // rewind streams in case we already wrote out the request
3493 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
3494 if (seekable) {
3495 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
3498 UpdateConnectionInfo(aFallbackConnInfo);
3499 Unused << gHttpHandler->ConnMgr()->ProcessNewTransaction(this);
3502 NS_IMETHODIMP
3503 nsHttpTransaction::Notify(nsITimer* aTimer) {
3504 MOZ_DIAGNOSTIC_ASSERT(OnSocketThread(), "not on socket thread");
3506 if (!gHttpHandler || !gHttpHandler->ConnMgr()) {
3507 return NS_OK;
3510 if (aTimer == mFastFallbackTimer) {
3511 OnFastFallbackTimer();
3512 } else if (aTimer == mHttp3BackupTimer) {
3513 OnHttp3BackupTimer();
3516 return NS_OK;
3519 NS_IMETHODIMP
3520 nsHttpTransaction::GetName(nsACString& aName) {
3521 aName.AssignLiteral("nsHttpTransaction");
3522 return NS_OK;
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()) {
3534 return;
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,
3542 mpbs);
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);
3552 } else {
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