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