1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* vim:set ts=4 sw=4 sts=4 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 "HttpLog.h"
9
10 #include "base/basictypes.h"
11
12 #include "nsHttpHandler.h"
13 #include "nsHttpTransaction.h"
14 #include "nsHttpRequestHead.h"
15 #include "nsHttpResponseHead.h"
16 #include "nsHttpChunkedDecoder.h"
17 #include "nsTransportUtils.h"
18 #include "nsNetCID.h"
19 #include "nsNetUtil.h"
20 #include "nsIChannel.h"
21 #include "nsIPipe.h"
22 #include "nsCRT.h"
23 #include "mozilla/Tokenizer.h"
24 #include "TCPFastOpenLayer.h"
25
26 #include "nsISeekableStream.h"
27 #include "nsMultiplexInputStream.h"
28 #include "nsStringStream.h"
29
30 #include "nsComponentManagerUtils.h" // do_CreateInstance
31 #include "nsIHttpActivityObserver.h"
32 #include "nsSocketTransportService2.h"
33 #include "nsICancelable.h"
34 #include "nsIClassOfService.h"
35 #include "nsIEventTarget.h"
36 #include "nsIHttpChannelInternal.h"
37 #include "nsIInputStream.h"
38 #include "nsIThrottledInputChannel.h"
39 #include "nsITransport.h"
40 #include "nsIOService.h"
41 #include "nsIRequestContext.h"
42 #include "nsIHttpAuthenticator.h"
43 #include "NSSErrorsService.h"
44 #include "sslerr.h"
45 #include <algorithm>
46
47 //-----------------------------------------------------------------------------
48
49 static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
50
51 // Place a limit on how much non-compliant HTTP can be skipped while
52 // looking for a response header
53 #define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
54
55 using namespace mozilla::net;
56
57 namespace mozilla {
58 namespace net {
59
60 //-----------------------------------------------------------------------------
61 // helpers
62 //-----------------------------------------------------------------------------
63
LogHeaders(const char * lineStart)64 static void LogHeaders(const char *lineStart) {
65 nsAutoCString buf;
66 char *endOfLine;
67 while ((endOfLine = PL_strstr(lineStart, "\r\n"))) {
68 buf.Assign(lineStart, endOfLine - lineStart);
69 if (PL_strcasestr(buf.get(), "authorization: ") ||
70 PL_strcasestr(buf.get(), "proxy-authorization: ")) {
71 char *p = PL_strchr(PL_strchr(buf.get(), ' ') + 1, ' ');
72 while (p && *++p) *p = '*';
73 }
74 LOG3((" %s\n", buf.get()));
75 lineStart = endOfLine + 2;
76 }
77 }
78
79 //-----------------------------------------------------------------------------
80 // nsHttpTransaction <public>
81 //-----------------------------------------------------------------------------
82
nsHttpTransaction()83 nsHttpTransaction::nsHttpTransaction()
84 : mLock("transaction lock"),
85 mRequestSize(0),
86 mRequestHead(nullptr),
87 mResponseHead(nullptr),
88 mReader(nullptr),
89 mWriter(nullptr),
90 mContentLength(-1),
91 mContentRead(0),
92 mTransferSize(0),
93 mInvalidResponseBytesRead(0),
94 mPushedStream(nullptr),
95 mInitialRwin(0),
96 mChunkedDecoder(nullptr),
97 mStatus(NS_OK),
98 mPriority(0),
99 mRestartCount(0),
100 mCaps(0),
101 mHttpVersion(NS_HTTP_VERSION_UNKNOWN),
102 mHttpResponseCode(0),
103 mCurrentHttpResponseHeaderSize(0),
104 mThrottlingReadAllowance(THROTTLE_NO_LIMIT),
105 mCapsToClear(0),
106 mResponseIsComplete(false),
107 mReadingStopped(false),
108 mClosed(false),
109 mConnected(false),
110 mActivated(false),
111 mHaveStatusLine(false),
112 mHaveAllHeaders(false),
113 mTransactionDone(false),
114 mDidContentStart(false),
115 mNoContent(false),
116 mSentData(false),
117 mReceivedData(false),
118 mStatusEventPending(false),
119 mHasRequestBody(false),
120 mProxyConnectFailed(false),
121 mHttpResponseMatched(false),
122 mPreserveStream(false),
123 mDispatchedAsBlocking(false),
124 mResponseTimeoutEnabled(true),
125 mForceRestart(false),
126 mReuseOnRestart(false),
127 mContentDecoding(false),
128 mContentDecodingCheck(false),
129 mDeferredSendProgress(false),
130 mWaitingOnPipeOut(false),
131 mReportedStart(false),
132 mReportedResponseHeader(false),
133 mForTakeResponseHead(nullptr),
134 mResponseHeadTaken(false),
135 mForTakeResponseTrailers(nullptr),
136 mResponseTrailersTaken(false),
137 mTopLevelOuterContentWindowId(0),
138 mSubmittedRatePacing(false),
139 mPassedRatePacing(false),
140 mSynchronousRatePaceRequest(false),
141 mClassOfService(0),
142 m0RTTInProgress(false),
143 mDoNotTryEarlyData(false),
144 mEarlyDataDisposition(EARLY_NONE),
145 mFastOpenStatus(TFO_NOT_TRIED) {
146 LOG(("Creating nsHttpTransaction @%p\n", this));
147
148 #ifdef MOZ_VALGRIND
149 memset(&mSelfAddr, 0, sizeof(NetAddr));
150 memset(&mPeerAddr, 0, sizeof(NetAddr));
151 #endif
152 mSelfAddr.raw.family = PR_AF_UNSPEC;
153 mPeerAddr.raw.family = PR_AF_UNSPEC;
154 }
155
ResumeReading()156 void nsHttpTransaction::ResumeReading() {
157 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
158
159 if (!mReadingStopped) {
160 return;
161 }
162
163 LOG(("nsHttpTransaction::ResumeReading %p", this));
164
165 mReadingStopped = false;
166
167 // This with either reengage the limit when still throttled in WriteSegments
168 // or simply reset to allow unlimeted reading again.
169 mThrottlingReadAllowance = THROTTLE_NO_LIMIT;
170
171 if (mConnection) {
172 mConnection->TransactionHasDataToRecv(this);
173 nsresult rv = mConnection->ResumeRecv();
174 if (NS_FAILED(rv)) {
175 LOG((" resume failed with rv=%" PRIx32, static_cast<uint32_t>(rv)));
176 }
177 }
178 }
179
EligibleForThrottling() const180 bool nsHttpTransaction::EligibleForThrottling() const {
181 return (mClassOfService &
182 (nsIClassOfService::Throttleable | nsIClassOfService::DontThrottle |
183 nsIClassOfService::Leader | nsIClassOfService::Unblocked)) ==
184 nsIClassOfService::Throttleable;
185 }
186
SetClassOfService(uint32_t cos)187 void nsHttpTransaction::SetClassOfService(uint32_t cos) {
188 bool wasThrottling = EligibleForThrottling();
189 mClassOfService = cos;
190 bool isThrottling = EligibleForThrottling();
191
192 if (mConnection && wasThrottling != isThrottling) {
193 // Do nothing until we are actually activated. For now
194 // only remember the throttle flag. Call to UpdateActiveTransaction
195 // would add this transaction to the list too early.
196 gHttpHandler->ConnMgr()->UpdateActiveTransaction(this);
197
198 if (mReadingStopped && !isThrottling) {
199 ResumeReading();
200 }
201 }
202 }
203
~nsHttpTransaction()204 nsHttpTransaction::~nsHttpTransaction() {
205 LOG(("Destroying nsHttpTransaction @%p\n", this));
206 if (mTransactionObserver) {
207 mTransactionObserver->Complete(this, NS_OK);
208 }
209 if (mPushedStream) {
210 mPushedStream->OnPushFailed();
211 mPushedStream = nullptr;
212 }
213
214 if (mTokenBucketCancel) {
215 mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
216 mTokenBucketCancel = nullptr;
217 }
218
219 // Force the callbacks and connection to be released right now
220 mCallbacks = nullptr;
221 mConnection = nullptr;
222
223 delete mResponseHead;
224 delete mForTakeResponseHead;
225 delete mChunkedDecoder;
226 ReleaseBlockingTransaction();
227 }
228
Init(uint32_t caps,nsHttpConnectionInfo * cinfo,nsHttpRequestHead * requestHead,nsIInputStream * requestBody,uint64_t requestContentLength,bool requestBodyHasHeaders,nsIEventTarget * target,nsIInterfaceRequestor * callbacks,nsITransportEventSink * eventsink,uint64_t topLevelOuterContentWindowId,nsIAsyncInputStream ** responseBody)229 nsresult nsHttpTransaction::Init(
230 uint32_t caps, nsHttpConnectionInfo *cinfo, nsHttpRequestHead *requestHead,
231 nsIInputStream *requestBody, uint64_t requestContentLength,
232 bool requestBodyHasHeaders, nsIEventTarget *target,
233 nsIInterfaceRequestor *callbacks, nsITransportEventSink *eventsink,
234 uint64_t topLevelOuterContentWindowId, nsIAsyncInputStream **responseBody) {
235 nsresult rv;
236
237 LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
238
239 MOZ_ASSERT(cinfo);
240 MOZ_ASSERT(requestHead);
241 MOZ_ASSERT(target);
242 MOZ_ASSERT(NS_IsMainThread());
243
244 mTopLevelOuterContentWindowId = topLevelOuterContentWindowId;
245 LOG((" window-id = %" PRIx64, mTopLevelOuterContentWindowId));
246
247 mActivityDistributor = services::GetActivityDistributor();
248 if (!mActivityDistributor) {
249 return NS_ERROR_NOT_AVAILABLE;
250 }
251
252 bool activityDistributorActive;
253 rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
254 if (NS_SUCCEEDED(rv) && activityDistributorActive) {
255 // there are some observers registered at activity distributor, gather
256 // nsISupports for the channel that called Init()
257 LOG(
258 ("nsHttpTransaction::Init() "
259 "mActivityDistributor is active "
260 "this=%p",
261 this));
262 } else {
263 // there is no observer, so don't use it
264 activityDistributorActive = false;
265 mActivityDistributor = nullptr;
266 }
267 mChannel = do_QueryInterface(eventsink);
268
269 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
270 do_QueryInterface(eventsink);
271 if (httpChannelInternal) {
272 rv = httpChannelInternal->GetResponseTimeoutEnabled(
273 &mResponseTimeoutEnabled);
274 if (NS_WARN_IF(NS_FAILED(rv))) {
275 return rv;
276 }
277 rv = httpChannelInternal->GetInitialRwin(&mInitialRwin);
278 MOZ_ASSERT(NS_SUCCEEDED(rv));
279 }
280
281 // create transport event sink proxy. it coalesces consecutive
282 // events of the same status type.
283 rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), eventsink,
284 target);
285
286 if (NS_FAILED(rv)) return rv;
287
288 mConnInfo = cinfo;
289 mCallbacks = callbacks;
290 mConsumerTarget = target;
291 mCaps = caps;
292
293 if (requestHead->IsHead()) {
294 mNoContent = true;
295 }
296
297 // Make sure that there is "Content-Length: 0" header in the requestHead
298 // in case of POST and PUT methods when there is no requestBody and
299 // requestHead doesn't contain "Transfer-Encoding" header.
300 //
301 // RFC1945 section 7.2.2:
302 // HTTP/1.0 requests containing an entity body must include a valid
303 // Content-Length header field.
304 //
305 // RFC2616 section 4.4:
306 // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
307 // containing a message-body MUST include a valid Content-Length header
308 // field unless the server is known to be HTTP/1.1 compliant.
309 if ((requestHead->IsPost() || requestHead->IsPut()) && !requestBody &&
310 !requestHead->HasHeader(nsHttp::Transfer_Encoding)) {
311 rv =
312 requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
313 MOZ_ASSERT(NS_SUCCEEDED(rv));
314 }
315
316 // grab a weak reference to the request head
317 mRequestHead = requestHead;
318
319 // make sure we eliminate any proxy specific headers from
320 // the request if we are using CONNECT
321 bool pruneProxyHeaders = cinfo->UsingConnect();
322
323 mReqHeaderBuf.Truncate();
324 requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders);
325
326 if (LOG3_ENABLED()) {
327 LOG3(("http request [\n"));
328 LogHeaders(mReqHeaderBuf.get());
329 LOG3(("]\n"));
330 }
331
332 // If the request body does not include headers or if there is no request
333 // body, then we must add the header/body separator manually.
334 if (!requestBodyHasHeaders || !requestBody)
335 mReqHeaderBuf.AppendLiteral("\r\n");
336
337 // report the request header
338 if (mActivityDistributor) {
339 rv = mActivityDistributor->ObserveActivity(
340 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
341 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, PR_Now(), 0, mReqHeaderBuf);
342 if (NS_FAILED(rv)) {
343 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
344 }
345 }
346
347 // Create a string stream for the request header buf (the stream holds
348 // a non-owning reference to the request header data, so we MUST keep
349 // mReqHeaderBuf around).
350 nsCOMPtr<nsIInputStream> headers;
351 rv = NS_NewByteInputStream(getter_AddRefs(headers), mReqHeaderBuf.get(),
352 mReqHeaderBuf.Length());
353 if (NS_FAILED(rv)) return rv;
354
355 mHasRequestBody = !!requestBody;
356 if (mHasRequestBody && !requestContentLength) {
357 mHasRequestBody = false;
358 }
359
360 requestContentLength += mReqHeaderBuf.Length();
361
362 if (mHasRequestBody) {
363 // wrap the headers and request body in a multiplexed input stream.
364 nsCOMPtr<nsIMultiplexInputStream> multi =
365 do_CreateInstance(kMultiplexInputStream, &rv);
366 if (NS_FAILED(rv)) return rv;
367
368 rv = multi->AppendStream(headers);
369 if (NS_FAILED(rv)) return rv;
370
371 rv = multi->AppendStream(requestBody);
372 if (NS_FAILED(rv)) return rv;
373
374 // wrap the multiplexed input stream with a buffered input stream, so
375 // that we write data in the largest chunks possible. this is actually
376 // necessary to workaround some common server bugs (see bug 137155).
377 nsCOMPtr<nsIInputStream> stream(do_QueryInterface(multi));
378 rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream),
379 stream.forget(),
380 nsIOService::gDefaultSegmentSize);
381 if (NS_FAILED(rv)) return rv;
382 } else {
383 mRequestStream = headers;
384 }
385
386 nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mChannel);
387 nsIInputChannelThrottleQueue *queue;
388 if (throttled) {
389 rv = throttled->GetThrottleQueue(&queue);
390 // In case of failure, just carry on without throttling.
391 if (NS_SUCCEEDED(rv) && queue) {
392 nsCOMPtr<nsIAsyncInputStream> wrappedStream;
393 rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream));
394 // Failure to throttle isn't sufficient reason to fail
395 // initialization
396 if (NS_SUCCEEDED(rv)) {
397 MOZ_ASSERT(wrappedStream != nullptr);
398 LOG(
399 ("nsHttpTransaction::Init %p wrapping input stream using throttle "
400 "queue %p\n",
401 this, queue));
402 mRequestStream = do_QueryInterface(wrappedStream);
403 }
404 }
405 }
406
407 // make sure request content-length fits within js MAX_SAFE_INTEGER
408 mRequestSize = InScriptableRange(requestContentLength)
409 ? static_cast<int64_t>(requestContentLength)
410 : -1;
411
412 // create pipe for response stream
413 rv = NS_NewPipe2(getter_AddRefs(mPipeIn), getter_AddRefs(mPipeOut), true,
414 true, nsIOService::gDefaultSegmentSize,
415 nsIOService::gDefaultSegmentCount);
416 if (NS_FAILED(rv)) return rv;
417
418 #ifdef WIN32 // bug 1153929
419 MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
420 uint32_t *vtable = (uint32_t *)mPipeOut.get();
421 MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
422 #endif // WIN32
423
424 nsCOMPtr<nsIAsyncInputStream> tmp(mPipeIn);
425 tmp.forget(responseBody);
426 return NS_OK;
427 }
428
429 // This method should only be used on the socket thread
Connection()430 nsAHttpConnection *nsHttpTransaction::Connection() {
431 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
432 return mConnection.get();
433 }
434
435 already_AddRefed<nsAHttpConnection>
GetConnectionReference()436 nsHttpTransaction::GetConnectionReference() {
437 MutexAutoLock lock(mLock);
438 RefPtr<nsAHttpConnection> connection(mConnection);
439 return connection.forget();
440 }
441
TakeResponseHead()442 nsHttpResponseHead *nsHttpTransaction::TakeResponseHead() {
443 MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
444
445 // Lock TakeResponseHead() against main thread
446 MutexAutoLock lock(*nsHttp::GetLock());
447
448 mResponseHeadTaken = true;
449
450 // Prefer mForTakeResponseHead over mResponseHead. It is always a complete
451 // set of headers.
452 nsHttpResponseHead *head;
453 if (mForTakeResponseHead) {
454 head = mForTakeResponseHead;
455 mForTakeResponseHead = nullptr;
456 return head;
457 }
458
459 // Even in OnStartRequest() the headers won't be available if we were
460 // canceled
461 if (!mHaveAllHeaders) {
462 NS_WARNING("response headers not available or incomplete");
463 return nullptr;
464 }
465
466 head = mResponseHead;
467 mResponseHead = nullptr;
468 return head;
469 }
470
TakeResponseTrailers()471 nsHttpHeaderArray *nsHttpTransaction::TakeResponseTrailers() {
472 MOZ_ASSERT(!mResponseTrailersTaken, "TakeResponseTrailers called 2x");
473
474 // Lock TakeResponseTrailers() against main thread
475 MutexAutoLock lock(*nsHttp::GetLock());
476
477 mResponseTrailersTaken = true;
478 return mForTakeResponseTrailers.forget();
479 }
480
SetProxyConnectFailed()481 void nsHttpTransaction::SetProxyConnectFailed() { mProxyConnectFailed = true; }
482
RequestHead()483 nsHttpRequestHead *nsHttpTransaction::RequestHead() { return mRequestHead; }
484
Http1xTransactionCount()485 uint32_t nsHttpTransaction::Http1xTransactionCount() { return 1; }
486
TakeSubTransactions(nsTArray<RefPtr<nsAHttpTransaction>> & outTransactions)487 nsresult nsHttpTransaction::TakeSubTransactions(
488 nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) {
489 return NS_ERROR_NOT_IMPLEMENTED;
490 }
491
492 //----------------------------------------------------------------------------
493 // nsHttpTransaction::nsAHttpTransaction
494 //----------------------------------------------------------------------------
495
SetConnection(nsAHttpConnection * conn)496 void nsHttpTransaction::SetConnection(nsAHttpConnection *conn) {
497 {
498 MutexAutoLock lock(mLock);
499 mConnection = conn;
500 }
501 }
502
OnActivated()503 void nsHttpTransaction::OnActivated() {
504 MOZ_ASSERT(OnSocketThread());
505
506 if (mActivated) {
507 return;
508 }
509
510 mActivated = true;
511 gHttpHandler->ConnMgr()->AddActiveTransaction(this);
512 }
513
GetSecurityCallbacks(nsIInterfaceRequestor ** cb)514 void nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb) {
515 MutexAutoLock lock(mLock);
516 nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks);
517 tmp.forget(cb);
518 }
519
SetSecurityCallbacks(nsIInterfaceRequestor * aCallbacks)520 void nsHttpTransaction::SetSecurityCallbacks(
521 nsIInterfaceRequestor *aCallbacks) {
522 {
523 MutexAutoLock lock(mLock);
524 mCallbacks = aCallbacks;
525 }
526
527 if (gSocketTransportService) {
528 RefPtr<UpdateSecurityCallbacks> event =
529 new UpdateSecurityCallbacks(this, aCallbacks);
530 gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
531 }
532 }
533
OnTransportStatus(nsITransport * transport,nsresult status,int64_t progress)534 void nsHttpTransaction::OnTransportStatus(nsITransport *transport,
535 nsresult status, int64_t progress) {
536 LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%" PRIx32
537 " progress=%" PRId64 "]\n",
538 this, static_cast<uint32_t>(status), progress));
539
540 if (status == NS_NET_STATUS_CONNECTED_TO ||
541 status == NS_NET_STATUS_WAITING_FOR) {
542 nsISocketTransport *socketTransport =
543 mConnection ? mConnection->Transport() : nullptr;
544 if (socketTransport) {
545 MutexAutoLock lock(mLock);
546 socketTransport->GetSelfAddr(&mSelfAddr);
547 socketTransport->GetPeerAddr(&mPeerAddr);
548 }
549 }
550
551 // If the timing is enabled, and we are not using a persistent connection
552 // then the requestStart timestamp will be null, so we mark the timestamps
553 // for domainLookupStart/End and connectStart/End
554 // If we are using a persistent connection they will remain null,
555 // and the correct value will be returned in Performance.
556 if (TimingEnabled() && GetRequestStart().IsNull()) {
557 if (status == NS_NET_STATUS_RESOLVING_HOST) {
558 SetDomainLookupStart(TimeStamp::Now(), true);
559 } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
560 SetDomainLookupEnd(TimeStamp::Now());
561 } else if (status == NS_NET_STATUS_CONNECTING_TO) {
562 SetConnectStart(TimeStamp::Now());
563 } else if (status == NS_NET_STATUS_CONNECTED_TO) {
564 TimeStamp tnow = TimeStamp::Now();
565 SetConnectEnd(tnow, true);
566 {
567 MutexAutoLock lock(mLock);
568 mTimings.tcpConnectEnd = tnow;
569 // After a socket is connected we know for sure whether data
570 // has been sent on SYN packet and if not we should update TLS
571 // start timing.
572 if ((mFastOpenStatus != TFO_DATA_SENT) &&
573 !mTimings.secureConnectionStart.IsNull()) {
574 mTimings.secureConnectionStart = tnow;
575 }
576 }
577 } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_STARTING) {
578 {
579 MutexAutoLock lock(mLock);
580 mTimings.secureConnectionStart = TimeStamp::Now();
581 }
582 } else if (status == NS_NET_STATUS_TLS_HANDSHAKE_ENDED) {
583 SetConnectEnd(TimeStamp::Now(), false);
584 } else if (status == NS_NET_STATUS_SENDING_TO) {
585 // Set the timestamp to Now(), only if it null
586 SetRequestStart(TimeStamp::Now(), true);
587 }
588 }
589
590 if (!mTransportSink) return;
591
592 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
593
594 // Need to do this before the STATUS_RECEIVING_FROM check below, to make
595 // sure that the activity distributor gets told about all status events.
596 if (mActivityDistributor) {
597 // upon STATUS_WAITING_FOR; report request body sent
598 if ((mHasRequestBody) && (status == NS_NET_STATUS_WAITING_FOR)) {
599 nsresult rv = mActivityDistributor->ObserveActivity(
600 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
601 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, PR_Now(), 0,
602 EmptyCString());
603 if (NS_FAILED(rv)) {
604 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
605 }
606 }
607
608 // report the status and progress
609 nsresult rv = mActivityDistributor->ObserveActivity(
610 mChannel, NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
611 static_cast<uint32_t>(status), PR_Now(), progress, EmptyCString());
612 if (NS_FAILED(rv)) {
613 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
614 }
615 }
616
617 // nsHttpChannel synthesizes progress events in OnDataAvailable
618 if (status == NS_NET_STATUS_RECEIVING_FROM) return;
619
620 int64_t progressMax;
621
622 if (status == NS_NET_STATUS_SENDING_TO) {
623 // suppress progress when only writing request headers
624 if (!mHasRequestBody) {
625 LOG(
626 ("nsHttpTransaction::OnTransportStatus %p "
627 "SENDING_TO without request body\n",
628 this));
629 return;
630 }
631
632 if (mReader) {
633 // A mRequestStream method is on the stack - wait.
634 LOG(
635 ("nsHttpTransaction::OnSocketStatus [this=%p] "
636 "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n",
637 this));
638 // its ok to coalesce several of these into one deferred event
639 mDeferredSendProgress = true;
640 return;
641 }
642
643 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
644 if (!seekable) {
645 LOG(
646 ("nsHttpTransaction::OnTransportStatus %p "
647 "SENDING_TO without seekable request stream\n",
648 this));
649 progress = 0;
650 } else {
651 int64_t prog = 0;
652 seekable->Tell(&prog);
653 progress = prog;
654 }
655
656 // when uploading, we include the request headers in the progress
657 // notifications.
658 progressMax = mRequestSize;
659 } else {
660 progress = 0;
661 progressMax = 0;
662 }
663
664 mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
665 }
666
IsDone()667 bool nsHttpTransaction::IsDone() { return mTransactionDone; }
668
Status()669 nsresult nsHttpTransaction::Status() { return mStatus; }
670
Caps()671 uint32_t nsHttpTransaction::Caps() { return mCaps & ~mCapsToClear; }
672
SetDNSWasRefreshed()673 void nsHttpTransaction::SetDNSWasRefreshed() {
674 MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
675 mCapsToClear |= NS_HTTP_REFRESH_DNS;
676 }
677
ReadRequestSegment(nsIInputStream * stream,void * closure,const char * buf,uint32_t offset,uint32_t count,uint32_t * countRead)678 nsresult nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream,
679 void *closure, const char *buf,
680 uint32_t offset, uint32_t count,
681 uint32_t *countRead) {
682 // For the tracking of sent bytes that we used to do for the networkstats
683 // API, please see bug 1318883 where it was removed.
684
685 nsHttpTransaction *trans = (nsHttpTransaction *)closure;
686 nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
687 if (NS_FAILED(rv)) return rv;
688
689 LOG(("nsHttpTransaction::ReadRequestSegment %p read=%u", trans, *countRead));
690
691 trans->mSentData = true;
692 return NS_OK;
693 }
694
ReadSegments(nsAHttpSegmentReader * reader,uint32_t count,uint32_t * countRead)695 nsresult nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
696 uint32_t count, uint32_t *countRead) {
697 LOG(("nsHttpTransaction::ReadSegments %p", this));
698
699 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
700
701 if (mTransactionDone) {
702 *countRead = 0;
703 return mStatus;
704 }
705
706 if (!mConnected && !m0RTTInProgress) {
707 mConnected = true;
708 mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
709 }
710
711 mDeferredSendProgress = false;
712 mReader = reader;
713 nsresult rv =
714 mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
715 mReader = nullptr;
716
717 if (m0RTTInProgress && (mEarlyDataDisposition == EARLY_NONE) &&
718 NS_SUCCEEDED(rv) && (*countRead > 0)) {
719 mEarlyDataDisposition = EARLY_SENT;
720 }
721
722 if (mDeferredSendProgress && mConnection && mConnection->Transport()) {
723 // to avoid using mRequestStream concurrently, OnTransportStatus()
724 // did not report upload status off the ReadSegments() stack from
725 // nsSocketTransport do it now.
726 OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0);
727 }
728 mDeferredSendProgress = false;
729
730 if (mForceRestart) {
731 // The forceRestart condition was dealt with on the stack, but it did not
732 // clear the flag because nsPipe in the readsegment stack clears out
733 // return codes, so we need to use the flag here as a cue to return
734 // ERETARGETED
735 if (NS_SUCCEEDED(rv)) {
736 rv = NS_BINDING_RETARGETED;
737 }
738 mForceRestart = false;
739 }
740
741 // if read would block then we need to AsyncWait on the request stream.
742 // have callback occur on socket thread so we stay synchronized.
743 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
744 nsCOMPtr<nsIAsyncInputStream> asyncIn = do_QueryInterface(mRequestStream);
745 if (asyncIn) {
746 nsCOMPtr<nsIEventTarget> target;
747 Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
748 if (target)
749 asyncIn->AsyncWait(this, 0, 0, target);
750 else {
751 NS_ERROR("no socket thread event target");
752 rv = NS_ERROR_UNEXPECTED;
753 }
754 }
755 }
756
757 return rv;
758 }
759
WritePipeSegment(nsIOutputStream * stream,void * closure,char * buf,uint32_t offset,uint32_t count,uint32_t * countWritten)760 nsresult nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream,
761 void *closure, char *buf,
762 uint32_t offset, uint32_t count,
763 uint32_t *countWritten) {
764 nsHttpTransaction *trans = (nsHttpTransaction *)closure;
765
766 if (trans->mTransactionDone) return NS_BASE_STREAM_CLOSED; // stop iterating
767
768 if (trans->TimingEnabled()) {
769 // Set the timestamp to Now(), only if it null
770 trans->SetResponseStart(TimeStamp::Now(), true);
771 }
772
773 // Bug 1153929 - add checks to fix windows crash
774 MOZ_ASSERT(trans->mWriter);
775 if (!trans->mWriter) {
776 return NS_ERROR_UNEXPECTED;
777 }
778
779 nsresult rv;
780 //
781 // OK, now let the caller fill this segment with data.
782 //
783 rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
784 if (NS_FAILED(rv)) return rv; // caller didn't want to write anything
785
786 LOG(("nsHttpTransaction::WritePipeSegment %p written=%u", trans,
787 *countWritten));
788
789 MOZ_ASSERT(*countWritten > 0, "bad writer");
790 trans->mReceivedData = true;
791 trans->mTransferSize += *countWritten;
792
793 // Let the transaction "play" with the buffer. It is free to modify
794 // the contents of the buffer and/or modify countWritten.
795 // - Bytes in HTTP headers don't count towards countWritten, so the input
796 // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
797 // OnInputStreamReady until all headers have been parsed.
798 //
799 rv = trans->ProcessData(buf, *countWritten, countWritten);
800 if (NS_FAILED(rv)) trans->Close(rv);
801
802 return rv; // failure code only stops WriteSegments; it is not propagated.
803 }
804
ShouldThrottle()805 bool nsHttpTransaction::ShouldThrottle() {
806 if (mClassOfService & nsIClassOfService::DontThrottle) {
807 // We deliberately don't touch the throttling window here since
808 // DontThrottle requests are expected to be long-standing media
809 // streams and would just unnecessarily block running downloads.
810 // If we want to ballance bandwidth for media responses against
811 // running downloads, we need to find something smarter like
812 // changing the suspend/resume throttling intervals at-runtime.
813 return false;
814 }
815
816 if (!gHttpHandler->ConnMgr()->ShouldThrottle(this)) {
817 // We are not obligated to throttle
818 return false;
819 }
820
821 if (mContentRead < 16000) {
822 // Let the first bytes go, it may also well be all the content we get
823 LOG(("nsHttpTransaction::ShouldThrottle too few content (%" PRIi64
824 ") this=%p",
825 mContentRead, this));
826 return false;
827 }
828
829 if (!(mClassOfService & nsIClassOfService::Throttleable) &&
830 gHttpHandler->ConnMgr()->IsConnEntryUnderPressure(mConnInfo)) {
831 LOG(("nsHttpTransaction::ShouldThrottle entry pressure this=%p", this));
832 // This is expensive to check (two hashtable lookups) but may help
833 // freeing connections for active tab transactions.
834 // Checking this only for transactions that are not explicitly marked
835 // as throttleable because trackers and (specially) downloads should
836 // keep throttling even under pressure.
837 return false;
838 }
839
840 return true;
841 }
842
WriteSegments(nsAHttpSegmentWriter * writer,uint32_t count,uint32_t * countWritten)843 nsresult nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
844 uint32_t count,
845 uint32_t *countWritten) {
846 LOG(("nsHttpTransaction::WriteSegments %p", this));
847
848 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
849
850 if (mTransactionDone) {
851 return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
852 }
853
854 if (ShouldThrottle()) {
855 if (mThrottlingReadAllowance == THROTTLE_NO_LIMIT) { // no limit set
856 // V1: ThrottlingReadLimit() returns 0
857 mThrottlingReadAllowance = gHttpHandler->ThrottlingReadLimit();
858 }
859 } else {
860 mThrottlingReadAllowance = THROTTLE_NO_LIMIT; // don't limit
861 }
862
863 if (mThrottlingReadAllowance == 0) { // depleted
864 if (gHttpHandler->ConnMgr()->CurrentTopLevelOuterContentWindowId() !=
865 mTopLevelOuterContentWindowId) {
866 nsHttp::NotifyActiveTabLoadOptimization();
867 }
868
869 // Must remember that we have to call ResumeRecv() on our connection when
870 // called back by the conn manager to resume reading.
871 LOG(("nsHttpTransaction::WriteSegments %p response throttled", this));
872 mReadingStopped = true;
873 // This makes the underlaying connection or stream wait for explicit resume.
874 // For h1 this means we stop reading from the socket.
875 // For h2 this means we stop updating recv window for the stream.
876 return NS_BASE_STREAM_WOULD_BLOCK;
877 }
878
879 mWriter = writer;
880
881 #ifdef WIN32 // bug 1153929
882 MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
883 uint32_t *vtable = (uint32_t *)mPipeOut.get();
884 MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
885 #endif // WIN32
886
887 if (!mPipeOut) {
888 return NS_ERROR_UNEXPECTED;
889 }
890
891 if (mThrottlingReadAllowance > 0) {
892 LOG(("nsHttpTransaction::WriteSegments %p limiting read from %u to %d",
893 this, count, mThrottlingReadAllowance));
894 count = std::min(count, static_cast<uint32_t>(mThrottlingReadAllowance));
895 }
896
897 nsresult rv =
898 mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
899
900 mWriter = nullptr;
901
902 if (mForceRestart) {
903 // The forceRestart condition was dealt with on the stack, but it did not
904 // clear the flag because nsPipe in the writesegment stack clears out
905 // return codes, so we need to use the flag here as a cue to return
906 // ERETARGETED
907 if (NS_SUCCEEDED(rv)) {
908 rv = NS_BINDING_RETARGETED;
909 }
910 mForceRestart = false;
911 }
912
913 // if pipe would block then we need to AsyncWait on it. have callback
914 // occur on socket thread so we stay synchronized.
915 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
916 nsCOMPtr<nsIEventTarget> target;
917 Unused << gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
918 if (target) {
919 mPipeOut->AsyncWait(this, 0, 0, target);
920 mWaitingOnPipeOut = true;
921 } else {
922 NS_ERROR("no socket thread event target");
923 rv = NS_ERROR_UNEXPECTED;
924 }
925 } else if (mThrottlingReadAllowance > 0 && NS_SUCCEEDED(rv)) {
926 MOZ_ASSERT(count >= *countWritten);
927 mThrottlingReadAllowance -= *countWritten;
928 }
929
930 return rv;
931 }
932
Close(nsresult reason)933 void nsHttpTransaction::Close(nsresult reason) {
934 LOG(("nsHttpTransaction::Close [this=%p reason=%" PRIx32 "]\n", this,
935 static_cast<uint32_t>(reason)));
936
937 if (!mClosed) {
938 gHttpHandler->ConnMgr()->RemoveActiveTransaction(this);
939 mActivated = false;
940 }
941
942 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
943 if (reason == NS_BINDING_RETARGETED) {
944 LOG((" close %p skipped due to ERETARGETED\n", this));
945 return;
946 }
947
948 if (mClosed) {
949 LOG((" already closed\n"));
950 return;
951 }
952
953 if (mTransactionObserver) {
954 mTransactionObserver->Complete(this, reason);
955 mTransactionObserver = nullptr;
956 }
957
958 if (mTokenBucketCancel) {
959 mTokenBucketCancel->Cancel(reason);
960 mTokenBucketCancel = nullptr;
961 }
962
963 if (mActivityDistributor) {
964 // report the reponse is complete if not already reported
965 if (!mResponseIsComplete) {
966 nsresult rv = mActivityDistributor->ObserveActivity(
967 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
968 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
969 static_cast<uint64_t>(mContentRead), EmptyCString());
970 if (NS_FAILED(rv)) {
971 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
972 }
973 }
974
975 // report that this transaction is closing
976 nsresult rv = mActivityDistributor->ObserveActivity(
977 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
978 NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, PR_Now(), 0,
979 EmptyCString());
980 if (NS_FAILED(rv)) {
981 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
982 }
983 }
984
985 // we must no longer reference the connection! find out if the
986 // connection was being reused before letting it go.
987 bool connReused = false;
988 if (mConnection) {
989 connReused = mConnection->IsReused();
990 }
991 mConnected = false;
992 mTunnelProvider = nullptr;
993
994 //
995 // if the connection was reset or closed before we wrote any part of the
996 // request or if we wrote the request but didn't receive any part of the
997 // response and the connection was being reused, then we can (and really
998 // should) assume that we wrote to a stale connection and we must therefore
999 // repeat the request over a new connection.
1000 //
1001 // We have decided to retry not only in case of the reused connections, but
1002 // all safe methods(bug 1236277).
1003 //
1004 // NOTE: the conditions under which we will automatically retry the HTTP
1005 // request have to be carefully selected to avoid duplication of the
1006 // request from the point-of-view of the server. such duplication could
1007 // have dire consequences including repeated purchases, etc.
1008 //
1009 // NOTE: because of the way SSL proxy CONNECT is implemented, it is
1010 // possible that the transaction may have received data without having
1011 // sent any data. for this reason, mSendData == FALSE does not imply
1012 // mReceivedData == FALSE. (see bug 203057 for more info.)
1013 //
1014 // Never restart transactions that are marked as sticky to their conenction.
1015 // We use that capability to identify transactions bound to connection based
1016 // authentication. Reissuing them on a different connections will break
1017 // this bondage. Major issue may arise when there is an NTLM message auth
1018 // header on the transaction and we send it to a different NTLM authenticated
1019 // connection. It will break that connection and also confuse the channel's
1020 // auth provider, beliving the cached credentials are wrong and asking for
1021 // the password mistakenly again from the user.
1022 if ((reason == NS_ERROR_NET_RESET || reason == NS_OK ||
1023 reason ==
1024 psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA)) &&
1025 (!(mCaps & NS_HTTP_STICKY_CONNECTION) ||
1026 (mCaps & NS_HTTP_CONNECTION_RESTARTABLE) ||
1027 (mEarlyDataDisposition == EARLY_425))) {
1028 if (mForceRestart && NS_SUCCEEDED(Restart())) {
1029 if (mResponseHead) {
1030 mResponseHead->Reset();
1031 }
1032 mContentRead = 0;
1033 mContentLength = -1;
1034 delete mChunkedDecoder;
1035 mChunkedDecoder = nullptr;
1036 mHaveStatusLine = false;
1037 mHaveAllHeaders = false;
1038 mHttpResponseMatched = false;
1039 mResponseIsComplete = false;
1040 mDidContentStart = false;
1041 mNoContent = false;
1042 mSentData = false;
1043 mReceivedData = false;
1044 LOG(("transaction force restarted\n"));
1045 return;
1046 }
1047
1048 // reallySentData is meant to separate the instances where data has
1049 // been sent by this transaction but buffered at a higher level while
1050 // a TLS session (perhaps via a tunnel) is setup.
1051 bool reallySentData =
1052 mSentData && (!mConnection || mConnection->BytesWritten());
1053
1054 if (reason ==
1055 psm::GetXPCOMFromNSSError(SSL_ERROR_DOWNGRADE_WITH_EARLY_DATA) ||
1056 (!mReceivedData && ((mRequestHead && mRequestHead->IsSafeMethod()) ||
1057 !reallySentData || connReused))) {
1058 // if restarting fails, then we must proceed to close the pipe,
1059 // which will notify the channel that the transaction failed.
1060
1061 if (NS_SUCCEEDED(Restart())) return;
1062 }
1063 }
1064
1065 if ((mChunkedDecoder || (mContentLength >= int64_t(0))) &&
1066 (NS_SUCCEEDED(reason) && !mResponseIsComplete)) {
1067 NS_WARNING("Partial transfer, incomplete HTTP response received");
1068
1069 if ((mHttpResponseCode / 100 == 2) &&
1070 (mHttpVersion >= NS_HTTP_VERSION_1_1)) {
1071 FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing();
1072 if (clevel >= FRAMECHECK_BARELY) {
1073 if ((clevel == FRAMECHECK_STRICT) ||
1074 (mChunkedDecoder && mChunkedDecoder->GetChunkRemaining()) ||
1075 (!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck)) {
1076 reason = NS_ERROR_NET_PARTIAL_TRANSFER;
1077 LOG(("Partial transfer, incomplete HTTP response received: %s",
1078 mChunkedDecoder ? "broken chunk" : "c-l underrun"));
1079 }
1080 }
1081 }
1082
1083 if (mConnection) {
1084 // whether or not we generate an error for the transaction
1085 // bad framing means we don't want a pconn
1086 mConnection->DontReuse();
1087 }
1088 }
1089
1090 bool relConn = true;
1091 if (NS_SUCCEEDED(reason)) {
1092 // the server has not sent the final \r\n terminating the header
1093 // section, and there may still be a header line unparsed. let's make
1094 // sure we parse the remaining header line, and then hopefully, the
1095 // response will be usable (see bug 88792).
1096 if (!mHaveAllHeaders) {
1097 char data = '\n';
1098 uint32_t unused;
1099 Unused << ParseHead(&data, 1, &unused);
1100
1101 if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) {
1102 // Reject 0 byte HTTP/0.9 Responses - bug 423506
1103 LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
1104 reason = NS_ERROR_NET_RESET;
1105 }
1106 }
1107
1108 // honor the sticky connection flag...
1109 if (mCaps & NS_HTTP_STICKY_CONNECTION) relConn = false;
1110 }
1111
1112 // mTimings.responseEnd is normally recorded based on the end of a
1113 // HTTP delimiter such as chunked-encodings or content-length. However,
1114 // EOF or an error still require an end time be recorded.
1115 if (TimingEnabled()) {
1116 const TimingStruct timings = Timings();
1117 if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) {
1118 SetResponseEnd(TimeStamp::Now());
1119 }
1120 }
1121
1122 if (relConn && mConnection) {
1123 MutexAutoLock lock(mLock);
1124 mConnection = nullptr;
1125 }
1126
1127 mStatus = reason;
1128 mTransactionDone = true; // forcibly flag the transaction as complete
1129 mClosed = true;
1130 ReleaseBlockingTransaction();
1131
1132 // release some resources that we no longer need
1133 mRequestStream = nullptr;
1134 mReqHeaderBuf.Truncate();
1135 mLineBuf.Truncate();
1136 if (mChunkedDecoder) {
1137 delete mChunkedDecoder;
1138 mChunkedDecoder = nullptr;
1139 }
1140
1141 // closing this pipe triggers the channel's OnStopRequest method.
1142 mPipeOut->CloseWithStatus(reason);
1143
1144 #ifdef WIN32 // bug 1153929
1145 MOZ_DIAGNOSTIC_ASSERT(mPipeOut);
1146 uint32_t *vtable = (uint32_t *)mPipeOut.get();
1147 MOZ_DIAGNOSTIC_ASSERT(*vtable != 0);
1148 mPipeOut = nullptr; // just in case
1149 #endif // WIN32
1150 }
1151
ConnectionInfo()1152 nsHttpConnectionInfo *nsHttpTransaction::ConnectionInfo() {
1153 return mConnInfo.get();
1154 }
1155
1156 bool // NOTE BASE CLASS
ResponseTimeoutEnabled() const1157 nsAHttpTransaction::ResponseTimeoutEnabled() const {
1158 return false;
1159 }
1160
1161 PRIntervalTime // NOTE BASE CLASS
ResponseTimeout()1162 nsAHttpTransaction::ResponseTimeout() {
1163 return gHttpHandler->ResponseTimeout();
1164 }
1165
ResponseTimeoutEnabled() const1166 bool nsHttpTransaction::ResponseTimeoutEnabled() const {
1167 return mResponseTimeoutEnabled;
1168 }
1169
1170 //-----------------------------------------------------------------------------
1171 // nsHttpTransaction <private>
1172 //-----------------------------------------------------------------------------
1173
Restart()1174 nsresult nsHttpTransaction::Restart() {
1175 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1176
1177 // limit the number of restart attempts - bug 92224
1178 if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
1179 LOG(("reached max request attempts, failing transaction @%p\n", this));
1180 return NS_ERROR_NET_RESET;
1181 }
1182
1183 LOG(("restarting transaction @%p\n", this));
1184 mTunnelProvider = nullptr;
1185
1186 // rewind streams in case we already wrote out the request
1187 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
1188 if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
1189
1190 // clear old connection state...
1191 mSecurityInfo = nullptr;
1192 if (mConnection) {
1193 if (!mReuseOnRestart) {
1194 mConnection->DontReuse();
1195 }
1196 MutexAutoLock lock(mLock);
1197 mConnection = nullptr;
1198 }
1199
1200 // Reset this to our default state, since this may change from one restart
1201 // to the next
1202 mReuseOnRestart = false;
1203
1204 if (!mConnInfo->GetRoutedHost().IsEmpty()) {
1205 MutexAutoLock lock(*nsHttp::GetLock());
1206 RefPtr<nsHttpConnectionInfo> ci;
1207 mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
1208 mConnInfo = ci;
1209 if (mRequestHead) {
1210 DebugOnly<nsresult> rv = mRequestHead->SetHeader(
1211 nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("0"));
1212 MOZ_ASSERT(NS_SUCCEEDED(rv));
1213 }
1214 }
1215
1216 return gHttpHandler->InitiateTransaction(this, mPriority);
1217 }
1218
LocateHttpStart(char * buf,uint32_t len,bool aAllowPartialMatch)1219 char *nsHttpTransaction::LocateHttpStart(char *buf, uint32_t len,
1220 bool aAllowPartialMatch) {
1221 MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty());
1222
1223 static const char HTTPHeader[] = "HTTP/1.";
1224 static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1;
1225 static const char HTTP2Header[] = "HTTP/2.0";
1226 static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1;
1227 // ShoutCast ICY is treated as HTTP/1.0
1228 static const char ICYHeader[] = "ICY ";
1229 static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1;
1230
1231 if (aAllowPartialMatch && (len < HTTPHeaderLen))
1232 return (PL_strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr;
1233
1234 // mLineBuf can contain partial match from previous search
1235 if (!mLineBuf.IsEmpty()) {
1236 MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen);
1237 int32_t checkChars = std::min(len, HTTPHeaderLen - mLineBuf.Length());
1238 if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(), checkChars) == 0) {
1239 mLineBuf.Append(buf, checkChars);
1240 if (mLineBuf.Length() == HTTPHeaderLen) {
1241 // We've found whole HTTPHeader sequence. Return pointer at the
1242 // end of matched sequence since it is stored in mLineBuf.
1243 return (buf + checkChars);
1244 }
1245 // Response matches pattern but is still incomplete.
1246 return 0;
1247 }
1248 // Previous partial match together with new data doesn't match the
1249 // pattern. Start the search again.
1250 mLineBuf.Truncate();
1251 }
1252
1253 bool firstByte = true;
1254 while (len > 0) {
1255 if (PL_strncasecmp(buf, HTTPHeader,
1256 std::min<uint32_t>(len, HTTPHeaderLen)) == 0) {
1257 if (len < HTTPHeaderLen) {
1258 // partial HTTPHeader sequence found
1259 // save partial match to mLineBuf
1260 mLineBuf.Assign(buf, len);
1261 return 0;
1262 }
1263
1264 // whole HTTPHeader sequence found
1265 return buf;
1266 }
1267
1268 // At least "SmarterTools/2.0.3974.16813" generates nonsensical
1269 // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
1270 // it as HTTP/1.1 to be compatible with old versions of ourselves and
1271 // other browsers
1272
1273 if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen &&
1274 (PL_strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) {
1275 LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
1276 return buf;
1277 }
1278
1279 // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion
1280 // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted
1281 // as HTTP/1.0 in nsHttpResponseHead::ParseVersion
1282
1283 if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen &&
1284 (PL_strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) {
1285 LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));
1286 return buf;
1287 }
1288
1289 if (!nsCRT::IsAsciiSpace(*buf)) firstByte = false;
1290 buf++;
1291 len--;
1292 }
1293 return 0;
1294 }
1295
ParseLine(nsACString & line)1296 nsresult nsHttpTransaction::ParseLine(nsACString &line) {
1297 LOG(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get()));
1298 nsresult rv = NS_OK;
1299
1300 if (!mHaveStatusLine) {
1301 mResponseHead->ParseStatusLine(line);
1302 mHaveStatusLine = true;
1303 // XXX this should probably never happen
1304 if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) mHaveAllHeaders = true;
1305 } else {
1306 rv = mResponseHead->ParseHeaderLine(line);
1307 }
1308 return rv;
1309 }
1310
ParseLineSegment(char * segment,uint32_t len)1311 nsresult nsHttpTransaction::ParseLineSegment(char *segment, uint32_t len) {
1312 NS_PRECONDITION(!mHaveAllHeaders, "already have all headers");
1313
1314 if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
1315 // trim off the new line char, and if this segment is
1316 // not a continuation of the previous or if we haven't
1317 // parsed the status line yet, then parse the contents
1318 // of mLineBuf.
1319 mLineBuf.Truncate(mLineBuf.Length() - 1);
1320 if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
1321 nsresult rv = ParseLine(mLineBuf);
1322 mLineBuf.Truncate();
1323 if (NS_FAILED(rv)) {
1324 return rv;
1325 }
1326 }
1327 }
1328
1329 // append segment to mLineBuf...
1330 mLineBuf.Append(segment, len);
1331
1332 // a line buf with only a new line char signifies the end of headers.
1333 if (mLineBuf.First() == '\n') {
1334 mLineBuf.Truncate();
1335 // discard this response if it is a 100 continue or other 1xx status.
1336 uint16_t status = mResponseHead->Status();
1337 if ((status != 101) && (status / 100 == 1)) {
1338 LOG(("ignoring 1xx response\n"));
1339 mHaveStatusLine = false;
1340 mHttpResponseMatched = false;
1341 mConnection->SetLastTransactionExpectedNoContent(true);
1342 mResponseHead->Reset();
1343 return NS_OK;
1344 }
1345 mHaveAllHeaders = true;
1346 }
1347 return NS_OK;
1348 }
1349
ParseHead(char * buf,uint32_t count,uint32_t * countRead)1350 nsresult nsHttpTransaction::ParseHead(char *buf, uint32_t count,
1351 uint32_t *countRead) {
1352 nsresult rv;
1353 uint32_t len;
1354 char *eol;
1355
1356 LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
1357
1358 *countRead = 0;
1359
1360 NS_PRECONDITION(!mHaveAllHeaders, "oops");
1361
1362 // allocate the response head object if necessary
1363 if (!mResponseHead) {
1364 mResponseHead = new nsHttpResponseHead();
1365 if (!mResponseHead) return NS_ERROR_OUT_OF_MEMORY;
1366
1367 // report that we have a least some of the response
1368 if (mActivityDistributor && !mReportedStart) {
1369 mReportedStart = true;
1370 rv = mActivityDistributor->ObserveActivity(
1371 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
1372 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, PR_Now(), 0, EmptyCString());
1373 if (NS_FAILED(rv)) {
1374 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
1375 }
1376 }
1377 }
1378
1379 if (!mHttpResponseMatched) {
1380 // Normally we insist on seeing HTTP/1.x in the first few bytes,
1381 // but if we are on a persistent connection and the previous transaction
1382 // was not supposed to have any content then we need to be prepared
1383 // to skip over a response body that the server may have sent even
1384 // though it wasn't allowed.
1385 if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
1386 // tolerate only minor junk before the status line
1387 mHttpResponseMatched = true;
1388 char *p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
1389 if (!p) {
1390 // Treat any 0.9 style response of a put as a failure.
1391 if (mRequestHead->IsPut()) return NS_ERROR_ABORT;
1392
1393 mResponseHead->ParseStatusLine(EmptyCString());
1394 mHaveStatusLine = true;
1395 mHaveAllHeaders = true;
1396 return NS_OK;
1397 }
1398 if (p > buf) {
1399 // skip over the junk
1400 mInvalidResponseBytesRead += p - buf;
1401 *countRead = p - buf;
1402 buf = p;
1403 }
1404 } else {
1405 char *p = LocateHttpStart(buf, count, false);
1406 if (p) {
1407 mInvalidResponseBytesRead += p - buf;
1408 *countRead = p - buf;
1409 buf = p;
1410 mHttpResponseMatched = true;
1411 } else {
1412 mInvalidResponseBytesRead += count;
1413 *countRead = count;
1414 if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
1415 LOG(
1416 ("nsHttpTransaction::ParseHead() "
1417 "Cannot find Response Header\n"));
1418 // cannot go back and call this 0.9 anymore as we
1419 // have thrown away a lot of the leading junk
1420 return NS_ERROR_ABORT;
1421 }
1422 return NS_OK;
1423 }
1424 }
1425 }
1426 // otherwise we can assume that we don't have a HTTP/0.9 response.
1427
1428 MOZ_ASSERT(mHttpResponseMatched);
1429 while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) !=
1430 nullptr) {
1431 // found line in range [buf:eol]
1432 len = eol - buf + 1;
1433
1434 *countRead += len;
1435
1436 // actually, the line is in the range [buf:eol-1]
1437 if ((eol > buf) && (*(eol - 1) == '\r')) len--;
1438
1439 buf[len - 1] = '\n';
1440 rv = ParseLineSegment(buf, len);
1441 if (NS_FAILED(rv)) return rv;
1442
1443 if (mHaveAllHeaders) return NS_OK;
1444
1445 // skip over line
1446 buf = eol + 1;
1447
1448 if (!mHttpResponseMatched) {
1449 // a 100 class response has caused us to throw away that set of
1450 // response headers and look for the next response
1451 return NS_ERROR_NET_INTERRUPT;
1452 }
1453 }
1454
1455 // do something about a partial header line
1456 if (!mHaveAllHeaders && (len = count - *countRead)) {
1457 *countRead = count;
1458 // ignore a trailing carriage return, and don't bother calling
1459 // ParseLineSegment if buf only contains a carriage return.
1460 if ((buf[len - 1] == '\r') && (--len == 0)) return NS_OK;
1461 rv = ParseLineSegment(buf, len);
1462 if (NS_FAILED(rv)) return rv;
1463 }
1464 return NS_OK;
1465 }
1466
HandleContentStart()1467 nsresult nsHttpTransaction::HandleContentStart() {
1468 LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
1469 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1470
1471 if (mResponseHead) {
1472 if (mEarlyDataDisposition == EARLY_ACCEPTED) {
1473 if (mResponseHead->Status() == 425) {
1474 // We will report this state when the final responce arrives.
1475 mEarlyDataDisposition = EARLY_425;
1476 } else {
1477 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
1478 NS_LITERAL_CSTRING("accepted"));
1479 }
1480 } else if (mEarlyDataDisposition == EARLY_SENT) {
1481 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
1482 NS_LITERAL_CSTRING("sent"));
1483 } else if (mEarlyDataDisposition == EARLY_425) {
1484 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_Early_Data,
1485 NS_LITERAL_CSTRING("received 425"));
1486 mEarlyDataDisposition = EARLY_NONE;
1487 } // no header on NONE case
1488
1489 if (mFastOpenStatus == TFO_DATA_SENT) {
1490 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,
1491 NS_LITERAL_CSTRING("data sent"));
1492 } else if (mFastOpenStatus == TFO_TRIED) {
1493 Unused << mResponseHead->SetHeader(
1494 nsHttp::X_Firefox_TCP_Fast_Open,
1495 NS_LITERAL_CSTRING("tried negotiating"));
1496 } else if (mFastOpenStatus == TFO_FAILED) {
1497 Unused << mResponseHead->SetHeader(nsHttp::X_Firefox_TCP_Fast_Open,
1498 NS_LITERAL_CSTRING("failed"));
1499 } // no header on TFO_NOT_TRIED case
1500
1501 if (LOG3_ENABLED()) {
1502 LOG3(("http response [\n"));
1503 nsAutoCString headers;
1504 mResponseHead->Flatten(headers, false);
1505 headers.AppendLiteral(" OriginalHeaders");
1506 headers.AppendLiteral("\r\n");
1507 mResponseHead->FlattenNetworkOriginalHeaders(headers);
1508 LogHeaders(headers.get());
1509 LOG3(("]\n"));
1510 }
1511
1512 CheckForStickyAuthScheme();
1513
1514 // Save http version, mResponseHead isn't available anymore after
1515 // TakeResponseHead() is called
1516 mHttpVersion = mResponseHead->Version();
1517 mHttpResponseCode = mResponseHead->Status();
1518
1519 // notify the connection, give it a chance to cause a reset.
1520 bool reset = false;
1521 nsresult rv = mConnection->OnHeadersAvailable(this, mRequestHead,
1522 mResponseHead, &reset);
1523 NS_ENSURE_SUCCESS(rv, rv);
1524
1525 // looks like we should ignore this response, resetting...
1526 if (reset) {
1527 LOG(("resetting transaction's response head\n"));
1528 mHaveAllHeaders = false;
1529 mHaveStatusLine = false;
1530 mReceivedData = false;
1531 mSentData = false;
1532 mHttpResponseMatched = false;
1533 mResponseHead->Reset();
1534 // wait to be called again...
1535 return NS_OK;
1536 }
1537
1538 // check if this is a no-content response
1539 switch (mResponseHead->Status()) {
1540 case 101:
1541 mPreserveStream = true;
1542 MOZ_FALLTHROUGH; // to other no content cases:
1543 case 204:
1544 case 205:
1545 case 304:
1546 mNoContent = true;
1547 LOG(("this response should not contain a body.\n"));
1548 break;
1549 case 421:
1550 LOG(("Misdirected Request.\n"));
1551 gHttpHandler->ConnMgr()->ClearHostMapping(mConnInfo);
1552
1553 // retry on a new connection - just in case
1554 if (!mRestartCount) {
1555 mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE;
1556 mForceRestart = true; // force restart has built in loop protection
1557 return NS_ERROR_NET_RESET;
1558 }
1559 break;
1560 case 425:
1561 LOG(("Too Early."));
1562 if ((mEarlyDataDisposition == EARLY_425) && !mDoNotTryEarlyData) {
1563 mDoNotTryEarlyData = true;
1564 mForceRestart = true; // force restart has built in loop protection
1565 if (mConnection->Version() == HTTP_VERSION_2) {
1566 mReuseOnRestart = true;
1567 }
1568 return NS_ERROR_NET_RESET;
1569 }
1570 break;
1571 }
1572
1573 if (mResponseHead->Status() == 200 &&
1574 mConnection->IsProxyConnectInProgress()) {
1575 // successful CONNECTs do not have response bodies
1576 mNoContent = true;
1577 }
1578 mConnection->SetLastTransactionExpectedNoContent(mNoContent);
1579
1580 if (mNoContent) {
1581 mContentLength = 0;
1582 } else {
1583 // grab the content-length from the response headers
1584 mContentLength = mResponseHead->ContentLength();
1585
1586 // handle chunked encoding here, so we'll know immediately when
1587 // we're done with the socket. please note that _all_ other
1588 // decoding is done when the channel receives the content data
1589 // so as not to block the socket transport thread too much.
1590 if (mResponseHead->Version() >= NS_HTTP_VERSION_1_0 &&
1591 mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
1592 // we only support the "chunked" transfer encoding right now.
1593 mChunkedDecoder = new nsHttpChunkedDecoder();
1594 LOG(("nsHttpTransaction %p chunked decoder created\n", this));
1595 // Ignore server specified Content-Length.
1596 if (mContentLength != int64_t(-1)) {
1597 LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this));
1598 mContentLength = -1;
1599 if (mConnection) {
1600 mConnection->DontReuse();
1601 }
1602 }
1603 } else if (mContentLength == int64_t(-1))
1604 LOG(("waiting for the server to close the connection.\n"));
1605 }
1606 }
1607
1608 mDidContentStart = true;
1609 return NS_OK;
1610 }
1611
1612 // called on the socket thread
HandleContent(char * buf,uint32_t count,uint32_t * contentRead,uint32_t * contentRemaining)1613 nsresult nsHttpTransaction::HandleContent(char *buf, uint32_t count,
1614 uint32_t *contentRead,
1615 uint32_t *contentRemaining) {
1616 nsresult rv;
1617
1618 LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count));
1619
1620 *contentRead = 0;
1621 *contentRemaining = 0;
1622
1623 MOZ_ASSERT(mConnection);
1624
1625 if (!mDidContentStart) {
1626 rv = HandleContentStart();
1627 if (NS_FAILED(rv)) return rv;
1628 // Do not write content to the pipe if we haven't started streaming yet
1629 if (!mDidContentStart) return NS_OK;
1630 }
1631
1632 if (mChunkedDecoder) {
1633 // give the buf over to the chunked decoder so it can reformat the
1634 // data and tell us how much is really there.
1635 rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead,
1636 contentRemaining);
1637 if (NS_FAILED(rv)) return rv;
1638 } else if (mContentLength >= int64_t(0)) {
1639 // HTTP/1.0 servers have been known to send erroneous Content-Length
1640 // headers. So, unless the connection is persistent, we must make
1641 // allowances for a possibly invalid Content-Length header. Thus, if
1642 // NOT persistent, we simply accept everything in |buf|.
1643 if (mConnection->IsPersistent() || mPreserveStream ||
1644 mHttpVersion >= NS_HTTP_VERSION_1_1) {
1645 int64_t remaining = mContentLength - mContentRead;
1646 *contentRead = uint32_t(std::min<int64_t>(count, remaining));
1647 *contentRemaining = count - *contentRead;
1648 } else {
1649 *contentRead = count;
1650 // mContentLength might need to be increased...
1651 int64_t position = mContentRead + int64_t(count);
1652 if (position > mContentLength) {
1653 mContentLength = position;
1654 // mResponseHead->SetContentLength(mContentLength);
1655 }
1656 }
1657 } else {
1658 // when we are just waiting for the server to close the connection...
1659 // (no explicit content-length given)
1660 *contentRead = count;
1661 }
1662
1663 if (*contentRead) {
1664 // update count of content bytes read and report progress...
1665 mContentRead += *contentRead;
1666 }
1667
1668 LOG(
1669 ("nsHttpTransaction::HandleContent [this=%p count=%u read=%u "
1670 "mContentRead=%" PRId64 " mContentLength=%" PRId64 "]\n",
1671 this, count, *contentRead, mContentRead, mContentLength));
1672
1673 // check for end-of-file
1674 if ((mContentRead == mContentLength) ||
1675 (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
1676 MutexAutoLock lock(*nsHttp::GetLock());
1677 if (mChunkedDecoder) {
1678 mForTakeResponseTrailers = mChunkedDecoder->TakeTrailers();
1679 }
1680
1681 // the transaction is done with a complete response.
1682 mTransactionDone = true;
1683 mResponseIsComplete = true;
1684 ReleaseBlockingTransaction();
1685
1686 if (TimingEnabled()) {
1687 SetResponseEnd(TimeStamp::Now());
1688 }
1689
1690 // report the entire response has arrived
1691 if (mActivityDistributor) {
1692 rv = mActivityDistributor->ObserveActivity(
1693 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
1694 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, PR_Now(),
1695 static_cast<uint64_t>(mContentRead), EmptyCString());
1696 if (NS_FAILED(rv)) {
1697 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
1698 }
1699 }
1700 }
1701
1702 return NS_OK;
1703 }
1704
ProcessData(char * buf,uint32_t count,uint32_t * countRead)1705 nsresult nsHttpTransaction::ProcessData(char *buf, uint32_t count,
1706 uint32_t *countRead) {
1707 nsresult rv;
1708
1709 LOG(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count));
1710
1711 *countRead = 0;
1712
1713 // we may not have read all of the headers yet...
1714 if (!mHaveAllHeaders) {
1715 uint32_t bytesConsumed = 0;
1716
1717 do {
1718 uint32_t localBytesConsumed = 0;
1719 char *localBuf = buf + bytesConsumed;
1720 uint32_t localCount = count - bytesConsumed;
1721
1722 rv = ParseHead(localBuf, localCount, &localBytesConsumed);
1723 if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT) return rv;
1724 bytesConsumed += localBytesConsumed;
1725 } while (rv == NS_ERROR_NET_INTERRUPT);
1726
1727 mCurrentHttpResponseHeaderSize += bytesConsumed;
1728 if (mCurrentHttpResponseHeaderSize >
1729 gHttpHandler->MaxHttpResponseHeaderSize()) {
1730 LOG(("nsHttpTransaction %p The response header exceeds the limit.\n",
1731 this));
1732 return NS_ERROR_FILE_TOO_BIG;
1733 }
1734 count -= bytesConsumed;
1735
1736 // if buf has some content in it, shift bytes to top of buf.
1737 if (count && bytesConsumed) memmove(buf, buf + bytesConsumed, count);
1738
1739 // report the completed response header
1740 if (mActivityDistributor && mResponseHead && mHaveAllHeaders &&
1741 !mReportedResponseHeader) {
1742 mReportedResponseHeader = true;
1743 nsAutoCString completeResponseHeaders;
1744 mResponseHead->Flatten(completeResponseHeaders, false);
1745 completeResponseHeaders.AppendLiteral("\r\n");
1746 rv = mActivityDistributor->ObserveActivity(
1747 mChannel, NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
1748 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER, PR_Now(), 0,
1749 completeResponseHeaders);
1750 if (NS_FAILED(rv)) {
1751 LOG3(("ObserveActivity failed (%08x)", static_cast<uint32_t>(rv)));
1752 }
1753 }
1754 }
1755
1756 // even though count may be 0, we still want to call HandleContent
1757 // so it can complete the transaction if this is a "no-content" response.
1758 if (mHaveAllHeaders) {
1759 uint32_t countRemaining = 0;
1760 //
1761 // buf layout:
1762 //
1763 // +--------------------------------------+----------------+-----+
1764 // | countRead | countRemaining | |
1765 // +--------------------------------------+----------------+-----+
1766 //
1767 // count : bytes read from the socket
1768 // countRead : bytes corresponding to this transaction
1769 // countRemaining : bytes corresponding to next transaction on conn
1770 //
1771 // NOTE:
1772 // count > countRead + countRemaining <==> chunked transfer encoding
1773 //
1774 rv = HandleContent(buf, count, countRead, &countRemaining);
1775 if (NS_FAILED(rv)) return rv;
1776 // we may have read more than our share, in which case we must give
1777 // the excess bytes back to the connection
1778 if (mResponseIsComplete && countRemaining) {
1779 MOZ_ASSERT(mConnection);
1780 rv = mConnection->PushBack(buf + *countRead, countRemaining);
1781 NS_ENSURE_SUCCESS(rv, rv);
1782 }
1783
1784 if (!mContentDecodingCheck && mResponseHead) {
1785 mContentDecoding = mResponseHead->HasHeader(nsHttp::Content_Encoding);
1786 mContentDecodingCheck = true;
1787 }
1788 }
1789
1790 return NS_OK;
1791 }
1792
SetRequestContext(nsIRequestContext * aRequestContext)1793 void nsHttpTransaction::SetRequestContext(nsIRequestContext *aRequestContext) {
1794 LOG(("nsHttpTransaction %p SetRequestContext %p\n", this, aRequestContext));
1795 mRequestContext = aRequestContext;
1796 }
1797
1798 // Called when the transaction marked for blocking is associated with a
1799 // connection (i.e. added to a new h1 conn, an idle http connection, etc..) It
1800 // is safe to call this multiple times with it only having an effect once.
DispatchedAsBlocking()1801 void nsHttpTransaction::DispatchedAsBlocking() {
1802 if (mDispatchedAsBlocking) return;
1803
1804 LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
1805
1806 if (!mRequestContext) return;
1807
1808 LOG(
1809 ("nsHttpTransaction adding blocking transaction %p from "
1810 "request context %p\n",
1811 this, mRequestContext.get()));
1812
1813 mRequestContext->AddBlockingTransaction();
1814 mDispatchedAsBlocking = true;
1815 }
1816
RemoveDispatchedAsBlocking()1817 void nsHttpTransaction::RemoveDispatchedAsBlocking() {
1818 if (!mRequestContext || !mDispatchedAsBlocking) {
1819 LOG(("nsHttpTransaction::RemoveDispatchedAsBlocking this=%p not blocking",
1820 this));
1821 return;
1822 }
1823
1824 uint32_t blockers = 0;
1825 nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers);
1826
1827 LOG(
1828 ("nsHttpTransaction removing blocking transaction %p from "
1829 "request context %p. %d blockers remain.\n",
1830 this, mRequestContext.get(), blockers));
1831
1832 if (NS_SUCCEEDED(rv) && !blockers) {
1833 LOG(
1834 ("nsHttpTransaction %p triggering release of blocked channels "
1835 " with request context=%p\n",
1836 this, mRequestContext.get()));
1837 rv = gHttpHandler->ConnMgr()->ProcessPendingQ();
1838 if (NS_FAILED(rv)) {
1839 LOG(
1840 ("nsHttpTransaction::RemoveDispatchedAsBlocking\n"
1841 " failed to process pending queue\n"));
1842 }
1843 }
1844
1845 mDispatchedAsBlocking = false;
1846 }
1847
ReleaseBlockingTransaction()1848 void nsHttpTransaction::ReleaseBlockingTransaction() {
1849 RemoveDispatchedAsBlocking();
1850 LOG(
1851 ("nsHttpTransaction %p request context set to null "
1852 "in ReleaseBlockingTransaction() - was %p\n",
1853 this, mRequestContext.get()));
1854 mRequestContext = nullptr;
1855 }
1856
DisableSpdy()1857 void nsHttpTransaction::DisableSpdy() {
1858 mCaps |= NS_HTTP_DISALLOW_SPDY;
1859 if (mConnInfo) {
1860 // This is our clone of the connection info, not the persistent one that
1861 // is owned by the connection manager, so we're safe to change this here
1862 mConnInfo->SetNoSpdy(true);
1863 }
1864 }
1865
CheckForStickyAuthScheme()1866 void nsHttpTransaction::CheckForStickyAuthScheme() {
1867 LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p", this));
1868
1869 MOZ_ASSERT(mHaveAllHeaders);
1870 MOZ_ASSERT(mResponseHead);
1871 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
1872
1873 CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate);
1874 CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate);
1875 }
1876
CheckForStickyAuthSchemeAt(nsHttpAtom const & header)1877 void nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom const &header) {
1878 if (mCaps & NS_HTTP_STICKY_CONNECTION) {
1879 LOG((" already sticky"));
1880 return;
1881 }
1882
1883 nsAutoCString auth;
1884 if (NS_FAILED(mResponseHead->GetHeader(header, auth))) {
1885 return;
1886 }
1887
1888 Tokenizer p(auth);
1889 nsAutoCString schema;
1890 while (p.ReadWord(schema)) {
1891 ToLowerCase(schema);
1892
1893 nsAutoCString contractid;
1894 contractid.AssignLiteral(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX);
1895 contractid.Append(schema);
1896
1897 // using a new instance because of thread safety of auth modules refcnt
1898 nsCOMPtr<nsIHttpAuthenticator> authenticator(
1899 do_CreateInstance(contractid.get()));
1900 if (authenticator) {
1901 uint32_t flags;
1902 nsresult rv = authenticator->GetAuthFlags(&flags);
1903 if (NS_SUCCEEDED(rv) &&
1904 (flags & nsIHttpAuthenticator::CONNECTION_BASED)) {
1905 LOG((" connection made sticky, found %s auth shema", schema.get()));
1906 // This is enough to make this transaction keep it's current connection,
1907 // prevents the connection from being released back to the pool.
1908 mCaps |= NS_HTTP_STICKY_CONNECTION;
1909 break;
1910 }
1911 }
1912
1913 // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader
1914 p.SkipUntil(Tokenizer::Token::NewLine());
1915 p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE);
1916 }
1917 }
1918
Timings()1919 const TimingStruct nsHttpTransaction::Timings() {
1920 mozilla::MutexAutoLock lock(mLock);
1921 TimingStruct timings = mTimings;
1922 return timings;
1923 }
1924
BootstrapTimings(TimingStruct times)1925 void nsHttpTransaction::BootstrapTimings(TimingStruct times) {
1926 mozilla::MutexAutoLock lock(mLock);
1927 mTimings = times;
1928 }
1929
SetDomainLookupStart(mozilla::TimeStamp timeStamp,bool onlyIfNull)1930 void nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp,
1931 bool onlyIfNull) {
1932 mozilla::MutexAutoLock lock(mLock);
1933 if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) {
1934 return; // We only set the timestamp if it was previously null
1935 }
1936 mTimings.domainLookupStart = timeStamp;
1937 }
1938
SetDomainLookupEnd(mozilla::TimeStamp timeStamp,bool onlyIfNull)1939 void nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp,
1940 bool onlyIfNull) {
1941 mozilla::MutexAutoLock lock(mLock);
1942 if (onlyIfNull && !mTimings.domainLookupEnd.IsNull()) {
1943 return; // We only set the timestamp if it was previously null
1944 }
1945 mTimings.domainLookupEnd = timeStamp;
1946 }
1947
SetConnectStart(mozilla::TimeStamp timeStamp,bool onlyIfNull)1948 void nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp,
1949 bool onlyIfNull) {
1950 mozilla::MutexAutoLock lock(mLock);
1951 if (onlyIfNull && !mTimings.connectStart.IsNull()) {
1952 return; // We only set the timestamp if it was previously null
1953 }
1954 mTimings.connectStart = timeStamp;
1955 }
1956
SetConnectEnd(mozilla::TimeStamp timeStamp,bool onlyIfNull)1957 void nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp,
1958 bool onlyIfNull) {
1959 mozilla::MutexAutoLock lock(mLock);
1960 if (onlyIfNull && !mTimings.connectEnd.IsNull()) {
1961 return; // We only set the timestamp if it was previously null
1962 }
1963 mTimings.connectEnd = timeStamp;
1964 }
1965
SetRequestStart(mozilla::TimeStamp timeStamp,bool onlyIfNull)1966 void nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp,
1967 bool onlyIfNull) {
1968 mozilla::MutexAutoLock lock(mLock);
1969 if (onlyIfNull && !mTimings.requestStart.IsNull()) {
1970 return; // We only set the timestamp if it was previously null
1971 }
1972 mTimings.requestStart = timeStamp;
1973 }
1974
SetResponseStart(mozilla::TimeStamp timeStamp,bool onlyIfNull)1975 void nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp,
1976 bool onlyIfNull) {
1977 mozilla::MutexAutoLock lock(mLock);
1978 if (onlyIfNull && !mTimings.responseStart.IsNull()) {
1979 return; // We only set the timestamp if it was previously null
1980 }
1981 mTimings.responseStart = timeStamp;
1982 }
1983
SetResponseEnd(mozilla::TimeStamp timeStamp,bool onlyIfNull)1984 void nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp,
1985 bool onlyIfNull) {
1986 mozilla::MutexAutoLock lock(mLock);
1987 if (onlyIfNull && !mTimings.responseEnd.IsNull()) {
1988 return; // We only set the timestamp if it was previously null
1989 }
1990 mTimings.responseEnd = timeStamp;
1991 }
1992
GetDomainLookupStart()1993 mozilla::TimeStamp nsHttpTransaction::GetDomainLookupStart() {
1994 mozilla::MutexAutoLock lock(mLock);
1995 return mTimings.domainLookupStart;
1996 }
1997
GetDomainLookupEnd()1998 mozilla::TimeStamp nsHttpTransaction::GetDomainLookupEnd() {
1999 mozilla::MutexAutoLock lock(mLock);
2000 return mTimings.domainLookupEnd;
2001 }
2002
GetConnectStart()2003 mozilla::TimeStamp nsHttpTransaction::GetConnectStart() {
2004 mozilla::MutexAutoLock lock(mLock);
2005 return mTimings.connectStart;
2006 }
2007
GetTcpConnectEnd()2008 mozilla::TimeStamp nsHttpTransaction::GetTcpConnectEnd() {
2009 mozilla::MutexAutoLock lock(mLock);
2010 return mTimings.tcpConnectEnd;
2011 }
2012
GetSecureConnectionStart()2013 mozilla::TimeStamp nsHttpTransaction::GetSecureConnectionStart() {
2014 mozilla::MutexAutoLock lock(mLock);
2015 return mTimings.secureConnectionStart;
2016 }
2017
GetConnectEnd()2018 mozilla::TimeStamp nsHttpTransaction::GetConnectEnd() {
2019 mozilla::MutexAutoLock lock(mLock);
2020 return mTimings.connectEnd;
2021 }
2022
GetRequestStart()2023 mozilla::TimeStamp nsHttpTransaction::GetRequestStart() {
2024 mozilla::MutexAutoLock lock(mLock);
2025 return mTimings.requestStart;
2026 }
2027
GetResponseStart()2028 mozilla::TimeStamp nsHttpTransaction::GetResponseStart() {
2029 mozilla::MutexAutoLock lock(mLock);
2030 return mTimings.responseStart;
2031 }
2032
GetResponseEnd()2033 mozilla::TimeStamp nsHttpTransaction::GetResponseEnd() {
2034 mozilla::MutexAutoLock lock(mLock);
2035 return mTimings.responseEnd;
2036 }
2037
2038 //-----------------------------------------------------------------------------
2039 // nsHttpTransaction deletion event
2040 //-----------------------------------------------------------------------------
2041
2042 class DeleteHttpTransaction : public Runnable {
2043 public:
DeleteHttpTransaction(nsHttpTransaction * trans)2044 explicit DeleteHttpTransaction(nsHttpTransaction *trans)
2045 : Runnable("net::DeleteHttpTransaction"), mTrans(trans) {}
2046
Run()2047 NS_IMETHOD Run() override {
2048 delete mTrans;
2049 return NS_OK;
2050 }
2051
2052 private:
2053 nsHttpTransaction *mTrans;
2054 };
2055
DeleteSelfOnConsumerThread()2056 void nsHttpTransaction::DeleteSelfOnConsumerThread() {
2057 LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
2058
2059 bool val;
2060 if (!mConsumerTarget ||
2061 (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
2062 delete this;
2063 } else {
2064 LOG(("proxying delete to consumer thread...\n"));
2065 nsCOMPtr<nsIRunnable> event = new DeleteHttpTransaction(this);
2066 if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL)))
2067 NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
2068 }
2069 }
2070
TryToRunPacedRequest()2071 bool nsHttpTransaction::TryToRunPacedRequest() {
2072 if (mSubmittedRatePacing) return mPassedRatePacing;
2073
2074 mSubmittedRatePacing = true;
2075 mSynchronousRatePaceRequest = true;
2076 Unused << gHttpHandler->SubmitPacedRequest(
2077 this, getter_AddRefs(mTokenBucketCancel));
2078 mSynchronousRatePaceRequest = false;
2079 return mPassedRatePacing;
2080 }
2081
OnTokenBucketAdmitted()2082 void nsHttpTransaction::OnTokenBucketAdmitted() {
2083 mPassedRatePacing = true;
2084 mTokenBucketCancel = nullptr;
2085
2086 if (!mSynchronousRatePaceRequest) {
2087 nsresult rv = gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
2088 if (NS_FAILED(rv)) {
2089 LOG(
2090 ("nsHttpTransaction::OnTokenBucketAdmitted\n"
2091 " failed to process pending queue\n"));
2092 }
2093 }
2094 }
2095
CancelPacing(nsresult reason)2096 void nsHttpTransaction::CancelPacing(nsresult reason) {
2097 if (mTokenBucketCancel) {
2098 mTokenBucketCancel->Cancel(reason);
2099 mTokenBucketCancel = nullptr;
2100 }
2101 }
2102
2103 //-----------------------------------------------------------------------------
2104 // nsHttpTransaction::nsISupports
2105 //-----------------------------------------------------------------------------
2106
2107 NS_IMPL_ADDREF(nsHttpTransaction)
2108
NS_IMETHODIMP_(MozExternalRefCountType)2109 NS_IMETHODIMP_(MozExternalRefCountType)
2110 nsHttpTransaction::Release() {
2111 nsrefcnt count;
2112 NS_PRECONDITION(0 != mRefCnt, "dup release");
2113 count = --mRefCnt;
2114 NS_LOG_RELEASE(this, count, "nsHttpTransaction");
2115 if (0 == count) {
2116 mRefCnt = 1; /* stablize */
2117 // it is essential that the transaction be destroyed on the consumer
2118 // thread (we could be holding the last reference to our consumer).
2119 DeleteSelfOnConsumerThread();
2120 return 0;
2121 }
2122 return count;
2123 }
2124
NS_IMPL_QUERY_INTERFACE(nsHttpTransaction,nsIInputStreamCallback,nsIOutputStreamCallback)2125 NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, nsIInputStreamCallback,
2126 nsIOutputStreamCallback)
2127
2128 //-----------------------------------------------------------------------------
2129 // nsHttpTransaction::nsIInputStreamCallback
2130 //-----------------------------------------------------------------------------
2131
2132 // called on the socket thread
2133 NS_IMETHODIMP
2134 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out) {
2135 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2136 if (mConnection) {
2137 mConnection->TransactionHasDataToWrite(this);
2138 nsresult rv = mConnection->ResumeSend();
2139 if (NS_FAILED(rv)) NS_ERROR("ResumeSend failed");
2140 }
2141 return NS_OK;
2142 }
2143
2144 //-----------------------------------------------------------------------------
2145 // nsHttpTransaction::nsIOutputStreamCallback
2146 //-----------------------------------------------------------------------------
2147
2148 // called on the socket thread
2149 NS_IMETHODIMP
OnOutputStreamReady(nsIAsyncOutputStream * out)2150 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) {
2151 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2152 mWaitingOnPipeOut = false;
2153 if (mConnection) {
2154 mConnection->TransactionHasDataToRecv(this);
2155 nsresult rv = mConnection->ResumeRecv();
2156 if (NS_FAILED(rv)) NS_ERROR("ResumeRecv failed");
2157 }
2158 return NS_OK;
2159 }
2160
GetNetworkAddresses(NetAddr & self,NetAddr & peer)2161 void nsHttpTransaction::GetNetworkAddresses(NetAddr &self, NetAddr &peer) {
2162 MutexAutoLock lock(mLock);
2163 self = mSelfAddr;
2164 peer = mPeerAddr;
2165 }
2166
CanDo0RTT()2167 bool nsHttpTransaction::CanDo0RTT() {
2168 if (mRequestHead->IsSafeMethod() && !mDoNotTryEarlyData &&
2169 (!mConnection || !mConnection->IsProxyConnectInProgress())) {
2170 return true;
2171 }
2172 return false;
2173 }
2174
Do0RTT()2175 bool nsHttpTransaction::Do0RTT() {
2176 if (mRequestHead->IsSafeMethod() && !mDoNotTryEarlyData &&
2177 (!mConnection || !mConnection->IsProxyConnectInProgress())) {
2178 m0RTTInProgress = true;
2179 }
2180 return m0RTTInProgress;
2181 }
2182
Finish0RTT(bool aRestart,bool aAlpnChanged)2183 nsresult nsHttpTransaction::Finish0RTT(bool aRestart,
2184 bool aAlpnChanged /* ignored */) {
2185 LOG(("nsHttpTransaction::Finish0RTT %p %d %d\n", this, aRestart,
2186 aAlpnChanged));
2187 MOZ_ASSERT(m0RTTInProgress);
2188 m0RTTInProgress = false;
2189 if (!aRestart && (mEarlyDataDisposition == EARLY_SENT)) {
2190 // note that if this is invoked by a 3 param version of finish0rtt this
2191 // disposition might be reverted
2192 mEarlyDataDisposition = EARLY_ACCEPTED;
2193 }
2194 if (aRestart) {
2195 // Reset request headers to be sent again.
2196 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
2197 if (seekable) {
2198 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
2199 } else {
2200 return NS_ERROR_FAILURE;
2201 }
2202 } else if (!mConnected) {
2203 // this is code that was skipped in ::ReadSegments while in 0RTT
2204 mConnected = true;
2205 mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
2206 }
2207 return NS_OK;
2208 }
2209
RestartOnFastOpenError()2210 nsresult nsHttpTransaction::RestartOnFastOpenError() {
2211 // This will happen on connection error during Fast Open or if connect
2212 // during Fast Open takes too long. So we should not have received any
2213 // data!
2214 MOZ_ASSERT(!mReceivedData);
2215 MOZ_ASSERT(OnSocketThread(), "not on socket thread");
2216
2217 LOG(
2218 ("nsHttpTransaction::RestartOnFastOpenError - restarting transaction "
2219 "%p\n",
2220 this));
2221
2222 // rewind streams in case we already wrote out the request
2223 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
2224 if (seekable) seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
2225 // clear old connection state...
2226 mSecurityInfo = nullptr;
2227
2228 if (!mConnInfo->GetRoutedHost().IsEmpty()) {
2229 MutexAutoLock lock(*nsHttp::GetLock());
2230 RefPtr<nsHttpConnectionInfo> ci;
2231 mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci));
2232 mConnInfo = ci;
2233 }
2234 mEarlyDataDisposition = EARLY_NONE;
2235 m0RTTInProgress = false;
2236 mFastOpenStatus = TFO_FAILED;
2237 mTimings = TimingStruct();
2238 return NS_OK;
2239 }
2240
SetFastOpenStatus(uint8_t aStatus)2241 void nsHttpTransaction::SetFastOpenStatus(uint8_t aStatus) {
2242 LOG(("nsHttpTransaction::SetFastOpenStatus %d [this=%p]\n", aStatus, this));
2243 mFastOpenStatus = aStatus;
2244 }
2245
Refused0RTT()2246 void nsHttpTransaction::Refused0RTT() {
2247 LOG(("nsHttpTransaction::Refused0RTT %p\n", this));
2248 if (mEarlyDataDisposition == EARLY_ACCEPTED) {
2249 mEarlyDataDisposition = EARLY_SENT; // undo accepted state
2250 }
2251 }
2252
SetHttpTrailers(nsCString & aTrailers)2253 void nsHttpTransaction::SetHttpTrailers(nsCString &aTrailers) {
2254 LOG(("nsHttpTransaction::SetHttpTrailers %p", this));
2255 LOG(("[\n %s\n]", aTrailers.BeginReading()));
2256 if (!mForTakeResponseTrailers) {
2257 mForTakeResponseTrailers = new nsHttpHeaderArray();
2258 }
2259
2260 int32_t cur = 0;
2261 int32_t len = aTrailers.Length();
2262 while (cur < len) {
2263 int32_t newline = aTrailers.FindCharInSet("\n", cur);
2264 if (newline == -1) {
2265 newline = len;
2266 }
2267
2268 int32_t end = aTrailers[newline - 1] == '\r' ? newline - 1 : newline;
2269 nsDependentCSubstring line(aTrailers, cur, end);
2270 nsHttpAtom hdr = {nullptr};
2271 nsAutoCString hdrNameOriginal;
2272 nsAutoCString val;
2273 if (NS_SUCCEEDED(mForTakeResponseTrailers->ParseHeaderLine(
2274 line, &hdr, &hdrNameOriginal, &val))) {
2275 if (hdr == nsHttp::Server_Timing) {
2276 Unused << mForTakeResponseTrailers->SetHeaderFromNet(
2277 hdr, hdrNameOriginal, val, true);
2278 }
2279 }
2280
2281 cur = newline + 1;
2282 }
2283
2284 if (mForTakeResponseTrailers->Count() == 0) {
2285 // Didn't find a Server-Timing header, so get rid of this.
2286 mForTakeResponseTrailers = nullptr;
2287 }
2288 }
2289
2290 } // namespace net
2291 } // namespace mozilla
2292