diff options
Diffstat (limited to 'netwerk/protocol/http/nsHttpTransaction.cpp')
-rw-r--r-- | netwerk/protocol/http/nsHttpTransaction.cpp | 2461 |
1 files changed, 2461 insertions, 0 deletions
diff --git a/netwerk/protocol/http/nsHttpTransaction.cpp b/netwerk/protocol/http/nsHttpTransaction.cpp new file mode 100644 index 000000000..ee3a88489 --- /dev/null +++ b/netwerk/protocol/http/nsHttpTransaction.cpp @@ -0,0 +1,2461 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim:set ts=4 sw=4 sts=4 et cin: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// HttpLog.h should generally be included first +#include "HttpLog.h" + +#include "base/basictypes.h" + +#include "nsHttpHandler.h" +#include "nsHttpTransaction.h" +#include "nsHttpRequestHead.h" +#include "nsHttpResponseHead.h" +#include "nsHttpChunkedDecoder.h" +#include "nsTransportUtils.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "nsIChannel.h" +#include "nsIPipe.h" +#include "nsCRT.h" +#include "mozilla/Tokenizer.h" + +#include "nsISeekableStream.h" +#include "nsMultiplexInputStream.h" +#include "nsStringStream.h" + +#include "nsComponentManagerUtils.h" // do_CreateInstance +#include "nsServiceManagerUtils.h" // do_GetService +#include "nsIHttpActivityObserver.h" +#include "nsSocketTransportService2.h" +#include "nsICancelable.h" +#include "nsIEventTarget.h" +#include "nsIHttpChannelInternal.h" +#include "nsIInputStream.h" +#include "nsIThrottledInputChannel.h" +#include "nsITransport.h" +#include "nsIOService.h" +#include "nsIRequestContext.h" +#include "nsIHttpAuthenticator.h" +#include <algorithm> + +#ifdef MOZ_WIDGET_GONK +#include "NetStatistics.h" +#endif + +//----------------------------------------------------------------------------- + +static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID); + +// Place a limit on how much non-compliant HTTP can be skipped while +// looking for a response header +#define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128) + +using namespace mozilla::net; + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// helpers +//----------------------------------------------------------------------------- + +static void +LogHeaders(const char *lineStart) +{ + nsAutoCString buf; + char *endOfLine; + while ((endOfLine = PL_strstr(lineStart, "\r\n"))) { + buf.Assign(lineStart, endOfLine - lineStart); + if (PL_strcasestr(buf.get(), "authorization: ") || + PL_strcasestr(buf.get(), "proxy-authorization: ")) { + char *p = PL_strchr(PL_strchr(buf.get(), ' ') + 1, ' '); + while (p && *++p) + *p = '*'; + } + LOG3((" %s\n", buf.get())); + lineStart = endOfLine + 2; + } +} + +//----------------------------------------------------------------------------- +// nsHttpTransaction <public> +//----------------------------------------------------------------------------- + +nsHttpTransaction::nsHttpTransaction() + : mLock("transaction lock") + , mRequestSize(0) + , mRequestHead(nullptr) + , mResponseHead(nullptr) + , mReader(nullptr) + , mWriter(nullptr) + , mContentLength(-1) + , mContentRead(0) + , mTransferSize(0) + , mInvalidResponseBytesRead(0) + , mPushedStream(nullptr) + , mInitialRwin(0) + , mChunkedDecoder(nullptr) + , mStatus(NS_OK) + , mPriority(0) + , mRestartCount(0) + , mCaps(0) + , mClassification(CLASS_GENERAL) + , mPipelinePosition(0) + , mHttpVersion(NS_HTTP_VERSION_UNKNOWN) + , mHttpResponseCode(0) + , mCurrentHttpResponseHeaderSize(0) + , mCapsToClear(0) + , mResponseIsComplete(false) + , mClosed(false) + , mConnected(false) + , mHaveStatusLine(false) + , mHaveAllHeaders(false) + , mTransactionDone(false) + , mDidContentStart(false) + , mNoContent(false) + , mSentData(false) + , mReceivedData(false) + , mStatusEventPending(false) + , mHasRequestBody(false) + , mProxyConnectFailed(false) + , mHttpResponseMatched(false) + , mPreserveStream(false) + , mDispatchedAsBlocking(false) + , mResponseTimeoutEnabled(true) + , mForceRestart(false) + , mReuseOnRestart(false) + , mContentDecoding(false) + , mContentDecodingCheck(false) + , mDeferredSendProgress(false) + , mWaitingOnPipeOut(false) + , mReportedStart(false) + , mReportedResponseHeader(false) + , mForTakeResponseHead(nullptr) + , mResponseHeadTaken(false) + , mSubmittedRatePacing(false) + , mPassedRatePacing(false) + , mSynchronousRatePaceRequest(false) + , mCountRecv(0) + , mCountSent(0) + , mAppId(NECKO_NO_APP_ID) + , mIsInIsolatedMozBrowser(false) + , mClassOfService(0) + , m0RTTInProgress(false) +{ + LOG(("Creating nsHttpTransaction @%p\n", this)); + gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize); + +#ifdef MOZ_VALGRIND + memset(&mSelfAddr, 0, sizeof(NetAddr)); + memset(&mPeerAddr, 0, sizeof(NetAddr)); +#endif + mSelfAddr.raw.family = PR_AF_UNSPEC; + mPeerAddr.raw.family = PR_AF_UNSPEC; +} + +nsHttpTransaction::~nsHttpTransaction() +{ + LOG(("Destroying nsHttpTransaction @%p\n", this)); + if (mTransactionObserver) { + mTransactionObserver->Complete(this, NS_OK); + } + if (mPushedStream) { + mPushedStream->OnPushFailed(); + mPushedStream = nullptr; + } + + if (mTokenBucketCancel) { + mTokenBucketCancel->Cancel(NS_ERROR_ABORT); + mTokenBucketCancel = nullptr; + } + + // Force the callbacks and connection to be released right now + mCallbacks = nullptr; + mConnection = nullptr; + + delete mResponseHead; + delete mForTakeResponseHead; + delete mChunkedDecoder; + ReleaseBlockingTransaction(); +} + +nsHttpTransaction::Classifier +nsHttpTransaction::Classify() +{ + if (!(mCaps & NS_HTTP_ALLOW_PIPELINING)) + return (mClassification = CLASS_SOLO); + + if (mRequestHead->HasHeader(nsHttp::If_Modified_Since) || + mRequestHead->HasHeader(nsHttp::If_None_Match)) + return (mClassification = CLASS_REVALIDATION); + + nsAutoCString accept; + bool hasAccept = NS_SUCCEEDED(mRequestHead->GetHeader(nsHttp::Accept, accept)); + if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("image/"))) { + return (mClassification = CLASS_IMAGE); + } + + if (hasAccept && StringBeginsWith(accept, NS_LITERAL_CSTRING("text/css"))) { + return (mClassification = CLASS_SCRIPT); + } + + mClassification = CLASS_GENERAL; + + nsAutoCString requestURI; + mRequestHead->RequestURI(requestURI); + int32_t queryPos = requestURI.FindChar('?'); + if (queryPos == kNotFound) { + if (StringEndsWith(requestURI, + NS_LITERAL_CSTRING(".js"))) + mClassification = CLASS_SCRIPT; + } + else if (queryPos >= 3 && + Substring(requestURI, queryPos - 3, 3). + EqualsLiteral(".js")) { + mClassification = CLASS_SCRIPT; + } + + return mClassification; +} + +nsresult +nsHttpTransaction::Init(uint32_t caps, + nsHttpConnectionInfo *cinfo, + nsHttpRequestHead *requestHead, + nsIInputStream *requestBody, + bool requestBodyHasHeaders, + nsIEventTarget *target, + nsIInterfaceRequestor *callbacks, + nsITransportEventSink *eventsink, + nsIAsyncInputStream **responseBody) +{ + nsresult rv; + + LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps)); + + MOZ_ASSERT(cinfo); + MOZ_ASSERT(requestHead); + MOZ_ASSERT(target); + MOZ_ASSERT(NS_IsMainThread()); + + mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv); + if (NS_FAILED(rv)) return rv; + + bool activityDistributorActive; + rv = mActivityDistributor->GetIsActive(&activityDistributorActive); + if (NS_SUCCEEDED(rv) && activityDistributorActive) { + // there are some observers registered at activity distributor, gather + // nsISupports for the channel that called Init() + LOG(("nsHttpTransaction::Init() " \ + "mActivityDistributor is active " \ + "this=%p", this)); + } else { + // there is no observer, so don't use it + activityDistributorActive = false; + mActivityDistributor = nullptr; + } + mChannel = do_QueryInterface(eventsink); + nsCOMPtr<nsIChannel> channel = do_QueryInterface(eventsink); + if (channel) { + NS_GetAppInfo(channel, &mAppId, &mIsInIsolatedMozBrowser); + } + +#ifdef MOZ_WIDGET_GONK + if (mAppId != NECKO_NO_APP_ID) { + nsCOMPtr<nsINetworkInfo> activeNetworkInfo; + GetActiveNetworkInfo(activeNetworkInfo); + mActiveNetworkInfo = + new nsMainThreadPtrHolder<nsINetworkInfo>(activeNetworkInfo); + } +#endif + + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal = + do_QueryInterface(eventsink); + if (httpChannelInternal) { + rv = httpChannelInternal->GetResponseTimeoutEnabled( + &mResponseTimeoutEnabled); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + httpChannelInternal->GetInitialRwin(&mInitialRwin); + } + + // create transport event sink proxy. it coalesces consecutive + // events of the same status type. + rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), + eventsink, target); + + if (NS_FAILED(rv)) return rv; + + mConnInfo = cinfo; + mCallbacks = callbacks; + mConsumerTarget = target; + mCaps = caps; + + if (requestHead->IsHead()) { + mNoContent = true; + } + + // Make sure that there is "Content-Length: 0" header in the requestHead + // in case of POST and PUT methods when there is no requestBody and + // requestHead doesn't contain "Transfer-Encoding" header. + // + // RFC1945 section 7.2.2: + // HTTP/1.0 requests containing an entity body must include a valid + // Content-Length header field. + // + // RFC2616 section 4.4: + // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests + // containing a message-body MUST include a valid Content-Length header + // field unless the server is known to be HTTP/1.1 compliant. + if ((requestHead->IsPost() || requestHead->IsPut()) && + !requestBody && !requestHead->HasHeader(nsHttp::Transfer_Encoding)) { + requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0")); + } + + // grab a weak reference to the request head + mRequestHead = requestHead; + + // make sure we eliminate any proxy specific headers from + // the request if we are using CONNECT + bool pruneProxyHeaders = cinfo->UsingConnect(); + + mReqHeaderBuf.Truncate(); + requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders); + + if (LOG3_ENABLED()) { + LOG3(("http request [\n")); + LogHeaders(mReqHeaderBuf.get()); + LOG3(("]\n")); + } + + // If the request body does not include headers or if there is no request + // body, then we must add the header/body separator manually. + if (!requestBodyHasHeaders || !requestBody) + mReqHeaderBuf.AppendLiteral("\r\n"); + + // report the request header + if (mActivityDistributor) + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, + PR_Now(), 0, + mReqHeaderBuf); + + // Create a string stream for the request header buf (the stream holds + // a non-owning reference to the request header data, so we MUST keep + // mReqHeaderBuf around). + nsCOMPtr<nsIInputStream> headers; + rv = NS_NewByteInputStream(getter_AddRefs(headers), + mReqHeaderBuf.get(), + mReqHeaderBuf.Length()); + if (NS_FAILED(rv)) return rv; + + mHasRequestBody = !!requestBody; + if (mHasRequestBody) { + // some non standard methods set a 0 byte content-length for + // clarity, we can avoid doing the mulitplexed request stream for them + uint64_t size; + if (NS_SUCCEEDED(requestBody->Available(&size)) && !size) { + mHasRequestBody = false; + } + } + + if (mHasRequestBody) { + // wrap the headers and request body in a multiplexed input stream. + nsCOMPtr<nsIMultiplexInputStream> multi = + do_CreateInstance(kMultiplexInputStream, &rv); + if (NS_FAILED(rv)) return rv; + + rv = multi->AppendStream(headers); + if (NS_FAILED(rv)) return rv; + + rv = multi->AppendStream(requestBody); + if (NS_FAILED(rv)) return rv; + + // wrap the multiplexed input stream with a buffered input stream, so + // that we write data in the largest chunks possible. this is actually + // necessary to workaround some common server bugs (see bug 137155). + rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi, + nsIOService::gDefaultSegmentSize); + if (NS_FAILED(rv)) return rv; + } + else + mRequestStream = headers; + + nsCOMPtr<nsIThrottledInputChannel> throttled = do_QueryInterface(mChannel); + nsIInputChannelThrottleQueue* queue; + if (throttled) { + rv = throttled->GetThrottleQueue(&queue); + // In case of failure, just carry on without throttling. + if (NS_SUCCEEDED(rv) && queue) { + nsCOMPtr<nsIAsyncInputStream> wrappedStream; + rv = queue->WrapStream(mRequestStream, getter_AddRefs(wrappedStream)); + // Failure to throttle isn't sufficient reason to fail + // initialization + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(wrappedStream != nullptr); + LOG(("nsHttpTransaction::Init %p wrapping input stream using throttle queue %p\n", + this, queue)); + mRequestStream = do_QueryInterface(wrappedStream); + } + } + } + + uint64_t size_u64; + rv = mRequestStream->Available(&size_u64); + if (NS_FAILED(rv)) { + return rv; + } + + // make sure it fits within js MAX_SAFE_INTEGER + mRequestSize = InScriptableRange(size_u64) ? static_cast<int64_t>(size_u64) : -1; + + // create pipe for response stream + rv = NS_NewPipe2(getter_AddRefs(mPipeIn), + getter_AddRefs(mPipeOut), + true, true, + nsIOService::gDefaultSegmentSize, + nsIOService::gDefaultSegmentCount); + if (NS_FAILED(rv)) return rv; + +#ifdef WIN32 // bug 1153929 + MOZ_DIAGNOSTIC_ASSERT(mPipeOut); + uint32_t * vtable = (uint32_t *) mPipeOut.get(); + MOZ_DIAGNOSTIC_ASSERT(*vtable != 0); +#endif // WIN32 + + Classify(); + + nsCOMPtr<nsIAsyncInputStream> tmp(mPipeIn); + tmp.forget(responseBody); + return NS_OK; +} + +// This method should only be used on the socket thread +nsAHttpConnection * +nsHttpTransaction::Connection() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + return mConnection.get(); +} + +already_AddRefed<nsAHttpConnection> +nsHttpTransaction::GetConnectionReference() +{ + MutexAutoLock lock(mLock); + RefPtr<nsAHttpConnection> connection(mConnection); + return connection.forget(); +} + +nsHttpResponseHead * +nsHttpTransaction::TakeResponseHead() +{ + MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x"); + + // Lock RestartInProgress() and TakeResponseHead() against main thread + MutexAutoLock lock(*nsHttp::GetLock()); + + mResponseHeadTaken = true; + + // Prefer mForTakeResponseHead over mResponseHead. It is always a complete + // set of headers. + nsHttpResponseHead *head; + if (mForTakeResponseHead) { + head = mForTakeResponseHead; + mForTakeResponseHead = nullptr; + return head; + } + + // Even in OnStartRequest() the headers won't be available if we were + // canceled + if (!mHaveAllHeaders) { + NS_WARNING("response headers not available or incomplete"); + return nullptr; + } + + head = mResponseHead; + mResponseHead = nullptr; + return head; +} + +void +nsHttpTransaction::SetProxyConnectFailed() +{ + mProxyConnectFailed = true; +} + +nsHttpRequestHead * +nsHttpTransaction::RequestHead() +{ + return mRequestHead; +} + +uint32_t +nsHttpTransaction::Http1xTransactionCount() +{ + return 1; +} + +nsresult +nsHttpTransaction::TakeSubTransactions( + nsTArray<RefPtr<nsAHttpTransaction> > &outTransactions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +//---------------------------------------------------------------------------- +// nsHttpTransaction::nsAHttpTransaction +//---------------------------------------------------------------------------- + +void +nsHttpTransaction::SetConnection(nsAHttpConnection *conn) +{ + { + MutexAutoLock lock(mLock); + mConnection = conn; + } +} + +void +nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb) +{ + MutexAutoLock lock(mLock); + nsCOMPtr<nsIInterfaceRequestor> tmp(mCallbacks); + tmp.forget(cb); +} + +void +nsHttpTransaction::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) +{ + { + MutexAutoLock lock(mLock); + mCallbacks = aCallbacks; + } + + if (gSocketTransportService) { + RefPtr<UpdateSecurityCallbacks> event = new UpdateSecurityCallbacks(this, aCallbacks); + gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); + } +} + +void +nsHttpTransaction::OnTransportStatus(nsITransport* transport, + nsresult status, int64_t progress) +{ + LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%x progress=%lld]\n", + this, status, progress)); + + if (status == NS_NET_STATUS_CONNECTED_TO || + status == NS_NET_STATUS_WAITING_FOR) { + nsISocketTransport *socketTransport = + mConnection ? mConnection->Transport() : nullptr; + if (socketTransport) { + MutexAutoLock lock(mLock); + socketTransport->GetSelfAddr(&mSelfAddr); + socketTransport->GetPeerAddr(&mPeerAddr); + } + } + + // If the timing is enabled, and we are not using a persistent connection + // then the requestStart timestamp will be null, so we mark the timestamps + // for domainLookupStart/End and connectStart/End + // If we are using a persistent connection they will remain null, + // and the correct value will be returned in Performance. + if (TimingEnabled() && GetRequestStart().IsNull()) { + if (status == NS_NET_STATUS_RESOLVING_HOST) { + SetDomainLookupStart(TimeStamp::Now(), true); + } else if (status == NS_NET_STATUS_RESOLVED_HOST) { + SetDomainLookupEnd(TimeStamp::Now()); + } else if (status == NS_NET_STATUS_CONNECTING_TO) { + SetConnectStart(TimeStamp::Now()); + } else if (status == NS_NET_STATUS_CONNECTED_TO) { + SetConnectEnd(TimeStamp::Now()); + } + } + + if (!mTransportSink) + return; + + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + // Need to do this before the STATUS_RECEIVING_FROM check below, to make + // sure that the activity distributor gets told about all status events. + if (mActivityDistributor) { + // upon STATUS_WAITING_FOR; report request body sent + if ((mHasRequestBody) && + (status == NS_NET_STATUS_WAITING_FOR)) + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, + PR_Now(), 0, EmptyCString()); + + // report the status and progress + if (!mRestartInProgressVerifier.IsDiscardingContent()) + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, + static_cast<uint32_t>(status), + PR_Now(), + progress, + EmptyCString()); + } + + // nsHttpChannel synthesizes progress events in OnDataAvailable + if (status == NS_NET_STATUS_RECEIVING_FROM) + return; + + int64_t progressMax; + + if (status == NS_NET_STATUS_SENDING_TO) { + // suppress progress when only writing request headers + if (!mHasRequestBody) { + LOG(("nsHttpTransaction::OnTransportStatus %p " + "SENDING_TO without request body\n", this)); + return; + } + + if (mReader) { + // A mRequestStream method is on the stack - wait. + LOG(("nsHttpTransaction::OnSocketStatus [this=%p] " + "Skipping Re-Entrant NS_NET_STATUS_SENDING_TO\n", this)); + // its ok to coalesce several of these into one deferred event + mDeferredSendProgress = true; + return; + } + + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream); + if (!seekable) { + LOG(("nsHttpTransaction::OnTransportStatus %p " + "SENDING_TO without seekable request stream\n", this)); + progress = 0; + } else { + int64_t prog = 0; + seekable->Tell(&prog); + progress = prog; + } + + // when uploading, we include the request headers in the progress + // notifications. + progressMax = mRequestSize; + } + else { + progress = 0; + progressMax = 0; + } + + mTransportSink->OnTransportStatus(transport, status, progress, progressMax); +} + +bool +nsHttpTransaction::IsDone() +{ + return mTransactionDone; +} + +nsresult +nsHttpTransaction::Status() +{ + return mStatus; +} + +uint32_t +nsHttpTransaction::Caps() +{ + return mCaps & ~mCapsToClear; +} + +void +nsHttpTransaction::SetDNSWasRefreshed() +{ + MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!"); + mCapsToClear |= NS_HTTP_REFRESH_DNS; +} + +uint64_t +nsHttpTransaction::Available() +{ + uint64_t size; + if (NS_FAILED(mRequestStream->Available(&size))) + size = 0; + return size; +} + +nsresult +nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream, + void *closure, + const char *buf, + uint32_t offset, + uint32_t count, + uint32_t *countRead) +{ + nsHttpTransaction *trans = (nsHttpTransaction *) closure; + nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead); + if (NS_FAILED(rv)) return rv; + + if (trans->TimingEnabled()) { + // Set the timestamp to Now(), only if it null + trans->SetRequestStart(TimeStamp::Now(), true); + } + + trans->CountSentBytes(*countRead); + trans->mSentData = true; + return NS_OK; +} + +nsresult +nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader, + uint32_t count, uint32_t *countRead) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (mTransactionDone) { + *countRead = 0; + return mStatus; + } + + if (!mConnected && !m0RTTInProgress) { + mConnected = true; + mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); + } + + mDeferredSendProgress = false; + mReader = reader; + nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead); + mReader = nullptr; + + if (mDeferredSendProgress && mConnection && mConnection->Transport()) { + // to avoid using mRequestStream concurrently, OnTransportStatus() + // did not report upload status off the ReadSegments() stack from nsSocketTransport + // do it now. + OnTransportStatus(mConnection->Transport(), NS_NET_STATUS_SENDING_TO, 0); + } + mDeferredSendProgress = false; + + if (mForceRestart) { + // The forceRestart condition was dealt with on the stack, but it did not + // clear the flag because nsPipe in the readsegment stack clears out + // return codes, so we need to use the flag here as a cue to return ERETARGETED + if (NS_SUCCEEDED(rv)) { + rv = NS_BINDING_RETARGETED; + } + mForceRestart = false; + } + + // if read would block then we need to AsyncWait on the request stream. + // have callback occur on socket thread so we stay synchronized. + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + nsCOMPtr<nsIAsyncInputStream> asyncIn = + do_QueryInterface(mRequestStream); + if (asyncIn) { + nsCOMPtr<nsIEventTarget> target; + gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); + if (target) + asyncIn->AsyncWait(this, 0, 0, target); + else { + NS_ERROR("no socket thread event target"); + rv = NS_ERROR_UNEXPECTED; + } + } + } + + return rv; +} + +nsresult +nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream, + void *closure, + char *buf, + uint32_t offset, + uint32_t count, + uint32_t *countWritten) +{ + nsHttpTransaction *trans = (nsHttpTransaction *) closure; + + if (trans->mTransactionDone) + return NS_BASE_STREAM_CLOSED; // stop iterating + + if (trans->TimingEnabled()) { + // Set the timestamp to Now(), only if it null + trans->SetResponseStart(TimeStamp::Now(), true); + } + + // Bug 1153929 - add checks to fix windows crash + MOZ_ASSERT(trans->mWriter); + if (!trans->mWriter) { + return NS_ERROR_UNEXPECTED; + } + + nsresult rv; + // + // OK, now let the caller fill this segment with data. + // + rv = trans->mWriter->OnWriteSegment(buf, count, countWritten); + if (NS_FAILED(rv)) return rv; // caller didn't want to write anything + + MOZ_ASSERT(*countWritten > 0, "bad writer"); + trans->CountRecvBytes(*countWritten); + trans->mReceivedData = true; + trans->mTransferSize += *countWritten; + + // Let the transaction "play" with the buffer. It is free to modify + // the contents of the buffer and/or modify countWritten. + // - Bytes in HTTP headers don't count towards countWritten, so the input + // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit + // OnInputStreamReady until all headers have been parsed. + // + rv = trans->ProcessData(buf, *countWritten, countWritten); + if (NS_FAILED(rv)) + trans->Close(rv); + + return rv; // failure code only stops WriteSegments; it is not propagated. +} + +nsresult +nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer, + uint32_t count, uint32_t *countWritten) +{ + static bool reentrantFlag = false; + LOG(("nsHttpTransaction::WriteSegments %p reentrantFlag=%d", + this, reentrantFlag)); + MOZ_DIAGNOSTIC_ASSERT(!reentrantFlag); + reentrantFlag = true; + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (mTransactionDone) { + reentrantFlag = false; + return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus; + } + + mWriter = writer; + +#ifdef WIN32 // bug 1153929 + MOZ_DIAGNOSTIC_ASSERT(mPipeOut); + uint32_t * vtable = (uint32_t *) mPipeOut.get(); + MOZ_DIAGNOSTIC_ASSERT(*vtable != 0); +#endif // WIN32 + + if (!mPipeOut) { + reentrantFlag = false; + return NS_ERROR_UNEXPECTED; + } + + nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten); + + mWriter = nullptr; + + if (mForceRestart) { + // The forceRestart condition was dealt with on the stack, but it did not + // clear the flag because nsPipe in the writesegment stack clears out + // return codes, so we need to use the flag here as a cue to return ERETARGETED + if (NS_SUCCEEDED(rv)) { + rv = NS_BINDING_RETARGETED; + } + mForceRestart = false; + } + + // if pipe would block then we need to AsyncWait on it. have callback + // occur on socket thread so we stay synchronized. + if (rv == NS_BASE_STREAM_WOULD_BLOCK) { + nsCOMPtr<nsIEventTarget> target; + gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); + if (target) { + mPipeOut->AsyncWait(this, 0, 0, target); + mWaitingOnPipeOut = true; + } else { + NS_ERROR("no socket thread event target"); + rv = NS_ERROR_UNEXPECTED; + } + } + + reentrantFlag = false; + return rv; +} + +nsresult +nsHttpTransaction::SaveNetworkStats(bool enforce) +{ +#ifdef MOZ_WIDGET_GONK + // Check if active network and appid are valid. + if (!mActiveNetworkInfo || mAppId == NECKO_NO_APP_ID) { + return NS_OK; + } + + if (mCountRecv <= 0 && mCountSent <= 0) { + // There is no traffic, no need to save. + return NS_OK; + } + + // If |enforce| is false, the traffic amount is saved + // only when the total amount exceeds the predefined + // threshold. + uint64_t totalBytes = mCountRecv + mCountSent; + if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { + return NS_OK; + } + + // Create the event to save the network statistics. + // the event is then dispatched to the main thread. + RefPtr<Runnable> event = + new SaveNetworkStatsEvent(mAppId, mIsInIsolatedMozBrowser, mActiveNetworkInfo, + mCountRecv, mCountSent, false); + NS_DispatchToMainThread(event); + + // Reset the counters after saving. + mCountSent = 0; + mCountRecv = 0; + + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +void +nsHttpTransaction::Close(nsresult reason) +{ + LOG(("nsHttpTransaction::Close [this=%p reason=%x]\n", this, reason)); + + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (reason == NS_BINDING_RETARGETED) { + LOG((" close %p skipped due to ERETARGETED\n", this)); + return; + } + + if (mClosed) { + LOG((" already closed\n")); + return; + } + + if (mTransactionObserver) { + mTransactionObserver->Complete(this, reason); + mTransactionObserver = nullptr; + } + + if (mTokenBucketCancel) { + mTokenBucketCancel->Cancel(reason); + mTokenBucketCancel = nullptr; + } + + if (mActivityDistributor) { + // report the reponse is complete if not already reported + if (!mResponseIsComplete) + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, + PR_Now(), + static_cast<uint64_t>(mContentRead), + EmptyCString()); + + // report that this transaction is closing + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, + PR_Now(), 0, EmptyCString()); + } + + // we must no longer reference the connection! find out if the + // connection was being reused before letting it go. + bool connReused = false; + if (mConnection) { + connReused = mConnection->IsReused(); + } + mConnected = false; + mTunnelProvider = nullptr; + + // + // if the connection was reset or closed before we wrote any part of the + // request or if we wrote the request but didn't receive any part of the + // response and the connection was being reused, then we can (and really + // should) assume that we wrote to a stale connection and we must therefore + // repeat the request over a new connection. + // + // We have decided to retry not only in case of the reused connections, but + // all safe methods(bug 1236277). + // + // NOTE: the conditions under which we will automatically retry the HTTP + // request have to be carefully selected to avoid duplication of the + // request from the point-of-view of the server. such duplication could + // have dire consequences including repeated purchases, etc. + // + // NOTE: because of the way SSL proxy CONNECT is implemented, it is + // possible that the transaction may have received data without having + // sent any data. for this reason, mSendData == FALSE does not imply + // mReceivedData == FALSE. (see bug 203057 for more info.) + // + // Never restart transactions that are marked as sticky to their conenction. + // We use that capability to identify transactions bound to connection based + // authentication. Reissuing them on a different connections will break + // this bondage. Major issue may arise when there is an NTLM message auth + // header on the transaction and we send it to a different NTLM authenticated + // connection. It will break that connection and also confuse the channel's + // auth provider, beliving the cached credentials are wrong and asking for + // the password mistakenly again from the user. + if ((reason == NS_ERROR_NET_RESET || reason == NS_OK) && + (!(mCaps & NS_HTTP_STICKY_CONNECTION) || (mCaps & NS_HTTP_CONNECTION_RESTARTABLE))) { + + if (mForceRestart && NS_SUCCEEDED(Restart())) { + if (mResponseHead) { + mResponseHead->Reset(); + } + mContentRead = 0; + mContentLength = -1; + delete mChunkedDecoder; + mChunkedDecoder = nullptr; + mHaveStatusLine = false; + mHaveAllHeaders = false; + mHttpResponseMatched = false; + mResponseIsComplete = false; + mDidContentStart = false; + mNoContent = false; + mSentData = false; + mReceivedData = false; + LOG(("transaction force restarted\n")); + return; + } + + // reallySentData is meant to separate the instances where data has + // been sent by this transaction but buffered at a higher level while + // a TLS session (perhaps via a tunnel) is setup. + bool reallySentData = + mSentData && (!mConnection || mConnection->BytesWritten()); + + if (!mReceivedData && + ((mRequestHead && mRequestHead->IsSafeMethod()) || + !reallySentData || connReused)) { + // if restarting fails, then we must proceed to close the pipe, + // which will notify the channel that the transaction failed. + + if (mPipelinePosition) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline, + nullptr, 0); + } + if (NS_SUCCEEDED(Restart())) + return; + } + else if (!mResponseIsComplete && mPipelinePosition && + reason == NS_ERROR_NET_RESET) { + // due to unhandled rst on a pipeline - safe to + // restart as only idempotent is found there + + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nullptr, 0); + if (NS_SUCCEEDED(RestartInProgress())) + return; + } + } + + if ((mChunkedDecoder || (mContentLength >= int64_t(0))) && + (NS_SUCCEEDED(reason) && !mResponseIsComplete)) { + + NS_WARNING("Partial transfer, incomplete HTTP response received"); + + if ((mHttpResponseCode / 100 == 2) && + (mHttpVersion >= NS_HTTP_VERSION_1_1)) { + FrameCheckLevel clevel = gHttpHandler->GetEnforceH1Framing(); + if (clevel >= FRAMECHECK_BARELY) { + if ((clevel == FRAMECHECK_STRICT) || + (mChunkedDecoder && mChunkedDecoder->GetChunkRemaining()) || + (!mChunkedDecoder && !mContentDecoding && mContentDecodingCheck) ) { + reason = NS_ERROR_NET_PARTIAL_TRANSFER; + LOG(("Partial transfer, incomplete HTTP response received: %s", + mChunkedDecoder ? "broken chunk" : "c-l underrun")); + } + } + } + + if (mConnection) { + // whether or not we generate an error for the transaction + // bad framing means we don't want a pconn + mConnection->DontReuse(); + } + } + + bool relConn = true; + if (NS_SUCCEEDED(reason)) { + if (!mResponseIsComplete) { + // The response has not been delimited with a high-confidence + // algorithm like Content-Length or Chunked Encoding. We + // need to use a strong framing mechanism to pipeline. + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, + nullptr, mClassification); + } + else if (mPipelinePosition) { + // report this success as feedback + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::GoodCompletedOK, + nullptr, mPipelinePosition); + } + + // the server has not sent the final \r\n terminating the header + // section, and there may still be a header line unparsed. let's make + // sure we parse the remaining header line, and then hopefully, the + // response will be usable (see bug 88792). + if (!mHaveAllHeaders) { + char data = '\n'; + uint32_t unused; + ParseHead(&data, 1, &unused); + + if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) { + // Reject 0 byte HTTP/0.9 Responses - bug 423506 + LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this)); + reason = NS_ERROR_NET_RESET; + } + } + + // honor the sticky connection flag... + if (mCaps & NS_HTTP_STICKY_CONNECTION) + relConn = false; + } + + // mTimings.responseEnd is normally recorded based on the end of a + // HTTP delimiter such as chunked-encodings or content-length. However, + // EOF or an error still require an end time be recorded. + if (TimingEnabled()) { + const TimingStruct timings = Timings(); + if (timings.responseEnd.IsNull() && !timings.responseStart.IsNull()) { + SetResponseEnd(TimeStamp::Now()); + } + } + + if (relConn && mConnection) { + MutexAutoLock lock(mLock); + mConnection = nullptr; + } + + // save network statistics in the end of transaction + SaveNetworkStats(true); + + mStatus = reason; + mTransactionDone = true; // forcibly flag the transaction as complete + mClosed = true; + ReleaseBlockingTransaction(); + + // release some resources that we no longer need + mRequestStream = nullptr; + mReqHeaderBuf.Truncate(); + mLineBuf.Truncate(); + if (mChunkedDecoder) { + delete mChunkedDecoder; + mChunkedDecoder = nullptr; + } + + // closing this pipe triggers the channel's OnStopRequest method. + mPipeOut->CloseWithStatus(reason); + +#ifdef WIN32 // bug 1153929 + MOZ_DIAGNOSTIC_ASSERT(mPipeOut); + uint32_t * vtable = (uint32_t *) mPipeOut.get(); + MOZ_DIAGNOSTIC_ASSERT(*vtable != 0); + mPipeOut = nullptr; // just in case +#endif // WIN32 +} + +nsHttpConnectionInfo * +nsHttpTransaction::ConnectionInfo() +{ + return mConnInfo.get(); +} + +nsresult +nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +uint32_t +nsHttpTransaction::PipelineDepth() +{ + return IsDone() ? 0 : 1; +} + +nsresult +nsHttpTransaction::SetPipelinePosition(int32_t position) +{ + mPipelinePosition = position; + return NS_OK; +} + +int32_t +nsHttpTransaction::PipelinePosition() +{ + return mPipelinePosition; +} + +bool // NOTE BASE CLASS +nsAHttpTransaction::ResponseTimeoutEnabled() const +{ + return false; +} + +PRIntervalTime // NOTE BASE CLASS +nsAHttpTransaction::ResponseTimeout() +{ + return gHttpHandler->ResponseTimeout(); +} + +bool +nsHttpTransaction::ResponseTimeoutEnabled() const +{ + return mResponseTimeoutEnabled; +} + +//----------------------------------------------------------------------------- +// nsHttpTransaction <private> +//----------------------------------------------------------------------------- + +nsresult +nsHttpTransaction::RestartInProgress() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if ((mRestartCount + 1) >= gHttpHandler->MaxRequestAttempts()) { + LOG(("nsHttpTransaction::RestartInProgress() " + "reached max request attempts, failing transaction %p\n", this)); + return NS_ERROR_NET_RESET; + } + + // Lock RestartInProgress() and TakeResponseHead() against main thread + MutexAutoLock lock(*nsHttp::GetLock()); + + // Don't try and RestartInProgress() things that haven't gotten a response + // header yet. Those should be handled under the normal restart() path if + // they are eligible. + if (!mHaveAllHeaders) + return NS_ERROR_NET_RESET; + + if (mCaps & NS_HTTP_STICKY_CONNECTION) { + return NS_ERROR_NET_RESET; + } + + // don't try and restart 0.9 or non 200/Get HTTP/1 + if (!mRestartInProgressVerifier.IsSetup()) + return NS_ERROR_NET_RESET; + + LOG(("Will restart transaction %p and skip first %lld bytes, " + "old Content-Length %lld", + this, mContentRead, mContentLength)); + + mRestartInProgressVerifier.SetAlreadyProcessed( + std::max(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead)); + + if (!mResponseHeadTaken && !mForTakeResponseHead) { + // TakeResponseHeader() has not been called yet and this + // is the first restart. Store the resp headers exclusively + // for TakeResponseHead() which is called from the main thread and + // could happen at any time - so we can't continue to modify those + // headers (which restarting will effectively do) + mForTakeResponseHead = mResponseHead; + mResponseHead = nullptr; + } + + if (mResponseHead) { + mResponseHead->Reset(); + } + + mContentRead = 0; + mContentLength = -1; + delete mChunkedDecoder; + mChunkedDecoder = nullptr; + mHaveStatusLine = false; + mHaveAllHeaders = false; + mHttpResponseMatched = false; + mResponseIsComplete = false; + mDidContentStart = false; + mNoContent = false; + mSentData = false; + mReceivedData = false; + + return Restart(); +} + +nsresult +nsHttpTransaction::Restart() +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + // limit the number of restart attempts - bug 92224 + if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) { + LOG(("reached max request attempts, failing transaction @%p\n", this)); + return NS_ERROR_NET_RESET; + } + + LOG(("restarting transaction @%p\n", this)); + mTunnelProvider = nullptr; + + // rewind streams in case we already wrote out the request + nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream); + if (seekable) + seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + + // clear old connection state... + mSecurityInfo = nullptr; + if (mConnection) { + if (!mReuseOnRestart) { + mConnection->DontReuse(); + } + MutexAutoLock lock(mLock); + mConnection = nullptr; + } + + // Reset this to our default state, since this may change from one restart + // to the next + mReuseOnRestart = false; + + // disable pipelining for the next attempt in case pipelining caused the + // reset. this is being overly cautious since we don't know if pipelining + // was the problem here. + mCaps &= ~NS_HTTP_ALLOW_PIPELINING; + SetPipelinePosition(0); + + if (!mConnInfo->GetRoutedHost().IsEmpty()) { + MutexAutoLock lock(*nsHttp::GetLock()); + RefPtr<nsHttpConnectionInfo> ci; + mConnInfo->CloneAsDirectRoute(getter_AddRefs(ci)); + mConnInfo = ci; + if (mRequestHead) { + mRequestHead->SetHeader(nsHttp::Alternate_Service_Used, NS_LITERAL_CSTRING("0")); + } + } + + return gHttpHandler->InitiateTransaction(this, mPriority); +} + +char * +nsHttpTransaction::LocateHttpStart(char *buf, uint32_t len, + bool aAllowPartialMatch) +{ + MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty()); + + static const char HTTPHeader[] = "HTTP/1."; + static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1; + static const char HTTP2Header[] = "HTTP/2.0"; + static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1; + // ShoutCast ICY is treated as HTTP/1.0 + static const char ICYHeader[] = "ICY "; + static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1; + + if (aAllowPartialMatch && (len < HTTPHeaderLen)) + return (PL_strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr; + + // mLineBuf can contain partial match from previous search + if (!mLineBuf.IsEmpty()) { + MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen); + int32_t checkChars = std::min(len, HTTPHeaderLen - mLineBuf.Length()); + if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(), + checkChars) == 0) { + mLineBuf.Append(buf, checkChars); + if (mLineBuf.Length() == HTTPHeaderLen) { + // We've found whole HTTPHeader sequence. Return pointer at the + // end of matched sequence since it is stored in mLineBuf. + return (buf + checkChars); + } + // Response matches pattern but is still incomplete. + return 0; + } + // Previous partial match together with new data doesn't match the + // pattern. Start the search again. + mLineBuf.Truncate(); + } + + bool firstByte = true; + while (len > 0) { + if (PL_strncasecmp(buf, HTTPHeader, std::min<uint32_t>(len, HTTPHeaderLen)) == 0) { + if (len < HTTPHeaderLen) { + // partial HTTPHeader sequence found + // save partial match to mLineBuf + mLineBuf.Assign(buf, len); + return 0; + } + + // whole HTTPHeader sequence found + return buf; + } + + // At least "SmarterTools/2.0.3974.16813" generates nonsensical + // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of + // it as HTTP/1.1 to be compatible with old versions of ourselves and + // other browsers + + if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen && + (PL_strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) { + LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n")); + return buf; + } + + // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion + // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted + // as HTTP/1.0 in nsHttpResponseHead::ParseVersion + + if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen && + (PL_strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) { + LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n")); + return buf; + } + + if (!nsCRT::IsAsciiSpace(*buf)) + firstByte = false; + buf++; + len--; + } + return 0; +} + +nsresult +nsHttpTransaction::ParseLine(nsACString &line) +{ + LOG(("nsHttpTransaction::ParseLine [%s]\n", PromiseFlatCString(line).get())); + nsresult rv = NS_OK; + + if (!mHaveStatusLine) { + mResponseHead->ParseStatusLine(line); + mHaveStatusLine = true; + // XXX this should probably never happen + if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) + mHaveAllHeaders = true; + } + else { + rv = mResponseHead->ParseHeaderLine(line); + } + return rv; +} + +nsresult +nsHttpTransaction::ParseLineSegment(char *segment, uint32_t len) +{ + NS_PRECONDITION(!mHaveAllHeaders, "already have all headers"); + + if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') { + // trim off the new line char, and if this segment is + // not a continuation of the previous or if we haven't + // parsed the status line yet, then parse the contents + // of mLineBuf. + mLineBuf.Truncate(mLineBuf.Length() - 1); + if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) { + nsresult rv = ParseLine(mLineBuf); + mLineBuf.Truncate(); + if (NS_FAILED(rv)) { + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, + nullptr, 0); + return rv; + } + } + } + + // append segment to mLineBuf... + mLineBuf.Append(segment, len); + + // a line buf with only a new line char signifies the end of headers. + if (mLineBuf.First() == '\n') { + mLineBuf.Truncate(); + // discard this response if it is a 100 continue or other 1xx status. + uint16_t status = mResponseHead->Status(); + if ((status != 101) && (status / 100 == 1)) { + LOG(("ignoring 1xx response\n")); + mHaveStatusLine = false; + mHttpResponseMatched = false; + mConnection->SetLastTransactionExpectedNoContent(true); + mResponseHead->Reset(); + return NS_OK; + } + mHaveAllHeaders = true; + } + return NS_OK; +} + +nsresult +nsHttpTransaction::ParseHead(char *buf, + uint32_t count, + uint32_t *countRead) +{ + nsresult rv; + uint32_t len; + char *eol; + + LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count)); + + *countRead = 0; + + NS_PRECONDITION(!mHaveAllHeaders, "oops"); + + // allocate the response head object if necessary + if (!mResponseHead) { + mResponseHead = new nsHttpResponseHead(); + if (!mResponseHead) + return NS_ERROR_OUT_OF_MEMORY; + + // report that we have a least some of the response + if (mActivityDistributor && !mReportedStart) { + mReportedStart = true; + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, + PR_Now(), 0, EmptyCString()); + } + } + + if (!mHttpResponseMatched) { + // Normally we insist on seeing HTTP/1.x in the first few bytes, + // but if we are on a persistent connection and the previous transaction + // was not supposed to have any content then we need to be prepared + // to skip over a response body that the server may have sent even + // though it wasn't allowed. + if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) { + // tolerate only minor junk before the status line + mHttpResponseMatched = true; + char *p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true); + if (!p) { + // Treat any 0.9 style response of a put as a failure. + if (mRequestHead->IsPut()) + return NS_ERROR_ABORT; + + mResponseHead->ParseStatusLine(EmptyCString()); + mHaveStatusLine = true; + mHaveAllHeaders = true; + return NS_OK; + } + if (p > buf) { + // skip over the junk + mInvalidResponseBytesRead += p - buf; + *countRead = p - buf; + buf = p; + } + } + else { + char *p = LocateHttpStart(buf, count, false); + if (p) { + mInvalidResponseBytesRead += p - buf; + *countRead = p - buf; + buf = p; + mHttpResponseMatched = true; + } else { + mInvalidResponseBytesRead += count; + *countRead = count; + if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) { + LOG(("nsHttpTransaction::ParseHead() " + "Cannot find Response Header\n")); + // cannot go back and call this 0.9 anymore as we + // have thrown away a lot of the leading junk + return NS_ERROR_ABORT; + } + return NS_OK; + } + } + } + // otherwise we can assume that we don't have a HTTP/0.9 response. + + MOZ_ASSERT (mHttpResponseMatched); + while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) != nullptr) { + // found line in range [buf:eol] + len = eol - buf + 1; + + *countRead += len; + + // actually, the line is in the range [buf:eol-1] + if ((eol > buf) && (*(eol-1) == '\r')) + len--; + + buf[len-1] = '\n'; + rv = ParseLineSegment(buf, len); + if (NS_FAILED(rv)) + return rv; + + if (mHaveAllHeaders) + return NS_OK; + + // skip over line + buf = eol + 1; + + if (!mHttpResponseMatched) { + // a 100 class response has caused us to throw away that set of + // response headers and look for the next response + return NS_ERROR_NET_INTERRUPT; + } + } + + // do something about a partial header line + if (!mHaveAllHeaders && (len = count - *countRead)) { + *countRead = count; + // ignore a trailing carriage return, and don't bother calling + // ParseLineSegment if buf only contains a carriage return. + if ((buf[len-1] == '\r') && (--len == 0)) + return NS_OK; + rv = ParseLineSegment(buf, len); + if (NS_FAILED(rv)) + return rv; + } + return NS_OK; +} + +nsresult +nsHttpTransaction::HandleContentStart() +{ + LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this)); + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + if (mResponseHead) { + if (LOG3_ENABLED()) { + LOG3(("http response [\n")); + nsAutoCString headers; + mResponseHead->Flatten(headers, false); + headers.AppendLiteral(" OriginalHeaders"); + headers.AppendLiteral("\r\n"); + mResponseHead->FlattenNetworkOriginalHeaders(headers); + LogHeaders(headers.get()); + LOG3(("]\n")); + } + + CheckForStickyAuthScheme(); + + // Save http version, mResponseHead isn't available anymore after + // TakeResponseHead() is called + mHttpVersion = mResponseHead->Version(); + mHttpResponseCode = mResponseHead->Status(); + + // notify the connection, give it a chance to cause a reset. + bool reset = false; + if (!mRestartInProgressVerifier.IsSetup()) + mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset); + + // looks like we should ignore this response, resetting... + if (reset) { + LOG(("resetting transaction's response head\n")); + mHaveAllHeaders = false; + mHaveStatusLine = false; + mReceivedData = false; + mSentData = false; + mHttpResponseMatched = false; + mResponseHead->Reset(); + // wait to be called again... + return NS_OK; + } + + // check if this is a no-content response + switch (mResponseHead->Status()) { + case 101: + mPreserveStream = true; + MOZ_FALLTHROUGH; // to other no content cases: + case 204: + case 205: + case 304: + mNoContent = true; + LOG(("this response should not contain a body.\n")); + break; + case 421: + LOG(("Misdirected Request.\n")); + gHttpHandler->ConnMgr()->ClearHostMapping(mConnInfo); + + // retry on a new connection - just in case + if (!mRestartCount) { + mCaps &= ~NS_HTTP_ALLOW_KEEPALIVE; + mForceRestart = true; // force restart has built in loop protection + return NS_ERROR_NET_RESET; + } + break; + } + + if (mResponseHead->Status() == 200 && + mConnection->IsProxyConnectInProgress()) { + // successful CONNECTs do not have response bodies + mNoContent = true; + } + mConnection->SetLastTransactionExpectedNoContent(mNoContent); + if (mInvalidResponseBytesRead) + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, + nullptr, mClassification); + + if (mNoContent) + mContentLength = 0; + else { + // grab the content-length from the response headers + mContentLength = mResponseHead->ContentLength(); + + if ((mClassification != CLASS_SOLO) && + (mContentLength > mMaxPipelineObjectSize)) + CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge); + + // handle chunked encoding here, so we'll know immediately when + // we're done with the socket. please note that _all_ other + // decoding is done when the channel receives the content data + // so as not to block the socket transport thread too much. + if (mResponseHead->Version() >= NS_HTTP_VERSION_1_0 && + mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) { + // we only support the "chunked" transfer encoding right now. + mChunkedDecoder = new nsHttpChunkedDecoder(); + LOG(("nsHttpTransaction %p chunked decoder created\n", this)); + // Ignore server specified Content-Length. + if (mContentLength != int64_t(-1)) { + LOG(("nsHttpTransaction %p chunked with C-L ignores C-L\n", this)); + mContentLength = -1; + if (mConnection) { + mConnection->DontReuse(); + } + } + } + else if (mContentLength == int64_t(-1)) + LOG(("waiting for the server to close the connection.\n")); + } + if (mRestartInProgressVerifier.IsSetup() && + !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) { + LOG(("Restart in progress subsequent transaction failed to match")); + return NS_ERROR_ABORT; + } + } + + mDidContentStart = true; + + // The verifier only initializes itself once (from the first iteration of + // a transaction that gets far enough to have response headers) + if (mRequestHead->IsGet()) + mRestartInProgressVerifier.Set(mContentLength, mResponseHead); + + return NS_OK; +} + +// called on the socket thread +nsresult +nsHttpTransaction::HandleContent(char *buf, + uint32_t count, + uint32_t *contentRead, + uint32_t *contentRemaining) +{ + nsresult rv; + + LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count)); + + *contentRead = 0; + *contentRemaining = 0; + + MOZ_ASSERT(mConnection); + + if (!mDidContentStart) { + rv = HandleContentStart(); + if (NS_FAILED(rv)) return rv; + // Do not write content to the pipe if we haven't started streaming yet + if (!mDidContentStart) + return NS_OK; + } + + if (mChunkedDecoder) { + // give the buf over to the chunked decoder so it can reformat the + // data and tell us how much is really there. + rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, contentRemaining); + if (NS_FAILED(rv)) return rv; + } + else if (mContentLength >= int64_t(0)) { + // HTTP/1.0 servers have been known to send erroneous Content-Length + // headers. So, unless the connection is persistent, we must make + // allowances for a possibly invalid Content-Length header. Thus, if + // NOT persistent, we simply accept everything in |buf|. + if (mConnection->IsPersistent() || mPreserveStream || + mHttpVersion >= NS_HTTP_VERSION_1_1) { + int64_t remaining = mContentLength - mContentRead; + *contentRead = uint32_t(std::min<int64_t>(count, remaining)); + *contentRemaining = count - *contentRead; + } + else { + *contentRead = count; + // mContentLength might need to be increased... + int64_t position = mContentRead + int64_t(count); + if (position > mContentLength) { + mContentLength = position; + //mResponseHead->SetContentLength(mContentLength); + } + } + } + else { + // when we are just waiting for the server to close the connection... + // (no explicit content-length given) + *contentRead = count; + } + + int64_t toReadBeforeRestart = + mRestartInProgressVerifier.ToReadBeforeRestart(); + + if (toReadBeforeRestart && *contentRead) { + uint32_t ignore = + static_cast<uint32_t>(std::min<int64_t>(toReadBeforeRestart, UINT32_MAX)); + ignore = std::min(*contentRead, ignore); + LOG(("Due To Restart ignoring %d of remaining %ld", + ignore, toReadBeforeRestart)); + *contentRead -= ignore; + mContentRead += ignore; + mRestartInProgressVerifier.HaveReadBeforeRestart(ignore); + memmove(buf, buf + ignore, *contentRead + *contentRemaining); + } + + if (*contentRead) { + // update count of content bytes read and report progress... + mContentRead += *contentRead; + } + + LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%lld mContentLength=%lld]\n", + this, count, *contentRead, mContentRead, mContentLength)); + + // Check the size of chunked responses. If we exceed the max pipeline size + // for this response reschedule the pipeline + if ((mClassification != CLASS_SOLO) && + mChunkedDecoder && + ((mContentRead + mChunkedDecoder->GetChunkRemaining()) > + mMaxPipelineObjectSize)) { + CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge); + } + + // check for end-of-file + if ((mContentRead == mContentLength) || + (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) { + // the transaction is done with a complete response. + mTransactionDone = true; + mResponseIsComplete = true; + ReleaseBlockingTransaction(); + + if (TimingEnabled()) { + SetResponseEnd(TimeStamp::Now()); + } + + // report the entire response has arrived + if (mActivityDistributor) + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, + PR_Now(), + static_cast<uint64_t>(mContentRead), + EmptyCString()); + } + + return NS_OK; +} + +nsresult +nsHttpTransaction::ProcessData(char *buf, uint32_t count, uint32_t *countRead) +{ + nsresult rv; + + LOG(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count)); + + *countRead = 0; + + // we may not have read all of the headers yet... + if (!mHaveAllHeaders) { + uint32_t bytesConsumed = 0; + + do { + uint32_t localBytesConsumed = 0; + char *localBuf = buf + bytesConsumed; + uint32_t localCount = count - bytesConsumed; + + rv = ParseHead(localBuf, localCount, &localBytesConsumed); + if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT) + return rv; + bytesConsumed += localBytesConsumed; + } while (rv == NS_ERROR_NET_INTERRUPT); + + mCurrentHttpResponseHeaderSize += bytesConsumed; + if (mCurrentHttpResponseHeaderSize > + gHttpHandler->MaxHttpResponseHeaderSize()) { + LOG(("nsHttpTransaction %p The response header exceeds the limit.\n", + this)); + return NS_ERROR_FILE_TOO_BIG; + } + count -= bytesConsumed; + + // if buf has some content in it, shift bytes to top of buf. + if (count && bytesConsumed) + memmove(buf, buf + bytesConsumed, count); + + // report the completed response header + if (mActivityDistributor && mResponseHead && mHaveAllHeaders && + !mReportedResponseHeader) { + mReportedResponseHeader = true; + nsAutoCString completeResponseHeaders; + mResponseHead->Flatten(completeResponseHeaders, false); + completeResponseHeaders.AppendLiteral("\r\n"); + mActivityDistributor->ObserveActivity( + mChannel, + NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, + NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER, + PR_Now(), 0, + completeResponseHeaders); + } + } + + // even though count may be 0, we still want to call HandleContent + // so it can complete the transaction if this is a "no-content" response. + if (mHaveAllHeaders) { + uint32_t countRemaining = 0; + // + // buf layout: + // + // +--------------------------------------+----------------+-----+ + // | countRead | countRemaining | | + // +--------------------------------------+----------------+-----+ + // + // count : bytes read from the socket + // countRead : bytes corresponding to this transaction + // countRemaining : bytes corresponding to next pipelined transaction + // + // NOTE: + // count > countRead + countRemaining <==> chunked transfer encoding + // + rv = HandleContent(buf, count, countRead, &countRemaining); + if (NS_FAILED(rv)) return rv; + // we may have read more than our share, in which case we must give + // the excess bytes back to the connection + if (mResponseIsComplete && countRemaining) { + MOZ_ASSERT(mConnection); + mConnection->PushBack(buf + *countRead, countRemaining); + } + + if (!mContentDecodingCheck && mResponseHead) { + mContentDecoding = + mResponseHead->HasHeader(nsHttp::Content_Encoding); + mContentDecodingCheck = true; + } + } + + return NS_OK; +} + +void +nsHttpTransaction::CancelPipeline(uint32_t reason) +{ + // reason is casted through a uint to avoid compiler header deps + gHttpHandler->ConnMgr()->PipelineFeedbackInfo( + mConnInfo, + static_cast<nsHttpConnectionMgr::PipelineFeedbackInfoType>(reason), + nullptr, mClassification); + + mConnection->CancelPipeline(NS_ERROR_ABORT); + + // Avoid pipelining this transaction on restart by classifying it as solo. + // This also prevents BadUnexpectedLarge from being reported more + // than one time per transaction. + mClassification = CLASS_SOLO; +} + + +void +nsHttpTransaction::SetRequestContext(nsIRequestContext *aRequestContext) +{ + LOG(("nsHttpTransaction %p SetRequestContext %p\n", this, aRequestContext)); + mRequestContext = aRequestContext; +} + +// Called when the transaction marked for blocking is associated with a connection +// (i.e. added to a new h1 conn, an idle http connection, or placed into +// a http pipeline). It is safe to call this multiple times with it only +// having an effect once. +void +nsHttpTransaction::DispatchedAsBlocking() +{ + if (mDispatchedAsBlocking) + return; + + LOG(("nsHttpTransaction %p dispatched as blocking\n", this)); + + if (!mRequestContext) + return; + + LOG(("nsHttpTransaction adding blocking transaction %p from " + "request context %p\n", this, mRequestContext.get())); + + mRequestContext->AddBlockingTransaction(); + mDispatchedAsBlocking = true; +} + +void +nsHttpTransaction::RemoveDispatchedAsBlocking() +{ + if (!mRequestContext || !mDispatchedAsBlocking) + return; + + uint32_t blockers = 0; + nsresult rv = mRequestContext->RemoveBlockingTransaction(&blockers); + + LOG(("nsHttpTransaction removing blocking transaction %p from " + "request context %p. %d blockers remain.\n", this, + mRequestContext.get(), blockers)); + + if (NS_SUCCEEDED(rv) && !blockers) { + LOG(("nsHttpTransaction %p triggering release of blocked channels " + " with request context=%p\n", this, mRequestContext.get())); + gHttpHandler->ConnMgr()->ProcessPendingQ(); + } + + mDispatchedAsBlocking = false; +} + +void +nsHttpTransaction::ReleaseBlockingTransaction() +{ + RemoveDispatchedAsBlocking(); + LOG(("nsHttpTransaction %p request context set to null " + "in ReleaseBlockingTransaction() - was %p\n", this, mRequestContext.get())); + mRequestContext = nullptr; +} + +void +nsHttpTransaction::DisableSpdy() +{ + mCaps |= NS_HTTP_DISALLOW_SPDY; + if (mConnInfo) { + // This is our clone of the connection info, not the persistent one that + // is owned by the connection manager, so we're safe to change this here + mConnInfo->SetNoSpdy(true); + } +} + +void +nsHttpTransaction::CheckForStickyAuthScheme() +{ + LOG(("nsHttpTransaction::CheckForStickyAuthScheme this=%p")); + + MOZ_ASSERT(mHaveAllHeaders); + MOZ_ASSERT(mResponseHead); + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + + CheckForStickyAuthSchemeAt(nsHttp::WWW_Authenticate); + CheckForStickyAuthSchemeAt(nsHttp::Proxy_Authenticate); +} + +void +nsHttpTransaction::CheckForStickyAuthSchemeAt(nsHttpAtom const& header) +{ + if (mCaps & NS_HTTP_STICKY_CONNECTION) { + LOG((" already sticky")); + return; + } + + nsAutoCString auth; + if (NS_FAILED(mResponseHead->GetHeader(header, auth))) { + return; + } + + Tokenizer p(auth); + nsAutoCString schema; + while (p.ReadWord(schema)) { + ToLowerCase(schema); + + nsAutoCString contractid; + contractid.Assign(NS_HTTP_AUTHENTICATOR_CONTRACTID_PREFIX); + contractid.Append(schema); + + // using a new instance because of thread safety of auth modules refcnt + nsCOMPtr<nsIHttpAuthenticator> authenticator(do_CreateInstance(contractid.get())); + if (authenticator) { + uint32_t flags; + authenticator->GetAuthFlags(&flags); + if (flags & nsIHttpAuthenticator::CONNECTION_BASED) { + LOG((" connection made sticky, found %s auth shema", schema.get())); + // This is enough to make this transaction keep it's current connection, + // prevents the connection from being released back to the pool. + mCaps |= NS_HTTP_STICKY_CONNECTION; + break; + } + } + + // schemes are separated with LFs, nsHttpHeaderArray::MergeHeader + p.SkipUntil(Tokenizer::Token::NewLine()); + p.SkipWhites(Tokenizer::INCLUDE_NEW_LINE); + } +} + +const TimingStruct +nsHttpTransaction::Timings() +{ + mozilla::MutexAutoLock lock(mLock); + TimingStruct timings = mTimings; + return timings; +} + +void +nsHttpTransaction::SetDomainLookupStart(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.domainLookupStart.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.domainLookupStart = timeStamp; +} + +void +nsHttpTransaction::SetDomainLookupEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.domainLookupEnd.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.domainLookupEnd = timeStamp; +} + +void +nsHttpTransaction::SetConnectStart(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.connectStart.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.connectStart = timeStamp; +} + +void +nsHttpTransaction::SetConnectEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.connectEnd.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.connectEnd = timeStamp; +} + +void +nsHttpTransaction::SetRequestStart(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.requestStart.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.requestStart = timeStamp; +} + +void +nsHttpTransaction::SetResponseStart(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.responseStart.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.responseStart = timeStamp; +} + +void +nsHttpTransaction::SetResponseEnd(mozilla::TimeStamp timeStamp, bool onlyIfNull) +{ + mozilla::MutexAutoLock lock(mLock); + if (onlyIfNull && !mTimings.responseEnd.IsNull()) { + return; // We only set the timestamp if it was previously null + } + mTimings.responseEnd = timeStamp; +} + +mozilla::TimeStamp +nsHttpTransaction::GetDomainLookupStart() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.domainLookupStart; +} + +mozilla::TimeStamp +nsHttpTransaction::GetDomainLookupEnd() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.domainLookupEnd; +} + +mozilla::TimeStamp +nsHttpTransaction::GetConnectStart() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.connectStart; +} + +mozilla::TimeStamp +nsHttpTransaction::GetConnectEnd() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.connectEnd; +} + +mozilla::TimeStamp +nsHttpTransaction::GetRequestStart() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.requestStart; +} + +mozilla::TimeStamp +nsHttpTransaction::GetResponseStart() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.responseStart; +} + +mozilla::TimeStamp +nsHttpTransaction::GetResponseEnd() +{ + mozilla::MutexAutoLock lock(mLock); + return mTimings.responseEnd; +} + +//----------------------------------------------------------------------------- +// nsHttpTransaction deletion event +//----------------------------------------------------------------------------- + +class DeleteHttpTransaction : public Runnable { +public: + explicit DeleteHttpTransaction(nsHttpTransaction *trans) + : mTrans(trans) + {} + + NS_IMETHOD Run() override + { + delete mTrans; + return NS_OK; + } +private: + nsHttpTransaction *mTrans; +}; + +void +nsHttpTransaction::DeleteSelfOnConsumerThread() +{ + LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this)); + + bool val; + if (!mConsumerTarget || + (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) { + delete this; + } else { + LOG(("proxying delete to consumer thread...\n")); + nsCOMPtr<nsIRunnable> event = new DeleteHttpTransaction(this); + if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL))) + NS_WARNING("failed to dispatch nsHttpDeleteTransaction event"); + } +} + +bool +nsHttpTransaction::TryToRunPacedRequest() +{ + if (mSubmittedRatePacing) + return mPassedRatePacing; + + mSubmittedRatePacing = true; + mSynchronousRatePaceRequest = true; + gHttpHandler->SubmitPacedRequest(this, getter_AddRefs(mTokenBucketCancel)); + mSynchronousRatePaceRequest = false; + return mPassedRatePacing; +} + +void +nsHttpTransaction::OnTokenBucketAdmitted() +{ + mPassedRatePacing = true; + mTokenBucketCancel = nullptr; + + if (!mSynchronousRatePaceRequest) + gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); +} + +void +nsHttpTransaction::CancelPacing(nsresult reason) +{ + if (mTokenBucketCancel) { + mTokenBucketCancel->Cancel(reason); + mTokenBucketCancel = nullptr; + } +} + +//----------------------------------------------------------------------------- +// nsHttpTransaction::nsISupports +//----------------------------------------------------------------------------- + +NS_IMPL_ADDREF(nsHttpTransaction) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsHttpTransaction::Release() +{ + nsrefcnt count; + NS_PRECONDITION(0 != mRefCnt, "dup release"); + count = --mRefCnt; + NS_LOG_RELEASE(this, count, "nsHttpTransaction"); + if (0 == count) { + mRefCnt = 1; /* stablize */ + // it is essential that the transaction be destroyed on the consumer + // thread (we could be holding the last reference to our consumer). + DeleteSelfOnConsumerThread(); + return 0; + } + return count; +} + +NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, + nsIInputStreamCallback, + nsIOutputStreamCallback) + +//----------------------------------------------------------------------------- +// nsHttpTransaction::nsIInputStreamCallback +//----------------------------------------------------------------------------- + +// called on the socket thread +NS_IMETHODIMP +nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + if (mConnection) { + mConnection->TransactionHasDataToWrite(this); + nsresult rv = mConnection->ResumeSend(); + if (NS_FAILED(rv)) + NS_ERROR("ResumeSend failed"); + } + return NS_OK; +} + +//----------------------------------------------------------------------------- +// nsHttpTransaction::nsIOutputStreamCallback +//----------------------------------------------------------------------------- + +// called on the socket thread +NS_IMETHODIMP +nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) +{ + MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); + mWaitingOnPipeOut = false; + if (mConnection) { + mConnection->TransactionHasDataToRecv(this); + nsresult rv = mConnection->ResumeRecv(); + if (NS_FAILED(rv)) + NS_ERROR("ResumeRecv failed"); + } + return NS_OK; +} + +// nsHttpTransaction::RestartVerifier + +static bool +matchOld(nsHttpResponseHead *newHead, nsCString &old, + nsHttpAtom headerAtom) +{ + nsAutoCString val; + + newHead->GetHeader(headerAtom, val); + if (!val.IsEmpty() && old.IsEmpty()) + return false; + if (val.IsEmpty() && !old.IsEmpty()) + return false; + if (!val.IsEmpty() && !old.Equals(val)) + return false; + return true; +} + +bool +nsHttpTransaction::RestartVerifier::Verify(int64_t contentLength, + nsHttpResponseHead *newHead) +{ + if (mContentLength != contentLength) + return false; + + if (newHead->Status() != 200) + return false; + + if (!matchOld(newHead, mContentRange, nsHttp::Content_Range)) + return false; + + if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified)) + return false; + + if (!matchOld(newHead, mETag, nsHttp::ETag)) + return false; + + if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding)) + return false; + + if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding)) + return false; + + return true; +} + +void +nsHttpTransaction::RestartVerifier::Set(int64_t contentLength, + nsHttpResponseHead *head) +{ + if (mSetup) + return; + + // If mSetup does not transition to true RestartInPogress() is later + // forbidden + + // Only RestartInProgress with 200 response code + if (!head || (head->Status() != 200)) { + return; + } + + mContentLength = contentLength; + + nsAutoCString val; + if (NS_SUCCEEDED(head->GetHeader(nsHttp::ETag, val))) { + mETag = val; + } + if (NS_SUCCEEDED(head->GetHeader(nsHttp::Last_Modified, val))) { + mLastModified = val; + } + if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Range, val))) { + mContentRange = val; + } + if (NS_SUCCEEDED(head->GetHeader(nsHttp::Content_Encoding, val))) { + mContentEncoding = val; + } + if (NS_SUCCEEDED(head->GetHeader(nsHttp::Transfer_Encoding, val))) { + mTransferEncoding = val; + } + + // We can only restart with any confidence if we have a stored etag or + // last-modified header + if (mETag.IsEmpty() && mLastModified.IsEmpty()) { + return; + } + + mSetup = true; +} + +void +nsHttpTransaction::GetNetworkAddresses(NetAddr &self, NetAddr &peer) +{ + MutexAutoLock lock(mLock); + self = mSelfAddr; + peer = mPeerAddr; +} + +bool +nsHttpTransaction::Do0RTT() +{ + if (mRequestHead->IsSafeMethod() && + !mConnection->IsProxyConnectInProgress()) { + m0RTTInProgress = true; + } + return m0RTTInProgress; +} + +nsresult +nsHttpTransaction::Finish0RTT(bool aRestart) +{ + MOZ_ASSERT(m0RTTInProgress); + m0RTTInProgress = false; + if (aRestart) { + // Reset request headers to be sent again. + nsCOMPtr<nsISeekableStream> seekable = + do_QueryInterface(mRequestStream); + if (seekable) { + seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + } else { + return NS_ERROR_FAILURE; + } + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla |