michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* vim:set ts=4 sw=4 sts=4 et cin: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // HttpLog.h should generally be included first michael@0: #include "HttpLog.h" michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "nsHttpHandler.h" michael@0: #include "nsHttpTransaction.h" michael@0: #include "nsHttpRequestHead.h" michael@0: #include "nsHttpResponseHead.h" michael@0: #include "nsHttpChunkedDecoder.h" michael@0: #include "nsTransportUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsCRT.h" michael@0: michael@0: #include "nsISeekableStream.h" michael@0: #include "nsMultiplexInputStream.h" michael@0: #include "nsStringStream.h" michael@0: #include "mozilla/VisualEventTracer.h" michael@0: michael@0: #include "nsComponentManagerUtils.h" // do_CreateInstance michael@0: #include "nsServiceManagerUtils.h" // do_GetService michael@0: #include "nsIHttpActivityObserver.h" michael@0: #include "nsSocketTransportService2.h" michael@0: #include "nsICancelable.h" michael@0: #include "nsIEventTarget.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsIInputStream.h" michael@0: #include "nsITransport.h" michael@0: #include "nsIOService.h" michael@0: #include michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: #include "NetStatistics.h" michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #ifdef DEBUG michael@0: // defined by the socket transport service while active michael@0: extern PRThread *gSocketThread; michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID); michael@0: michael@0: // Place a limit on how much non-compliant HTTP can be skipped while michael@0: // looking for a response header michael@0: #define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128) michael@0: michael@0: using namespace mozilla::net; michael@0: michael@0: namespace mozilla { michael@0: namespace net { michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // helpers michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: #if defined(PR_LOGGING) michael@0: static void michael@0: LogHeaders(const char *lineStart) michael@0: { michael@0: nsAutoCString buf; michael@0: char *endOfLine; michael@0: while ((endOfLine = PL_strstr(lineStart, "\r\n"))) { michael@0: buf.Assign(lineStart, endOfLine - lineStart); michael@0: if (PL_strcasestr(buf.get(), "authorization: ") || michael@0: PL_strcasestr(buf.get(), "proxy-authorization: ")) { michael@0: char *p = PL_strchr(PL_strchr(buf.get(), ' ') + 1, ' '); michael@0: while (p && *++p) michael@0: *p = '*'; michael@0: } michael@0: LOG3((" %s\n", buf.get())); michael@0: lineStart = endOfLine + 2; michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpTransaction michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsHttpTransaction::nsHttpTransaction() michael@0: : mLock("transaction lock") michael@0: , mRequestSize(0) michael@0: , mConnection(nullptr) michael@0: , mConnInfo(nullptr) michael@0: , mRequestHead(nullptr) michael@0: , mResponseHead(nullptr) michael@0: , mContentLength(-1) michael@0: , mContentRead(0) michael@0: , mInvalidResponseBytesRead(0) michael@0: , mChunkedDecoder(nullptr) michael@0: , mStatus(NS_OK) michael@0: , mPriority(0) michael@0: , mRestartCount(0) michael@0: , mCaps(0) michael@0: , mCapsToClear(0) michael@0: , mClassification(CLASS_GENERAL) michael@0: , mPipelinePosition(0) michael@0: , mHttpVersion(NS_HTTP_VERSION_UNKNOWN) michael@0: , mClosed(false) michael@0: , mConnected(false) michael@0: , mHaveStatusLine(false) michael@0: , mHaveAllHeaders(false) michael@0: , mTransactionDone(false) michael@0: , mResponseIsComplete(false) michael@0: , mDidContentStart(false) michael@0: , mNoContent(false) michael@0: , mSentData(false) michael@0: , mReceivedData(false) michael@0: , mStatusEventPending(false) michael@0: , mHasRequestBody(false) michael@0: , mProxyConnectFailed(false) michael@0: , mHttpResponseMatched(false) michael@0: , mPreserveStream(false) michael@0: , mDispatchedAsBlocking(false) michael@0: , mResponseTimeoutEnabled(true) michael@0: , mReportedStart(false) michael@0: , mReportedResponseHeader(false) michael@0: , mForTakeResponseHead(nullptr) michael@0: , mResponseHeadTaken(false) michael@0: , mSubmittedRatePacing(false) michael@0: , mPassedRatePacing(false) michael@0: , mSynchronousRatePaceRequest(false) michael@0: , mCountRecv(0) michael@0: , mCountSent(0) michael@0: , mAppId(NECKO_NO_APP_ID) michael@0: { michael@0: LOG(("Creating nsHttpTransaction @%p\n", this)); michael@0: gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize); michael@0: } michael@0: michael@0: nsHttpTransaction::~nsHttpTransaction() michael@0: { michael@0: LOG(("Destroying nsHttpTransaction @%p\n", this)); michael@0: michael@0: if (mTokenBucketCancel) { michael@0: mTokenBucketCancel->Cancel(NS_ERROR_ABORT); michael@0: mTokenBucketCancel = nullptr; michael@0: } michael@0: michael@0: // Force the callbacks to be released right now michael@0: mCallbacks = nullptr; michael@0: michael@0: NS_IF_RELEASE(mConnection); michael@0: NS_IF_RELEASE(mConnInfo); michael@0: michael@0: delete mResponseHead; michael@0: delete mForTakeResponseHead; michael@0: delete mChunkedDecoder; michael@0: ReleaseBlockingTransaction(); michael@0: } michael@0: michael@0: nsHttpTransaction::Classifier michael@0: nsHttpTransaction::Classify() michael@0: { michael@0: if (!(mCaps & NS_HTTP_ALLOW_PIPELINING)) michael@0: return (mClassification = CLASS_SOLO); michael@0: michael@0: if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) || michael@0: mRequestHead->PeekHeader(nsHttp::If_None_Match)) michael@0: return (mClassification = CLASS_REVALIDATION); michael@0: michael@0: const char *accept = mRequestHead->PeekHeader(nsHttp::Accept); michael@0: if (accept && !PL_strncmp(accept, "image/", 6)) michael@0: return (mClassification = CLASS_IMAGE); michael@0: michael@0: if (accept && !PL_strncmp(accept, "text/css", 8)) michael@0: return (mClassification = CLASS_SCRIPT); michael@0: michael@0: mClassification = CLASS_GENERAL; michael@0: michael@0: int32_t queryPos = mRequestHead->RequestURI().FindChar('?'); michael@0: if (queryPos == kNotFound) { michael@0: if (StringEndsWith(mRequestHead->RequestURI(), michael@0: NS_LITERAL_CSTRING(".js"))) michael@0: mClassification = CLASS_SCRIPT; michael@0: } michael@0: else if (queryPos >= 3 && michael@0: Substring(mRequestHead->RequestURI(), queryPos - 3, 3). michael@0: EqualsLiteral(".js")) { michael@0: mClassification = CLASS_SCRIPT; michael@0: } michael@0: michael@0: return mClassification; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::Init(uint32_t caps, michael@0: nsHttpConnectionInfo *cinfo, michael@0: nsHttpRequestHead *requestHead, michael@0: nsIInputStream *requestBody, michael@0: bool requestBodyHasHeaders, michael@0: nsIEventTarget *target, michael@0: nsIInterfaceRequestor *callbacks, michael@0: nsITransportEventSink *eventsink, michael@0: nsIAsyncInputStream **responseBody) michael@0: { michael@0: MOZ_EVENT_TRACER_COMPOUND_NAME(static_cast(this), michael@0: requestHead->PeekHeader(nsHttp::Host), michael@0: requestHead->RequestURI().BeginReading()); michael@0: michael@0: MOZ_EVENT_TRACER_WAIT(static_cast(this), michael@0: "net::http::transaction"); michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps)); michael@0: michael@0: MOZ_ASSERT(cinfo); michael@0: MOZ_ASSERT(requestHead); michael@0: MOZ_ASSERT(target); michael@0: michael@0: mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: bool activityDistributorActive; michael@0: rv = mActivityDistributor->GetIsActive(&activityDistributorActive); michael@0: if (NS_SUCCEEDED(rv) && activityDistributorActive) { michael@0: // there are some observers registered at activity distributor, gather michael@0: // nsISupports for the channel that called Init() michael@0: mChannel = do_QueryInterface(eventsink); michael@0: LOG(("nsHttpTransaction::Init() " \ michael@0: "mActivityDistributor is active " \ michael@0: "this=%p", this)); michael@0: } else { michael@0: // there is no observer, so don't use it michael@0: activityDistributorActive = false; michael@0: mActivityDistributor = nullptr; michael@0: } michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(eventsink); michael@0: if (channel) { michael@0: bool isInBrowser; michael@0: NS_GetAppInfo(channel, &mAppId, &isInBrowser); michael@0: } michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: if (mAppId != NECKO_NO_APP_ID) { michael@0: nsCOMPtr activeNetwork; michael@0: GetActiveNetworkInterface(activeNetwork); michael@0: mActiveNetwork = michael@0: new nsMainThreadPtrHolder(activeNetwork); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr httpChannelInternal = michael@0: do_QueryInterface(eventsink); michael@0: if (httpChannelInternal) { michael@0: rv = httpChannelInternal->GetResponseTimeoutEnabled( michael@0: &mResponseTimeoutEnabled); michael@0: if (NS_WARN_IF(NS_FAILED(rv))) { michael@0: return rv; michael@0: } michael@0: } michael@0: michael@0: // create transport event sink proxy. it coalesces all events if and only michael@0: // if the activity observer is not active. when the observer is active michael@0: // we need not to coalesce any events to get all expected notifications michael@0: // of the transaction state, necessary for correct debugging and logging. michael@0: rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink), michael@0: eventsink, target, michael@0: !activityDistributorActive); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: NS_ADDREF(mConnInfo = cinfo); michael@0: mCallbacks = callbacks; michael@0: mConsumerTarget = target; michael@0: mCaps = caps; michael@0: michael@0: if (requestHead->IsHead()) { michael@0: mNoContent = true; michael@0: } michael@0: michael@0: // Make sure that there is "Content-Length: 0" header in the requestHead michael@0: // in case of POST and PUT methods when there is no requestBody and michael@0: // requestHead doesn't contain "Transfer-Encoding" header. michael@0: // michael@0: // RFC1945 section 7.2.2: michael@0: // HTTP/1.0 requests containing an entity body must include a valid michael@0: // Content-Length header field. michael@0: // michael@0: // RFC2616 section 4.4: michael@0: // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests michael@0: // containing a message-body MUST include a valid Content-Length header michael@0: // field unless the server is known to be HTTP/1.1 compliant. michael@0: if ((requestHead->IsPost() || requestHead->IsPut()) && michael@0: !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) { michael@0: requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0")); michael@0: } michael@0: michael@0: // grab a weak reference to the request head michael@0: mRequestHead = requestHead; michael@0: michael@0: // make sure we eliminate any proxy specific headers from michael@0: // the request if we are using CONNECT michael@0: bool pruneProxyHeaders = cinfo->UsingConnect(); michael@0: michael@0: mReqHeaderBuf.Truncate(); michael@0: requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders); michael@0: michael@0: #if defined(PR_LOGGING) michael@0: if (LOG3_ENABLED()) { michael@0: LOG3(("http request [\n")); michael@0: LogHeaders(mReqHeaderBuf.get()); michael@0: LOG3(("]\n")); michael@0: } michael@0: #endif michael@0: michael@0: // If the request body does not include headers or if there is no request michael@0: // body, then we must add the header/body separator manually. michael@0: if (!requestBodyHasHeaders || !requestBody) michael@0: mReqHeaderBuf.AppendLiteral("\r\n"); michael@0: michael@0: // report the request header michael@0: if (mActivityDistributor) michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER, michael@0: PR_Now(), 0, michael@0: mReqHeaderBuf); michael@0: michael@0: // Create a string stream for the request header buf (the stream holds michael@0: // a non-owning reference to the request header data, so we MUST keep michael@0: // mReqHeaderBuf around). michael@0: nsCOMPtr headers; michael@0: rv = NS_NewByteInputStream(getter_AddRefs(headers), michael@0: mReqHeaderBuf.get(), michael@0: mReqHeaderBuf.Length()); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (requestBody) { michael@0: mHasRequestBody = true; michael@0: michael@0: // wrap the headers and request body in a multiplexed input stream. michael@0: nsCOMPtr multi = michael@0: do_CreateInstance(kMultiplexInputStream, &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = multi->AppendStream(headers); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = multi->AppendStream(requestBody); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // wrap the multiplexed input stream with a buffered input stream, so michael@0: // that we write data in the largest chunks possible. this is actually michael@0: // necessary to workaround some common server bugs (see bug 137155). michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi, michael@0: nsIOService::gDefaultSegmentSize); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: else michael@0: mRequestStream = headers; michael@0: michael@0: rv = mRequestStream->Available(&mRequestSize); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: // create pipe for response stream michael@0: rv = NS_NewPipe2(getter_AddRefs(mPipeIn), michael@0: getter_AddRefs(mPipeOut), michael@0: true, true, michael@0: nsIOService::gDefaultSegmentSize, michael@0: nsIOService::gDefaultSegmentCount); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: Classify(); michael@0: michael@0: NS_ADDREF(*responseBody = mPipeIn); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // This method should only be used on the socket thread michael@0: nsAHttpConnection * michael@0: nsHttpTransaction::Connection() michael@0: { michael@0: return mConnection; michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsHttpTransaction::GetConnectionReference() michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: nsRefPtr connection = mConnection; michael@0: return connection.forget(); michael@0: } michael@0: michael@0: nsHttpResponseHead * michael@0: nsHttpTransaction::TakeResponseHead() michael@0: { michael@0: MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x"); michael@0: michael@0: // Lock RestartInProgress() and TakeResponseHead() against main thread michael@0: MutexAutoLock lock(*nsHttp::GetLock()); michael@0: michael@0: mResponseHeadTaken = true; michael@0: michael@0: // Prefer mForTakeResponseHead over mResponseHead. It is always a complete michael@0: // set of headers. michael@0: nsHttpResponseHead *head; michael@0: if (mForTakeResponseHead) { michael@0: head = mForTakeResponseHead; michael@0: mForTakeResponseHead = nullptr; michael@0: return head; michael@0: } michael@0: michael@0: // Even in OnStartRequest() the headers won't be available if we were michael@0: // canceled michael@0: if (!mHaveAllHeaders) { michael@0: NS_WARNING("response headers not available or incomplete"); michael@0: return nullptr; michael@0: } michael@0: michael@0: head = mResponseHead; michael@0: mResponseHead = nullptr; michael@0: return head; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::SetProxyConnectFailed() michael@0: { michael@0: mProxyConnectFailed = true; michael@0: } michael@0: michael@0: nsHttpRequestHead * michael@0: nsHttpTransaction::RequestHead() michael@0: { michael@0: return mRequestHead; michael@0: } michael@0: michael@0: uint32_t michael@0: nsHttpTransaction::Http1xTransactionCount() michael@0: { michael@0: return 1; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::TakeSubTransactions( michael@0: nsTArray > &outTransactions) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: //---------------------------------------------------------------------------- michael@0: // nsHttpTransaction::nsAHttpTransaction michael@0: //---------------------------------------------------------------------------- michael@0: michael@0: void michael@0: nsHttpTransaction::SetConnection(nsAHttpConnection *conn) michael@0: { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: NS_IF_RELEASE(mConnection); michael@0: NS_IF_ADDREF(mConnection = conn); michael@0: } michael@0: michael@0: if (conn) { michael@0: MOZ_EVENT_TRACER_EXEC(static_cast(this), michael@0: "net::http::transaction"); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb) michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: NS_IF_ADDREF(*cb = mCallbacks); michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks) michael@0: { michael@0: { michael@0: MutexAutoLock lock(mLock); michael@0: mCallbacks = aCallbacks; michael@0: } michael@0: michael@0: if (gSocketTransportService) { michael@0: nsRefPtr event = new UpdateSecurityCallbacks(this, aCallbacks); michael@0: gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::OnTransportStatus(nsITransport* transport, michael@0: nsresult status, uint64_t progress) michael@0: { michael@0: LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%x progress=%llu]\n", michael@0: this, status, progress)); michael@0: michael@0: if (TimingEnabled()) { michael@0: if (status == NS_NET_STATUS_RESOLVING_HOST) { michael@0: mTimings.domainLookupStart = TimeStamp::Now(); michael@0: } else if (status == NS_NET_STATUS_RESOLVED_HOST) { michael@0: mTimings.domainLookupEnd = TimeStamp::Now(); michael@0: } else if (status == NS_NET_STATUS_CONNECTING_TO) { michael@0: mTimings.connectStart = TimeStamp::Now(); michael@0: } else if (status == NS_NET_STATUS_CONNECTED_TO) { michael@0: mTimings.connectEnd = TimeStamp::Now(); michael@0: } michael@0: } michael@0: michael@0: if (!mTransportSink) michael@0: return; michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: // Need to do this before the STATUS_RECEIVING_FROM check below, to make michael@0: // sure that the activity distributor gets told about all status events. michael@0: if (mActivityDistributor) { michael@0: // upon STATUS_WAITING_FOR; report request body sent michael@0: if ((mHasRequestBody) && michael@0: (status == NS_NET_STATUS_WAITING_FOR)) michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT, michael@0: PR_Now(), 0, EmptyCString()); michael@0: michael@0: // report the status and progress michael@0: if (!mRestartInProgressVerifier.IsDiscardingContent()) michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT, michael@0: static_cast(status), michael@0: PR_Now(), michael@0: progress, michael@0: EmptyCString()); michael@0: } michael@0: michael@0: // nsHttpChannel synthesizes progress events in OnDataAvailable michael@0: if (status == NS_NET_STATUS_RECEIVING_FROM) michael@0: return; michael@0: michael@0: uint64_t progressMax; michael@0: michael@0: if (status == NS_NET_STATUS_SENDING_TO) { michael@0: // suppress progress when only writing request headers michael@0: if (!mHasRequestBody) michael@0: return; michael@0: michael@0: nsCOMPtr seekable = do_QueryInterface(mRequestStream); michael@0: MOZ_ASSERT(seekable, "Request stream isn't seekable?!?"); michael@0: michael@0: int64_t prog = 0; michael@0: seekable->Tell(&prog); michael@0: progress = prog; michael@0: michael@0: // when uploading, we include the request headers in the progress michael@0: // notifications. michael@0: progressMax = mRequestSize; // XXX mRequestSize is 32-bit! michael@0: } michael@0: else { michael@0: progress = 0; michael@0: progressMax = 0; michael@0: } michael@0: michael@0: mTransportSink->OnTransportStatus(transport, status, progress, progressMax); michael@0: } michael@0: michael@0: bool michael@0: nsHttpTransaction::IsDone() michael@0: { michael@0: return mTransactionDone; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::Status() michael@0: { michael@0: return mStatus; michael@0: } michael@0: michael@0: uint32_t michael@0: nsHttpTransaction::Caps() michael@0: { michael@0: return mCaps & ~mCapsToClear; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::SetDNSWasRefreshed() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!"); michael@0: mCapsToClear |= NS_HTTP_REFRESH_DNS; michael@0: } michael@0: michael@0: uint64_t michael@0: nsHttpTransaction::Available() michael@0: { michael@0: uint64_t size; michael@0: if (NS_FAILED(mRequestStream->Available(&size))) michael@0: size = 0; michael@0: return size; michael@0: } michael@0: michael@0: NS_METHOD michael@0: nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream, michael@0: void *closure, michael@0: const char *buf, michael@0: uint32_t offset, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: nsHttpTransaction *trans = (nsHttpTransaction *) closure; michael@0: nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (trans->TimingEnabled() && trans->mTimings.requestStart.IsNull()) { michael@0: // First data we're sending -> this is requestStart michael@0: trans->mTimings.requestStart = TimeStamp::Now(); michael@0: } michael@0: michael@0: if (!trans->mSentData) { michael@0: MOZ_EVENT_TRACER_MARK(static_cast(trans), michael@0: "net::http::first-write"); michael@0: } michael@0: michael@0: trans->CountSentBytes(*countRead); michael@0: trans->mSentData = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader, michael@0: uint32_t count, uint32_t *countRead) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (mTransactionDone) { michael@0: *countRead = 0; michael@0: return mStatus; michael@0: } michael@0: michael@0: if (!mConnected) { michael@0: mConnected = true; michael@0: mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo)); michael@0: } michael@0: michael@0: mReader = reader; michael@0: michael@0: nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead); michael@0: michael@0: mReader = nullptr; michael@0: michael@0: // if read would block then we need to AsyncWait on the request stream. michael@0: // have callback occur on socket thread so we stay synchronized. michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) { michael@0: nsCOMPtr asyncIn = michael@0: do_QueryInterface(mRequestStream); michael@0: if (asyncIn) { michael@0: nsCOMPtr target; michael@0: gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); michael@0: if (target) michael@0: asyncIn->AsyncWait(this, 0, 0, target); michael@0: else { michael@0: NS_ERROR("no socket thread event target"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_METHOD michael@0: nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream, michael@0: void *closure, michael@0: char *buf, michael@0: uint32_t offset, michael@0: uint32_t count, michael@0: uint32_t *countWritten) michael@0: { michael@0: nsHttpTransaction *trans = (nsHttpTransaction *) closure; michael@0: michael@0: if (trans->mTransactionDone) michael@0: return NS_BASE_STREAM_CLOSED; // stop iterating michael@0: michael@0: if (trans->TimingEnabled() && trans->mTimings.responseStart.IsNull()) { michael@0: trans->mTimings.responseStart = TimeStamp::Now(); michael@0: } michael@0: michael@0: nsresult rv; michael@0: // michael@0: // OK, now let the caller fill this segment with data. michael@0: // michael@0: rv = trans->mWriter->OnWriteSegment(buf, count, countWritten); michael@0: if (NS_FAILED(rv)) return rv; // caller didn't want to write anything michael@0: michael@0: if (!trans->mReceivedData) { michael@0: MOZ_EVENT_TRACER_MARK(static_cast(trans), michael@0: "net::http::first-read"); michael@0: } michael@0: michael@0: MOZ_ASSERT(*countWritten > 0, "bad writer"); michael@0: trans->CountRecvBytes(*countWritten); michael@0: trans->mReceivedData = true; michael@0: michael@0: // Let the transaction "play" with the buffer. It is free to modify michael@0: // the contents of the buffer and/or modify countWritten. michael@0: // - Bytes in HTTP headers don't count towards countWritten, so the input michael@0: // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit michael@0: // OnInputStreamReady until all headers have been parsed. michael@0: // michael@0: rv = trans->ProcessData(buf, *countWritten, countWritten); michael@0: if (NS_FAILED(rv)) michael@0: trans->Close(rv); michael@0: michael@0: return rv; // failure code only stops WriteSegments; it is not propagated. michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer, michael@0: uint32_t count, uint32_t *countWritten) michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (mTransactionDone) michael@0: return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus; michael@0: michael@0: mWriter = writer; michael@0: michael@0: nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten); michael@0: michael@0: mWriter = nullptr; michael@0: michael@0: // if pipe would block then we need to AsyncWait on it. have callback michael@0: // occur on socket thread so we stay synchronized. michael@0: if (rv == NS_BASE_STREAM_WOULD_BLOCK) { michael@0: nsCOMPtr target; michael@0: gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target)); michael@0: if (target) michael@0: mPipeOut->AsyncWait(this, 0, 0, target); michael@0: else { michael@0: NS_ERROR("no socket thread event target"); michael@0: rv = NS_ERROR_UNEXPECTED; michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::SaveNetworkStats(bool enforce) michael@0: { michael@0: #ifdef MOZ_WIDGET_GONK michael@0: // Check if active network and appid are valid. michael@0: if (!mActiveNetwork || mAppId == NECKO_NO_APP_ID) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mCountRecv <= 0 && mCountSent <= 0) { michael@0: // There is no traffic, no need to save. michael@0: return NS_OK; michael@0: } michael@0: michael@0: // If |enforce| is false, the traffic amount is saved michael@0: // only when the total amount exceeds the predefined michael@0: // threshold. michael@0: uint64_t totalBytes = mCountRecv + mCountSent; michael@0: if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Create the event to save the network statistics. michael@0: // the event is then dispathed to the main thread. michael@0: nsRefPtr event = michael@0: new SaveNetworkStatsEvent(mAppId, mActiveNetwork, michael@0: mCountRecv, mCountSent, false); michael@0: NS_DispatchToMainThread(event); michael@0: michael@0: // Reset the counters after saving. michael@0: mCountSent = 0; michael@0: mCountRecv = 0; michael@0: michael@0: return NS_OK; michael@0: #else michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::Close(nsresult reason) michael@0: { michael@0: LOG(("nsHttpTransaction::Close [this=%p reason=%x]\n", this, reason)); michael@0: michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if (mClosed) { michael@0: LOG((" already closed\n")); michael@0: return; michael@0: } michael@0: michael@0: if (mTokenBucketCancel) { michael@0: mTokenBucketCancel->Cancel(reason); michael@0: mTokenBucketCancel = nullptr; michael@0: } michael@0: michael@0: if (mActivityDistributor) { michael@0: // report the reponse is complete if not already reported michael@0: if (!mResponseIsComplete) michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, michael@0: PR_Now(), michael@0: static_cast(mContentRead), michael@0: EmptyCString()); michael@0: michael@0: // report that this transaction is closing michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE, michael@0: PR_Now(), 0, EmptyCString()); michael@0: } michael@0: michael@0: // we must no longer reference the connection! find out if the michael@0: // connection was being reused before letting it go. michael@0: bool connReused = false; michael@0: if (mConnection) michael@0: connReused = mConnection->IsReused(); michael@0: mConnected = false; michael@0: michael@0: // michael@0: // if the connection was reset or closed before we wrote any part of the michael@0: // request or if we wrote the request but didn't receive any part of the michael@0: // response and the connection was being reused, then we can (and really michael@0: // should) assume that we wrote to a stale connection and we must therefore michael@0: // repeat the request over a new connection. michael@0: // michael@0: // NOTE: the conditions under which we will automatically retry the HTTP michael@0: // request have to be carefully selected to avoid duplication of the michael@0: // request from the point-of-view of the server. such duplication could michael@0: // have dire consequences including repeated purchases, etc. michael@0: // michael@0: // NOTE: because of the way SSL proxy CONNECT is implemented, it is michael@0: // possible that the transaction may have received data without having michael@0: // sent any data. for this reason, mSendData == FALSE does not imply michael@0: // mReceivedData == FALSE. (see bug 203057 for more info.) michael@0: // michael@0: if (reason == NS_ERROR_NET_RESET || reason == NS_OK) { michael@0: michael@0: // reallySentData is meant to separate the instances where data has michael@0: // been sent by this transaction but buffered at a higher level while michael@0: // a TLS session (perhaps via a tunnel) is setup. michael@0: bool reallySentData = michael@0: mSentData && (!mConnection || mConnection->BytesWritten()); michael@0: michael@0: if (!mReceivedData && michael@0: (!reallySentData || connReused || mPipelinePosition)) { michael@0: // if restarting fails, then we must proceed to close the pipe, michael@0: // which will notify the channel that the transaction failed. michael@0: michael@0: if (mPipelinePosition) { michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline, michael@0: nullptr, 0); michael@0: } michael@0: if (NS_SUCCEEDED(Restart())) michael@0: return; michael@0: } michael@0: else if (!mResponseIsComplete && mPipelinePosition && michael@0: reason == NS_ERROR_NET_RESET) { michael@0: // due to unhandled rst on a pipeline - safe to michael@0: // restart as only idempotent is found there michael@0: michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nullptr, 0); michael@0: if (NS_SUCCEEDED(RestartInProgress())) michael@0: return; michael@0: } michael@0: } michael@0: michael@0: bool relConn = true; michael@0: if (NS_SUCCEEDED(reason)) { michael@0: if (!mResponseIsComplete) { michael@0: // The response has not been delimited with a high-confidence michael@0: // algorithm like Content-Length or Chunked Encoding. We michael@0: // need to use a strong framing mechanism to pipeline. michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, michael@0: nullptr, mClassification); michael@0: } michael@0: else if (mPipelinePosition) { michael@0: // report this success as feedback michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, nsHttpConnectionMgr::GoodCompletedOK, michael@0: nullptr, mPipelinePosition); michael@0: } michael@0: michael@0: // the server has not sent the final \r\n terminating the header michael@0: // section, and there may still be a header line unparsed. let's make michael@0: // sure we parse the remaining header line, and then hopefully, the michael@0: // response will be usable (see bug 88792). michael@0: if (!mHaveAllHeaders) { michael@0: char data = '\n'; michael@0: uint32_t unused; michael@0: ParseHead(&data, 1, &unused); michael@0: michael@0: if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) { michael@0: // Reject 0 byte HTTP/0.9 Responses - bug 423506 michael@0: LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this)); michael@0: reason = NS_ERROR_NET_RESET; michael@0: } michael@0: } michael@0: michael@0: // honor the sticky connection flag... michael@0: if (mCaps & NS_HTTP_STICKY_CONNECTION) michael@0: relConn = false; michael@0: } michael@0: michael@0: // mTimings.responseEnd is normally recorded based on the end of a michael@0: // HTTP delimiter such as chunked-encodings or content-length. However, michael@0: // EOF or an error still require an end time be recorded. michael@0: if (TimingEnabled() && michael@0: mTimings.responseEnd.IsNull() && !mTimings.responseStart.IsNull()) michael@0: mTimings.responseEnd = TimeStamp::Now(); michael@0: michael@0: if (relConn && mConnection) { michael@0: MutexAutoLock lock(mLock); michael@0: NS_RELEASE(mConnection); michael@0: } michael@0: michael@0: // save network statistics in the end of transaction michael@0: SaveNetworkStats(true); michael@0: michael@0: mStatus = reason; michael@0: mTransactionDone = true; // forcibly flag the transaction as complete michael@0: mClosed = true; michael@0: ReleaseBlockingTransaction(); michael@0: michael@0: // release some resources that we no longer need michael@0: mRequestStream = nullptr; michael@0: mReqHeaderBuf.Truncate(); michael@0: mLineBuf.Truncate(); michael@0: if (mChunkedDecoder) { michael@0: delete mChunkedDecoder; michael@0: mChunkedDecoder = nullptr; michael@0: } michael@0: michael@0: // closing this pipe triggers the channel's OnStopRequest method. michael@0: mPipeOut->CloseWithStatus(reason); michael@0: michael@0: MOZ_EVENT_TRACER_DONE(static_cast(this), michael@0: "net::http::transaction"); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans) michael@0: { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: uint32_t michael@0: nsHttpTransaction::PipelineDepth() michael@0: { michael@0: return IsDone() ? 0 : 1; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::SetPipelinePosition(int32_t position) michael@0: { michael@0: mPipelinePosition = position; michael@0: return NS_OK; michael@0: } michael@0: michael@0: int32_t michael@0: nsHttpTransaction::PipelinePosition() michael@0: { michael@0: return mPipelinePosition; michael@0: } michael@0: michael@0: bool // NOTE BASE CLASS michael@0: nsAHttpTransaction::ResponseTimeoutEnabled() const michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: PRIntervalTime // NOTE BASE CLASS michael@0: nsAHttpTransaction::ResponseTimeout() michael@0: { michael@0: return gHttpHandler->ResponseTimeout(); michael@0: } michael@0: michael@0: bool michael@0: nsHttpTransaction::ResponseTimeoutEnabled() const michael@0: { michael@0: return mResponseTimeoutEnabled; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpTransaction michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: nsresult michael@0: nsHttpTransaction::RestartInProgress() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: if ((mRestartCount + 1) >= gHttpHandler->MaxRequestAttempts()) { michael@0: LOG(("nsHttpTransaction::RestartInProgress() " michael@0: "reached max request attempts, failing transaction %p\n", this)); michael@0: return NS_ERROR_NET_RESET; michael@0: } michael@0: michael@0: // Lock RestartInProgress() and TakeResponseHead() against main thread michael@0: MutexAutoLock lock(*nsHttp::GetLock()); michael@0: michael@0: // Don't try and RestartInProgress() things that haven't gotten a response michael@0: // header yet. Those should be handled under the normal restart() path if michael@0: // they are eligible. michael@0: if (!mHaveAllHeaders) michael@0: return NS_ERROR_NET_RESET; michael@0: michael@0: // don't try and restart 0.9 or non 200/Get HTTP/1 michael@0: if (!mRestartInProgressVerifier.IsSetup()) michael@0: return NS_ERROR_NET_RESET; michael@0: michael@0: LOG(("Will restart transaction %p and skip first %lld bytes, " michael@0: "old Content-Length %lld", michael@0: this, mContentRead, mContentLength)); michael@0: michael@0: mRestartInProgressVerifier.SetAlreadyProcessed( michael@0: std::max(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead)); michael@0: michael@0: if (!mResponseHeadTaken && !mForTakeResponseHead) { michael@0: // TakeResponseHeader() has not been called yet and this michael@0: // is the first restart. Store the resp headers exclusively michael@0: // for TakeResponseHead() which is called from the main thread and michael@0: // could happen at any time - so we can't continue to modify those michael@0: // headers (which restarting will effectively do) michael@0: mForTakeResponseHead = mResponseHead; michael@0: mResponseHead = nullptr; michael@0: } michael@0: michael@0: if (mResponseHead) { michael@0: mResponseHead->Reset(); michael@0: } michael@0: michael@0: mContentRead = 0; michael@0: mContentLength = -1; michael@0: delete mChunkedDecoder; michael@0: mChunkedDecoder = nullptr; michael@0: mHaveStatusLine = false; michael@0: mHaveAllHeaders = false; michael@0: mHttpResponseMatched = false; michael@0: mResponseIsComplete = false; michael@0: mDidContentStart = false; michael@0: mNoContent = false; michael@0: mSentData = false; michael@0: mReceivedData = false; michael@0: michael@0: return Restart(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::Restart() michael@0: { michael@0: MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread); michael@0: michael@0: // limit the number of restart attempts - bug 92224 michael@0: if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) { michael@0: LOG(("reached max request attempts, failing transaction @%p\n", this)); michael@0: return NS_ERROR_NET_RESET; michael@0: } michael@0: michael@0: LOG(("restarting transaction @%p\n", this)); michael@0: michael@0: // rewind streams in case we already wrote out the request michael@0: nsCOMPtr seekable = do_QueryInterface(mRequestStream); michael@0: if (seekable) michael@0: seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); michael@0: michael@0: // clear old connection state... michael@0: mSecurityInfo = 0; michael@0: if (mConnection) { michael@0: MutexAutoLock lock(mLock); michael@0: NS_RELEASE(mConnection); michael@0: } michael@0: michael@0: // disable pipelining for the next attempt in case pipelining caused the michael@0: // reset. this is being overly cautious since we don't know if pipelining michael@0: // was the problem here. michael@0: mCaps &= ~NS_HTTP_ALLOW_PIPELINING; michael@0: SetPipelinePosition(0); michael@0: michael@0: return gHttpHandler->InitiateTransaction(this, mPriority); michael@0: } michael@0: michael@0: char * michael@0: nsHttpTransaction::LocateHttpStart(char *buf, uint32_t len, michael@0: bool aAllowPartialMatch) michael@0: { michael@0: MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty()); michael@0: michael@0: static const char HTTPHeader[] = "HTTP/1."; michael@0: static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1; michael@0: static const char HTTP2Header[] = "HTTP/2.0"; michael@0: static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1; michael@0: // ShoutCast ICY is treated as HTTP/1.0 michael@0: static const char ICYHeader[] = "ICY "; michael@0: static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1; michael@0: michael@0: if (aAllowPartialMatch && (len < HTTPHeaderLen)) michael@0: return (PL_strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr; michael@0: michael@0: // mLineBuf can contain partial match from previous search michael@0: if (!mLineBuf.IsEmpty()) { michael@0: MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen); michael@0: int32_t checkChars = std::min(len, HTTPHeaderLen - mLineBuf.Length()); michael@0: if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(), michael@0: checkChars) == 0) { michael@0: mLineBuf.Append(buf, checkChars); michael@0: if (mLineBuf.Length() == HTTPHeaderLen) { michael@0: // We've found whole HTTPHeader sequence. Return pointer at the michael@0: // end of matched sequence since it is stored in mLineBuf. michael@0: return (buf + checkChars); michael@0: } michael@0: // Response matches pattern but is still incomplete. michael@0: return 0; michael@0: } michael@0: // Previous partial match together with new data doesn't match the michael@0: // pattern. Start the search again. michael@0: mLineBuf.Truncate(); michael@0: } michael@0: michael@0: bool firstByte = true; michael@0: while (len > 0) { michael@0: if (PL_strncasecmp(buf, HTTPHeader, std::min(len, HTTPHeaderLen)) == 0) { michael@0: if (len < HTTPHeaderLen) { michael@0: // partial HTTPHeader sequence found michael@0: // save partial match to mLineBuf michael@0: mLineBuf.Assign(buf, len); michael@0: return 0; michael@0: } michael@0: michael@0: // whole HTTPHeader sequence found michael@0: return buf; michael@0: } michael@0: michael@0: // At least "SmarterTools/2.0.3974.16813" generates nonsensical michael@0: // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of michael@0: // it as HTTP/1.1 to be compatible with old versions of ourselves and michael@0: // other browsers michael@0: michael@0: if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen && michael@0: (PL_strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) { michael@0: LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n")); michael@0: return buf; michael@0: } michael@0: michael@0: // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion michael@0: // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted michael@0: // as HTTP/1.0 in nsHttpResponseHead::ParseVersion michael@0: michael@0: if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen && michael@0: (PL_strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) { michael@0: LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n")); michael@0: return buf; michael@0: } michael@0: michael@0: if (!nsCRT::IsAsciiSpace(*buf)) michael@0: firstByte = false; michael@0: buf++; michael@0: len--; michael@0: } michael@0: return 0; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::ParseLine(char *line) michael@0: { michael@0: LOG(("nsHttpTransaction::ParseLine [%s]\n", line)); michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mHaveStatusLine) { michael@0: mResponseHead->ParseStatusLine(line); michael@0: mHaveStatusLine = true; michael@0: // XXX this should probably never happen michael@0: if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) michael@0: mHaveAllHeaders = true; michael@0: } michael@0: else { michael@0: rv = mResponseHead->ParseHeaderLine(line); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::ParseLineSegment(char *segment, uint32_t len) michael@0: { michael@0: NS_PRECONDITION(!mHaveAllHeaders, "already have all headers"); michael@0: michael@0: if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') { michael@0: // trim off the new line char, and if this segment is michael@0: // not a continuation of the previous or if we haven't michael@0: // parsed the status line yet, then parse the contents michael@0: // of mLineBuf. michael@0: mLineBuf.Truncate(mLineBuf.Length() - 1); michael@0: if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) { michael@0: nsresult rv = ParseLine(mLineBuf.BeginWriting()); michael@0: mLineBuf.Truncate(); michael@0: if (NS_FAILED(rv)) { michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, michael@0: nullptr, 0); michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // append segment to mLineBuf... michael@0: mLineBuf.Append(segment, len); michael@0: michael@0: // a line buf with only a new line char signifies the end of headers. michael@0: if (mLineBuf.First() == '\n') { michael@0: mLineBuf.Truncate(); michael@0: // discard this response if it is a 100 continue or other 1xx status. michael@0: uint16_t status = mResponseHead->Status(); michael@0: if ((status != 101) && (status / 100 == 1)) { michael@0: LOG(("ignoring 1xx response\n")); michael@0: mHaveStatusLine = false; michael@0: mHttpResponseMatched = false; michael@0: mConnection->SetLastTransactionExpectedNoContent(true); michael@0: mResponseHead->Reset(); michael@0: return NS_OK; michael@0: } michael@0: mHaveAllHeaders = true; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::ParseHead(char *buf, michael@0: uint32_t count, michael@0: uint32_t *countRead) michael@0: { michael@0: nsresult rv; michael@0: uint32_t len; michael@0: char *eol; michael@0: michael@0: LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count)); michael@0: michael@0: *countRead = 0; michael@0: michael@0: NS_PRECONDITION(!mHaveAllHeaders, "oops"); michael@0: michael@0: // allocate the response head object if necessary michael@0: if (!mResponseHead) { michael@0: mResponseHead = new nsHttpResponseHead(); michael@0: if (!mResponseHead) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: // report that we have a least some of the response michael@0: if (mActivityDistributor && !mReportedStart) { michael@0: mReportedStart = true; michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START, michael@0: PR_Now(), 0, EmptyCString()); michael@0: } michael@0: } michael@0: michael@0: if (!mHttpResponseMatched) { michael@0: // Normally we insist on seeing HTTP/1.x in the first few bytes, michael@0: // but if we are on a persistent connection and the previous transaction michael@0: // was not supposed to have any content then we need to be prepared michael@0: // to skip over a response body that the server may have sent even michael@0: // though it wasn't allowed. michael@0: if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) { michael@0: // tolerate only minor junk before the status line michael@0: mHttpResponseMatched = true; michael@0: char *p = LocateHttpStart(buf, std::min(count, 11), true); michael@0: if (!p) { michael@0: // Treat any 0.9 style response of a put as a failure. michael@0: if (mRequestHead->IsPut()) michael@0: return NS_ERROR_ABORT; michael@0: michael@0: mResponseHead->ParseStatusLine(""); michael@0: mHaveStatusLine = true; michael@0: mHaveAllHeaders = true; michael@0: return NS_OK; michael@0: } michael@0: if (p > buf) { michael@0: // skip over the junk michael@0: mInvalidResponseBytesRead += p - buf; michael@0: *countRead = p - buf; michael@0: buf = p; michael@0: } michael@0: } michael@0: else { michael@0: char *p = LocateHttpStart(buf, count, false); michael@0: if (p) { michael@0: mInvalidResponseBytesRead += p - buf; michael@0: *countRead = p - buf; michael@0: buf = p; michael@0: mHttpResponseMatched = true; michael@0: } else { michael@0: mInvalidResponseBytesRead += count; michael@0: *countRead = count; michael@0: if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) { michael@0: LOG(("nsHttpTransaction::ParseHead() " michael@0: "Cannot find Response Header\n")); michael@0: // cannot go back and call this 0.9 anymore as we michael@0: // have thrown away a lot of the leading junk michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: } michael@0: } michael@0: // otherwise we can assume that we don't have a HTTP/0.9 response. michael@0: michael@0: MOZ_ASSERT (mHttpResponseMatched); michael@0: while ((eol = static_cast(memchr(buf, '\n', count - *countRead))) != nullptr) { michael@0: // found line in range [buf:eol] michael@0: len = eol - buf + 1; michael@0: michael@0: *countRead += len; michael@0: michael@0: // actually, the line is in the range [buf:eol-1] michael@0: if ((eol > buf) && (*(eol-1) == '\r')) michael@0: len--; michael@0: michael@0: buf[len-1] = '\n'; michael@0: rv = ParseLineSegment(buf, len); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: if (mHaveAllHeaders) michael@0: return NS_OK; michael@0: michael@0: // skip over line michael@0: buf = eol + 1; michael@0: michael@0: if (!mHttpResponseMatched) { michael@0: // a 100 class response has caused us to throw away that set of michael@0: // response headers and look for the next response michael@0: return NS_ERROR_NET_INTERRUPT; michael@0: } michael@0: } michael@0: michael@0: // do something about a partial header line michael@0: if (!mHaveAllHeaders && (len = count - *countRead)) { michael@0: *countRead = count; michael@0: // ignore a trailing carriage return, and don't bother calling michael@0: // ParseLineSegment if buf only contains a carriage return. michael@0: if ((buf[len-1] == '\r') && (--len == 0)) michael@0: return NS_OK; michael@0: rv = ParseLineSegment(buf, len); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // called on the socket thread michael@0: nsresult michael@0: nsHttpTransaction::HandleContentStart() michael@0: { michael@0: LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this)); michael@0: michael@0: if (mResponseHead) { michael@0: #if defined(PR_LOGGING) michael@0: if (LOG3_ENABLED()) { michael@0: LOG3(("http response [\n")); michael@0: nsAutoCString headers; michael@0: mResponseHead->Flatten(headers, false); michael@0: LogHeaders(headers.get()); michael@0: LOG3(("]\n")); michael@0: } michael@0: #endif michael@0: // Save http version, mResponseHead isn't available anymore after michael@0: // TakeResponseHead() is called michael@0: mHttpVersion = mResponseHead->Version(); michael@0: michael@0: // notify the connection, give it a chance to cause a reset. michael@0: bool reset = false; michael@0: if (!mRestartInProgressVerifier.IsSetup()) michael@0: mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset); michael@0: michael@0: // looks like we should ignore this response, resetting... michael@0: if (reset) { michael@0: LOG(("resetting transaction's response head\n")); michael@0: mHaveAllHeaders = false; michael@0: mHaveStatusLine = false; michael@0: mReceivedData = false; michael@0: mSentData = false; michael@0: mHttpResponseMatched = false; michael@0: mResponseHead->Reset(); michael@0: // wait to be called again... michael@0: return NS_OK; michael@0: } michael@0: michael@0: // check if this is a no-content response michael@0: switch (mResponseHead->Status()) { michael@0: case 101: michael@0: mPreserveStream = true; // fall through to other no content michael@0: case 204: michael@0: case 205: michael@0: case 304: michael@0: mNoContent = true; michael@0: LOG(("this response should not contain a body.\n")); michael@0: break; michael@0: } michael@0: michael@0: if (mResponseHead->Status() == 200 && michael@0: mConnection->IsProxyConnectInProgress()) { michael@0: // successful CONNECTs do not have response bodies michael@0: mNoContent = true; michael@0: } michael@0: mConnection->SetLastTransactionExpectedNoContent(mNoContent); michael@0: if (mInvalidResponseBytesRead) michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming, michael@0: nullptr, mClassification); michael@0: michael@0: if (mNoContent) michael@0: mContentLength = 0; michael@0: else { michael@0: // grab the content-length from the response headers michael@0: mContentLength = mResponseHead->ContentLength(); michael@0: michael@0: if ((mClassification != CLASS_SOLO) && michael@0: (mContentLength > mMaxPipelineObjectSize)) michael@0: CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge); michael@0: michael@0: // handle chunked encoding here, so we'll know immediately when michael@0: // we're done with the socket. please note that _all_ other michael@0: // decoding is done when the channel receives the content data michael@0: // so as not to block the socket transport thread too much. michael@0: // ignore chunked responses from HTTP/1.0 servers and proxies. michael@0: if (mResponseHead->Version() >= NS_HTTP_VERSION_1_1 && michael@0: mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) { michael@0: // we only support the "chunked" transfer encoding right now. michael@0: mChunkedDecoder = new nsHttpChunkedDecoder(); michael@0: if (!mChunkedDecoder) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: LOG(("chunked decoder created\n")); michael@0: // Ignore server specified Content-Length. michael@0: mContentLength = -1; michael@0: } michael@0: #if defined(PR_LOGGING) michael@0: else if (mContentLength == int64_t(-1)) michael@0: LOG(("waiting for the server to close the connection.\n")); michael@0: #endif michael@0: } michael@0: if (mRestartInProgressVerifier.IsSetup() && michael@0: !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) { michael@0: LOG(("Restart in progress subsequent transaction failed to match")); michael@0: return NS_ERROR_ABORT; michael@0: } michael@0: } michael@0: michael@0: mDidContentStart = true; michael@0: michael@0: // The verifier only initializes itself once (from the first iteration of michael@0: // a transaction that gets far enough to have response headers) michael@0: if (mRequestHead->IsGet()) michael@0: mRestartInProgressVerifier.Set(mContentLength, mResponseHead); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // called on the socket thread michael@0: nsresult michael@0: nsHttpTransaction::HandleContent(char *buf, michael@0: uint32_t count, michael@0: uint32_t *contentRead, michael@0: uint32_t *contentRemaining) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count)); michael@0: michael@0: *contentRead = 0; michael@0: *contentRemaining = 0; michael@0: michael@0: MOZ_ASSERT(mConnection); michael@0: michael@0: if (!mDidContentStart) { michael@0: rv = HandleContentStart(); michael@0: if (NS_FAILED(rv)) return rv; michael@0: // Do not write content to the pipe if we haven't started streaming yet michael@0: if (!mDidContentStart) michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mChunkedDecoder) { michael@0: // give the buf over to the chunked decoder so it can reformat the michael@0: // data and tell us how much is really there. michael@0: rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, contentRemaining); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: else if (mContentLength >= int64_t(0)) { michael@0: // HTTP/1.0 servers have been known to send erroneous Content-Length michael@0: // headers. So, unless the connection is persistent, we must make michael@0: // allowances for a possibly invalid Content-Length header. Thus, if michael@0: // NOT persistent, we simply accept everything in |buf|. michael@0: if (mConnection->IsPersistent() || mPreserveStream || michael@0: mHttpVersion >= NS_HTTP_VERSION_1_1) { michael@0: int64_t remaining = mContentLength - mContentRead; michael@0: *contentRead = uint32_t(std::min(count, remaining)); michael@0: *contentRemaining = count - *contentRead; michael@0: } michael@0: else { michael@0: *contentRead = count; michael@0: // mContentLength might need to be increased... michael@0: int64_t position = mContentRead + int64_t(count); michael@0: if (position > mContentLength) { michael@0: mContentLength = position; michael@0: //mResponseHead->SetContentLength(mContentLength); michael@0: } michael@0: } michael@0: } michael@0: else { michael@0: // when we are just waiting for the server to close the connection... michael@0: // (no explicit content-length given) michael@0: *contentRead = count; michael@0: } michael@0: michael@0: int64_t toReadBeforeRestart = michael@0: mRestartInProgressVerifier.ToReadBeforeRestart(); michael@0: michael@0: if (toReadBeforeRestart && *contentRead) { michael@0: uint32_t ignore = michael@0: static_cast(std::min(toReadBeforeRestart, UINT32_MAX)); michael@0: ignore = std::min(*contentRead, ignore); michael@0: LOG(("Due To Restart ignoring %d of remaining %ld", michael@0: ignore, toReadBeforeRestart)); michael@0: *contentRead -= ignore; michael@0: mContentRead += ignore; michael@0: mRestartInProgressVerifier.HaveReadBeforeRestart(ignore); michael@0: memmove(buf, buf + ignore, *contentRead + *contentRemaining); michael@0: } michael@0: michael@0: if (*contentRead) { michael@0: // update count of content bytes read and report progress... michael@0: mContentRead += *contentRead; michael@0: /* when uncommenting, take care of 64-bit integers w/ std::max... michael@0: if (mProgressSink) michael@0: mProgressSink->OnProgress(nullptr, nullptr, mContentRead, std::max(0, mContentLength)); michael@0: */ michael@0: } michael@0: michael@0: LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%lld mContentLength=%lld]\n", michael@0: this, count, *contentRead, mContentRead, mContentLength)); michael@0: michael@0: // Check the size of chunked responses. If we exceed the max pipeline size michael@0: // for this response reschedule the pipeline michael@0: if ((mClassification != CLASS_SOLO) && michael@0: mChunkedDecoder && michael@0: ((mContentRead + mChunkedDecoder->GetChunkRemaining()) > michael@0: mMaxPipelineObjectSize)) { michael@0: CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge); michael@0: } michael@0: michael@0: // check for end-of-file michael@0: if ((mContentRead == mContentLength) || michael@0: (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) { michael@0: // the transaction is done with a complete response. michael@0: mTransactionDone = true; michael@0: mResponseIsComplete = true; michael@0: ReleaseBlockingTransaction(); michael@0: michael@0: if (TimingEnabled()) michael@0: mTimings.responseEnd = TimeStamp::Now(); michael@0: michael@0: // report the entire response has arrived michael@0: if (mActivityDistributor) michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE, michael@0: PR_Now(), michael@0: static_cast(mContentRead), michael@0: EmptyCString()); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsHttpTransaction::ProcessData(char *buf, uint32_t count, uint32_t *countRead) michael@0: { michael@0: nsresult rv; michael@0: michael@0: LOG(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count)); michael@0: michael@0: *countRead = 0; michael@0: michael@0: // we may not have read all of the headers yet... michael@0: if (!mHaveAllHeaders) { michael@0: uint32_t bytesConsumed = 0; michael@0: michael@0: do { michael@0: uint32_t localBytesConsumed = 0; michael@0: char *localBuf = buf + bytesConsumed; michael@0: uint32_t localCount = count - bytesConsumed; michael@0: michael@0: rv = ParseHead(localBuf, localCount, &localBytesConsumed); michael@0: if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT) michael@0: return rv; michael@0: bytesConsumed += localBytesConsumed; michael@0: } while (rv == NS_ERROR_NET_INTERRUPT); michael@0: michael@0: count -= bytesConsumed; michael@0: michael@0: // if buf has some content in it, shift bytes to top of buf. michael@0: if (count && bytesConsumed) michael@0: memmove(buf, buf + bytesConsumed, count); michael@0: michael@0: // report the completed response header michael@0: if (mActivityDistributor && mResponseHead && mHaveAllHeaders && michael@0: !mReportedResponseHeader) { michael@0: mReportedResponseHeader = true; michael@0: nsAutoCString completeResponseHeaders; michael@0: mResponseHead->Flatten(completeResponseHeaders, false); michael@0: completeResponseHeaders.AppendLiteral("\r\n"); michael@0: mActivityDistributor->ObserveActivity( michael@0: mChannel, michael@0: NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION, michael@0: NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER, michael@0: PR_Now(), 0, michael@0: completeResponseHeaders); michael@0: } michael@0: } michael@0: michael@0: // even though count may be 0, we still want to call HandleContent michael@0: // so it can complete the transaction if this is a "no-content" response. michael@0: if (mHaveAllHeaders) { michael@0: uint32_t countRemaining = 0; michael@0: // michael@0: // buf layout: michael@0: // michael@0: // +--------------------------------------+----------------+-----+ michael@0: // | countRead | countRemaining | | michael@0: // +--------------------------------------+----------------+-----+ michael@0: // michael@0: // count : bytes read from the socket michael@0: // countRead : bytes corresponding to this transaction michael@0: // countRemaining : bytes corresponding to next pipelined transaction michael@0: // michael@0: // NOTE: michael@0: // count > countRead + countRemaining <==> chunked transfer encoding michael@0: // michael@0: rv = HandleContent(buf, count, countRead, &countRemaining); michael@0: if (NS_FAILED(rv)) return rv; michael@0: // we may have read more than our share, in which case we must give michael@0: // the excess bytes back to the connection michael@0: if (mResponseIsComplete && countRemaining) { michael@0: MOZ_ASSERT(mConnection); michael@0: mConnection->PushBack(buf + *countRead, countRemaining); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::CancelPipeline(uint32_t reason) michael@0: { michael@0: // reason is casted through a uint to avoid compiler header deps michael@0: gHttpHandler->ConnMgr()->PipelineFeedbackInfo( michael@0: mConnInfo, michael@0: static_cast(reason), michael@0: nullptr, mClassification); michael@0: michael@0: mConnection->CancelPipeline(NS_ERROR_ABORT); michael@0: michael@0: // Avoid pipelining this transaction on restart by classifying it as solo. michael@0: // This also prevents BadUnexpectedLarge from being reported more michael@0: // than one time per transaction. michael@0: mClassification = CLASS_SOLO; michael@0: } michael@0: michael@0: // Called when the transaction marked for blocking is associated with a connection michael@0: // (i.e. added to a spdy session, an idle http connection, or placed into michael@0: // a http pipeline). It is safe to call this multiple times with it only michael@0: // having an effect once. michael@0: void michael@0: nsHttpTransaction::DispatchedAsBlocking() michael@0: { michael@0: if (mDispatchedAsBlocking) michael@0: return; michael@0: michael@0: LOG(("nsHttpTransaction %p dispatched as blocking\n", this)); michael@0: michael@0: if (!mLoadGroupCI) michael@0: return; michael@0: michael@0: LOG(("nsHttpTransaction adding blocking channel %p from " michael@0: "loadgroup %p\n", this, mLoadGroupCI.get())); michael@0: michael@0: mLoadGroupCI->AddBlockingTransaction(); michael@0: mDispatchedAsBlocking = true; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::RemoveDispatchedAsBlocking() michael@0: { michael@0: if (!mLoadGroupCI || !mDispatchedAsBlocking) michael@0: return; michael@0: michael@0: uint32_t blockers = 0; michael@0: nsresult rv = mLoadGroupCI->RemoveBlockingTransaction(&blockers); michael@0: michael@0: LOG(("nsHttpTransaction removing blocking channel %p from " michael@0: "loadgroup %p. %d blockers remain.\n", this, michael@0: mLoadGroupCI.get(), blockers)); michael@0: michael@0: if (NS_SUCCEEDED(rv) && !blockers) { michael@0: LOG(("nsHttpTransaction %p triggering release of blocked channels.\n", michael@0: this)); michael@0: gHttpHandler->ConnMgr()->ProcessPendingQ(); michael@0: } michael@0: michael@0: mDispatchedAsBlocking = false; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::ReleaseBlockingTransaction() michael@0: { michael@0: RemoveDispatchedAsBlocking(); michael@0: mLoadGroupCI = nullptr; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpTransaction deletion event michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: class nsDeleteHttpTransaction : public nsRunnable { michael@0: public: michael@0: nsDeleteHttpTransaction(nsHttpTransaction *trans) michael@0: : mTrans(trans) michael@0: {} michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: delete mTrans; michael@0: return NS_OK; michael@0: } michael@0: private: michael@0: nsHttpTransaction *mTrans; michael@0: }; michael@0: michael@0: void michael@0: nsHttpTransaction::DeleteSelfOnConsumerThread() michael@0: { michael@0: LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this)); michael@0: michael@0: bool val; michael@0: if (!mConsumerTarget || michael@0: (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) { michael@0: delete this; michael@0: } else { michael@0: LOG(("proxying delete to consumer thread...\n")); michael@0: nsCOMPtr event = new nsDeleteHttpTransaction(this); michael@0: if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL))) michael@0: NS_WARNING("failed to dispatch nsHttpDeleteTransaction event"); michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsHttpTransaction::TryToRunPacedRequest() michael@0: { michael@0: if (mSubmittedRatePacing) michael@0: return mPassedRatePacing; michael@0: michael@0: mSubmittedRatePacing = true; michael@0: mSynchronousRatePaceRequest = true; michael@0: gHttpHandler->SubmitPacedRequest(this, getter_AddRefs(mTokenBucketCancel)); michael@0: mSynchronousRatePaceRequest = false; michael@0: return mPassedRatePacing; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::OnTokenBucketAdmitted() michael@0: { michael@0: mPassedRatePacing = true; michael@0: mTokenBucketCancel = nullptr; michael@0: michael@0: if (!mSynchronousRatePaceRequest) michael@0: gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo); michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::CancelPacing(nsresult reason) michael@0: { michael@0: if (mTokenBucketCancel) { michael@0: mTokenBucketCancel->Cancel(reason); michael@0: mTokenBucketCancel = nullptr; michael@0: } michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpTransaction::nsISupports michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: NS_IMPL_ADDREF(nsHttpTransaction) michael@0: michael@0: NS_IMETHODIMP_(MozExternalRefCountType) michael@0: nsHttpTransaction::Release() michael@0: { michael@0: nsrefcnt count; michael@0: NS_PRECONDITION(0 != mRefCnt, "dup release"); michael@0: count = --mRefCnt; michael@0: NS_LOG_RELEASE(this, count, "nsHttpTransaction"); michael@0: if (0 == count) { michael@0: mRefCnt = 1; /* stablize */ michael@0: // it is essential that the transaction be destroyed on the consumer michael@0: // thread (we could be holding the last reference to our consumer). michael@0: DeleteSelfOnConsumerThread(); michael@0: return 0; michael@0: } michael@0: return count; michael@0: } michael@0: michael@0: NS_IMPL_QUERY_INTERFACE(nsHttpTransaction, michael@0: nsIInputStreamCallback, michael@0: nsIOutputStreamCallback) michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpTransaction::nsIInputStreamCallback michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // called on the socket thread michael@0: NS_IMETHODIMP michael@0: nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out) michael@0: { michael@0: if (mConnection) { michael@0: mConnection->TransactionHasDataToWrite(this); michael@0: nsresult rv = mConnection->ResumeSend(); michael@0: if (NS_FAILED(rv)) michael@0: NS_ERROR("ResumeSend failed"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: //----------------------------------------------------------------------------- michael@0: // nsHttpTransaction::nsIOutputStreamCallback michael@0: //----------------------------------------------------------------------------- michael@0: michael@0: // called on the socket thread michael@0: NS_IMETHODIMP michael@0: nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out) michael@0: { michael@0: if (mConnection) { michael@0: nsresult rv = mConnection->ResumeRecv(); michael@0: if (NS_FAILED(rv)) michael@0: NS_ERROR("ResumeRecv failed"); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // nsHttpTransaction::RestartVerifier michael@0: michael@0: static bool michael@0: matchOld(nsHttpResponseHead *newHead, nsCString &old, michael@0: nsHttpAtom headerAtom) michael@0: { michael@0: const char *val; michael@0: michael@0: val = newHead->PeekHeader(headerAtom); michael@0: if (val && old.IsEmpty()) michael@0: return false; michael@0: if (!val && !old.IsEmpty()) michael@0: return false; michael@0: if (val && !old.Equals(val)) michael@0: return false; michael@0: return true; michael@0: } michael@0: michael@0: bool michael@0: nsHttpTransaction::RestartVerifier::Verify(int64_t contentLength, michael@0: nsHttpResponseHead *newHead) michael@0: { michael@0: if (mContentLength != contentLength) michael@0: return false; michael@0: michael@0: if (newHead->Status() != 200) michael@0: return false; michael@0: michael@0: if (!matchOld(newHead, mContentRange, nsHttp::Content_Range)) michael@0: return false; michael@0: michael@0: if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified)) michael@0: return false; michael@0: michael@0: if (!matchOld(newHead, mETag, nsHttp::ETag)) michael@0: return false; michael@0: michael@0: if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding)) michael@0: return false; michael@0: michael@0: if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding)) michael@0: return false; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsHttpTransaction::RestartVerifier::Set(int64_t contentLength, michael@0: nsHttpResponseHead *head) michael@0: { michael@0: if (mSetup) michael@0: return; michael@0: michael@0: // If mSetup does not transition to true RestartInPogress() is later michael@0: // forbidden michael@0: michael@0: // Only RestartInProgress with 200 response code michael@0: if (head->Status() != 200) michael@0: return; michael@0: michael@0: mContentLength = contentLength; michael@0: michael@0: if (head) { michael@0: const char *val; michael@0: val = head->PeekHeader(nsHttp::ETag); michael@0: if (val) michael@0: mETag.Assign(val); michael@0: val = head->PeekHeader(nsHttp::Last_Modified); michael@0: if (val) michael@0: mLastModified.Assign(val); michael@0: val = head->PeekHeader(nsHttp::Content_Range); michael@0: if (val) michael@0: mContentRange.Assign(val); michael@0: val = head->PeekHeader(nsHttp::Content_Encoding); michael@0: if (val) michael@0: mContentEncoding.Assign(val); michael@0: val = head->PeekHeader(nsHttp::Transfer_Encoding); michael@0: if (val) michael@0: mTransferEncoding.Assign(val); michael@0: michael@0: // We can only restart with any confidence if we have a stored etag or michael@0: // last-modified header michael@0: if (mETag.IsEmpty() && mLastModified.IsEmpty()) michael@0: return; michael@0: michael@0: mSetup = true; michael@0: } michael@0: } michael@0: michael@0: } // namespace mozilla::net michael@0: } // namespace mozilla