Coverage Report

Created: 2018-09-25 14:53

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