netwerk/protocol/http/nsHttpTransaction.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
michael@0 2 /* vim:set ts=4 sw=4 sts=4 et cin: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 // HttpLog.h should generally be included first
michael@0 8 #include "HttpLog.h"
michael@0 9
michael@0 10 #include "base/basictypes.h"
michael@0 11
michael@0 12 #include "nsHttpHandler.h"
michael@0 13 #include "nsHttpTransaction.h"
michael@0 14 #include "nsHttpRequestHead.h"
michael@0 15 #include "nsHttpResponseHead.h"
michael@0 16 #include "nsHttpChunkedDecoder.h"
michael@0 17 #include "nsTransportUtils.h"
michael@0 18 #include "nsNetUtil.h"
michael@0 19 #include "nsCRT.h"
michael@0 20
michael@0 21 #include "nsISeekableStream.h"
michael@0 22 #include "nsMultiplexInputStream.h"
michael@0 23 #include "nsStringStream.h"
michael@0 24 #include "mozilla/VisualEventTracer.h"
michael@0 25
michael@0 26 #include "nsComponentManagerUtils.h" // do_CreateInstance
michael@0 27 #include "nsServiceManagerUtils.h" // do_GetService
michael@0 28 #include "nsIHttpActivityObserver.h"
michael@0 29 #include "nsSocketTransportService2.h"
michael@0 30 #include "nsICancelable.h"
michael@0 31 #include "nsIEventTarget.h"
michael@0 32 #include "nsIHttpChannelInternal.h"
michael@0 33 #include "nsIInputStream.h"
michael@0 34 #include "nsITransport.h"
michael@0 35 #include "nsIOService.h"
michael@0 36 #include <algorithm>
michael@0 37
michael@0 38 #ifdef MOZ_WIDGET_GONK
michael@0 39 #include "NetStatistics.h"
michael@0 40 #endif
michael@0 41
michael@0 42 //-----------------------------------------------------------------------------
michael@0 43
michael@0 44 #ifdef DEBUG
michael@0 45 // defined by the socket transport service while active
michael@0 46 extern PRThread *gSocketThread;
michael@0 47 #endif
michael@0 48
michael@0 49 //-----------------------------------------------------------------------------
michael@0 50
michael@0 51 static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
michael@0 52
michael@0 53 // Place a limit on how much non-compliant HTTP can be skipped while
michael@0 54 // looking for a response header
michael@0 55 #define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
michael@0 56
michael@0 57 using namespace mozilla::net;
michael@0 58
michael@0 59 namespace mozilla {
michael@0 60 namespace net {
michael@0 61
michael@0 62 //-----------------------------------------------------------------------------
michael@0 63 // helpers
michael@0 64 //-----------------------------------------------------------------------------
michael@0 65
michael@0 66 #if defined(PR_LOGGING)
michael@0 67 static void
michael@0 68 LogHeaders(const char *lineStart)
michael@0 69 {
michael@0 70 nsAutoCString buf;
michael@0 71 char *endOfLine;
michael@0 72 while ((endOfLine = PL_strstr(lineStart, "\r\n"))) {
michael@0 73 buf.Assign(lineStart, endOfLine - lineStart);
michael@0 74 if (PL_strcasestr(buf.get(), "authorization: ") ||
michael@0 75 PL_strcasestr(buf.get(), "proxy-authorization: ")) {
michael@0 76 char *p = PL_strchr(PL_strchr(buf.get(), ' ') + 1, ' ');
michael@0 77 while (p && *++p)
michael@0 78 *p = '*';
michael@0 79 }
michael@0 80 LOG3((" %s\n", buf.get()));
michael@0 81 lineStart = endOfLine + 2;
michael@0 82 }
michael@0 83 }
michael@0 84 #endif
michael@0 85
michael@0 86 //-----------------------------------------------------------------------------
michael@0 87 // nsHttpTransaction <public>
michael@0 88 //-----------------------------------------------------------------------------
michael@0 89
michael@0 90 nsHttpTransaction::nsHttpTransaction()
michael@0 91 : mLock("transaction lock")
michael@0 92 , mRequestSize(0)
michael@0 93 , mConnection(nullptr)
michael@0 94 , mConnInfo(nullptr)
michael@0 95 , mRequestHead(nullptr)
michael@0 96 , mResponseHead(nullptr)
michael@0 97 , mContentLength(-1)
michael@0 98 , mContentRead(0)
michael@0 99 , mInvalidResponseBytesRead(0)
michael@0 100 , mChunkedDecoder(nullptr)
michael@0 101 , mStatus(NS_OK)
michael@0 102 , mPriority(0)
michael@0 103 , mRestartCount(0)
michael@0 104 , mCaps(0)
michael@0 105 , mCapsToClear(0)
michael@0 106 , mClassification(CLASS_GENERAL)
michael@0 107 , mPipelinePosition(0)
michael@0 108 , mHttpVersion(NS_HTTP_VERSION_UNKNOWN)
michael@0 109 , mClosed(false)
michael@0 110 , mConnected(false)
michael@0 111 , mHaveStatusLine(false)
michael@0 112 , mHaveAllHeaders(false)
michael@0 113 , mTransactionDone(false)
michael@0 114 , mResponseIsComplete(false)
michael@0 115 , mDidContentStart(false)
michael@0 116 , mNoContent(false)
michael@0 117 , mSentData(false)
michael@0 118 , mReceivedData(false)
michael@0 119 , mStatusEventPending(false)
michael@0 120 , mHasRequestBody(false)
michael@0 121 , mProxyConnectFailed(false)
michael@0 122 , mHttpResponseMatched(false)
michael@0 123 , mPreserveStream(false)
michael@0 124 , mDispatchedAsBlocking(false)
michael@0 125 , mResponseTimeoutEnabled(true)
michael@0 126 , mReportedStart(false)
michael@0 127 , mReportedResponseHeader(false)
michael@0 128 , mForTakeResponseHead(nullptr)
michael@0 129 , mResponseHeadTaken(false)
michael@0 130 , mSubmittedRatePacing(false)
michael@0 131 , mPassedRatePacing(false)
michael@0 132 , mSynchronousRatePaceRequest(false)
michael@0 133 , mCountRecv(0)
michael@0 134 , mCountSent(0)
michael@0 135 , mAppId(NECKO_NO_APP_ID)
michael@0 136 {
michael@0 137 LOG(("Creating nsHttpTransaction @%p\n", this));
michael@0 138 gHttpHandler->GetMaxPipelineObjectSize(&mMaxPipelineObjectSize);
michael@0 139 }
michael@0 140
michael@0 141 nsHttpTransaction::~nsHttpTransaction()
michael@0 142 {
michael@0 143 LOG(("Destroying nsHttpTransaction @%p\n", this));
michael@0 144
michael@0 145 if (mTokenBucketCancel) {
michael@0 146 mTokenBucketCancel->Cancel(NS_ERROR_ABORT);
michael@0 147 mTokenBucketCancel = nullptr;
michael@0 148 }
michael@0 149
michael@0 150 // Force the callbacks to be released right now
michael@0 151 mCallbacks = nullptr;
michael@0 152
michael@0 153 NS_IF_RELEASE(mConnection);
michael@0 154 NS_IF_RELEASE(mConnInfo);
michael@0 155
michael@0 156 delete mResponseHead;
michael@0 157 delete mForTakeResponseHead;
michael@0 158 delete mChunkedDecoder;
michael@0 159 ReleaseBlockingTransaction();
michael@0 160 }
michael@0 161
michael@0 162 nsHttpTransaction::Classifier
michael@0 163 nsHttpTransaction::Classify()
michael@0 164 {
michael@0 165 if (!(mCaps & NS_HTTP_ALLOW_PIPELINING))
michael@0 166 return (mClassification = CLASS_SOLO);
michael@0 167
michael@0 168 if (mRequestHead->PeekHeader(nsHttp::If_Modified_Since) ||
michael@0 169 mRequestHead->PeekHeader(nsHttp::If_None_Match))
michael@0 170 return (mClassification = CLASS_REVALIDATION);
michael@0 171
michael@0 172 const char *accept = mRequestHead->PeekHeader(nsHttp::Accept);
michael@0 173 if (accept && !PL_strncmp(accept, "image/", 6))
michael@0 174 return (mClassification = CLASS_IMAGE);
michael@0 175
michael@0 176 if (accept && !PL_strncmp(accept, "text/css", 8))
michael@0 177 return (mClassification = CLASS_SCRIPT);
michael@0 178
michael@0 179 mClassification = CLASS_GENERAL;
michael@0 180
michael@0 181 int32_t queryPos = mRequestHead->RequestURI().FindChar('?');
michael@0 182 if (queryPos == kNotFound) {
michael@0 183 if (StringEndsWith(mRequestHead->RequestURI(),
michael@0 184 NS_LITERAL_CSTRING(".js")))
michael@0 185 mClassification = CLASS_SCRIPT;
michael@0 186 }
michael@0 187 else if (queryPos >= 3 &&
michael@0 188 Substring(mRequestHead->RequestURI(), queryPos - 3, 3).
michael@0 189 EqualsLiteral(".js")) {
michael@0 190 mClassification = CLASS_SCRIPT;
michael@0 191 }
michael@0 192
michael@0 193 return mClassification;
michael@0 194 }
michael@0 195
michael@0 196 nsresult
michael@0 197 nsHttpTransaction::Init(uint32_t caps,
michael@0 198 nsHttpConnectionInfo *cinfo,
michael@0 199 nsHttpRequestHead *requestHead,
michael@0 200 nsIInputStream *requestBody,
michael@0 201 bool requestBodyHasHeaders,
michael@0 202 nsIEventTarget *target,
michael@0 203 nsIInterfaceRequestor *callbacks,
michael@0 204 nsITransportEventSink *eventsink,
michael@0 205 nsIAsyncInputStream **responseBody)
michael@0 206 {
michael@0 207 MOZ_EVENT_TRACER_COMPOUND_NAME(static_cast<nsAHttpTransaction*>(this),
michael@0 208 requestHead->PeekHeader(nsHttp::Host),
michael@0 209 requestHead->RequestURI().BeginReading());
michael@0 210
michael@0 211 MOZ_EVENT_TRACER_WAIT(static_cast<nsAHttpTransaction*>(this),
michael@0 212 "net::http::transaction");
michael@0 213 nsresult rv;
michael@0 214
michael@0 215 LOG(("nsHttpTransaction::Init [this=%p caps=%x]\n", this, caps));
michael@0 216
michael@0 217 MOZ_ASSERT(cinfo);
michael@0 218 MOZ_ASSERT(requestHead);
michael@0 219 MOZ_ASSERT(target);
michael@0 220
michael@0 221 mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
michael@0 222 if (NS_FAILED(rv)) return rv;
michael@0 223
michael@0 224 bool activityDistributorActive;
michael@0 225 rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
michael@0 226 if (NS_SUCCEEDED(rv) && activityDistributorActive) {
michael@0 227 // there are some observers registered at activity distributor, gather
michael@0 228 // nsISupports for the channel that called Init()
michael@0 229 mChannel = do_QueryInterface(eventsink);
michael@0 230 LOG(("nsHttpTransaction::Init() " \
michael@0 231 "mActivityDistributor is active " \
michael@0 232 "this=%p", this));
michael@0 233 } else {
michael@0 234 // there is no observer, so don't use it
michael@0 235 activityDistributorActive = false;
michael@0 236 mActivityDistributor = nullptr;
michael@0 237 }
michael@0 238
michael@0 239 nsCOMPtr<nsIChannel> channel = do_QueryInterface(eventsink);
michael@0 240 if (channel) {
michael@0 241 bool isInBrowser;
michael@0 242 NS_GetAppInfo(channel, &mAppId, &isInBrowser);
michael@0 243 }
michael@0 244
michael@0 245 #ifdef MOZ_WIDGET_GONK
michael@0 246 if (mAppId != NECKO_NO_APP_ID) {
michael@0 247 nsCOMPtr<nsINetworkInterface> activeNetwork;
michael@0 248 GetActiveNetworkInterface(activeNetwork);
michael@0 249 mActiveNetwork =
michael@0 250 new nsMainThreadPtrHolder<nsINetworkInterface>(activeNetwork);
michael@0 251 }
michael@0 252 #endif
michael@0 253
michael@0 254 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
michael@0 255 do_QueryInterface(eventsink);
michael@0 256 if (httpChannelInternal) {
michael@0 257 rv = httpChannelInternal->GetResponseTimeoutEnabled(
michael@0 258 &mResponseTimeoutEnabled);
michael@0 259 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 260 return rv;
michael@0 261 }
michael@0 262 }
michael@0 263
michael@0 264 // create transport event sink proxy. it coalesces all events if and only
michael@0 265 // if the activity observer is not active. when the observer is active
michael@0 266 // we need not to coalesce any events to get all expected notifications
michael@0 267 // of the transaction state, necessary for correct debugging and logging.
michael@0 268 rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink),
michael@0 269 eventsink, target,
michael@0 270 !activityDistributorActive);
michael@0 271 if (NS_FAILED(rv)) return rv;
michael@0 272
michael@0 273 NS_ADDREF(mConnInfo = cinfo);
michael@0 274 mCallbacks = callbacks;
michael@0 275 mConsumerTarget = target;
michael@0 276 mCaps = caps;
michael@0 277
michael@0 278 if (requestHead->IsHead()) {
michael@0 279 mNoContent = true;
michael@0 280 }
michael@0 281
michael@0 282 // Make sure that there is "Content-Length: 0" header in the requestHead
michael@0 283 // in case of POST and PUT methods when there is no requestBody and
michael@0 284 // requestHead doesn't contain "Transfer-Encoding" header.
michael@0 285 //
michael@0 286 // RFC1945 section 7.2.2:
michael@0 287 // HTTP/1.0 requests containing an entity body must include a valid
michael@0 288 // Content-Length header field.
michael@0 289 //
michael@0 290 // RFC2616 section 4.4:
michael@0 291 // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
michael@0 292 // containing a message-body MUST include a valid Content-Length header
michael@0 293 // field unless the server is known to be HTTP/1.1 compliant.
michael@0 294 if ((requestHead->IsPost() || requestHead->IsPut()) &&
michael@0 295 !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) {
michael@0 296 requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
michael@0 297 }
michael@0 298
michael@0 299 // grab a weak reference to the request head
michael@0 300 mRequestHead = requestHead;
michael@0 301
michael@0 302 // make sure we eliminate any proxy specific headers from
michael@0 303 // the request if we are using CONNECT
michael@0 304 bool pruneProxyHeaders = cinfo->UsingConnect();
michael@0 305
michael@0 306 mReqHeaderBuf.Truncate();
michael@0 307 requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders);
michael@0 308
michael@0 309 #if defined(PR_LOGGING)
michael@0 310 if (LOG3_ENABLED()) {
michael@0 311 LOG3(("http request [\n"));
michael@0 312 LogHeaders(mReqHeaderBuf.get());
michael@0 313 LOG3(("]\n"));
michael@0 314 }
michael@0 315 #endif
michael@0 316
michael@0 317 // If the request body does not include headers or if there is no request
michael@0 318 // body, then we must add the header/body separator manually.
michael@0 319 if (!requestBodyHasHeaders || !requestBody)
michael@0 320 mReqHeaderBuf.AppendLiteral("\r\n");
michael@0 321
michael@0 322 // report the request header
michael@0 323 if (mActivityDistributor)
michael@0 324 mActivityDistributor->ObserveActivity(
michael@0 325 mChannel,
michael@0 326 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 327 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,
michael@0 328 PR_Now(), 0,
michael@0 329 mReqHeaderBuf);
michael@0 330
michael@0 331 // Create a string stream for the request header buf (the stream holds
michael@0 332 // a non-owning reference to the request header data, so we MUST keep
michael@0 333 // mReqHeaderBuf around).
michael@0 334 nsCOMPtr<nsIInputStream> headers;
michael@0 335 rv = NS_NewByteInputStream(getter_AddRefs(headers),
michael@0 336 mReqHeaderBuf.get(),
michael@0 337 mReqHeaderBuf.Length());
michael@0 338 if (NS_FAILED(rv)) return rv;
michael@0 339
michael@0 340 if (requestBody) {
michael@0 341 mHasRequestBody = true;
michael@0 342
michael@0 343 // wrap the headers and request body in a multiplexed input stream.
michael@0 344 nsCOMPtr<nsIMultiplexInputStream> multi =
michael@0 345 do_CreateInstance(kMultiplexInputStream, &rv);
michael@0 346 if (NS_FAILED(rv)) return rv;
michael@0 347
michael@0 348 rv = multi->AppendStream(headers);
michael@0 349 if (NS_FAILED(rv)) return rv;
michael@0 350
michael@0 351 rv = multi->AppendStream(requestBody);
michael@0 352 if (NS_FAILED(rv)) return rv;
michael@0 353
michael@0 354 // wrap the multiplexed input stream with a buffered input stream, so
michael@0 355 // that we write data in the largest chunks possible. this is actually
michael@0 356 // necessary to workaround some common server bugs (see bug 137155).
michael@0 357 rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi,
michael@0 358 nsIOService::gDefaultSegmentSize);
michael@0 359 if (NS_FAILED(rv)) return rv;
michael@0 360 }
michael@0 361 else
michael@0 362 mRequestStream = headers;
michael@0 363
michael@0 364 rv = mRequestStream->Available(&mRequestSize);
michael@0 365 if (NS_FAILED(rv)) return rv;
michael@0 366
michael@0 367 // create pipe for response stream
michael@0 368 rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
michael@0 369 getter_AddRefs(mPipeOut),
michael@0 370 true, true,
michael@0 371 nsIOService::gDefaultSegmentSize,
michael@0 372 nsIOService::gDefaultSegmentCount);
michael@0 373 if (NS_FAILED(rv)) return rv;
michael@0 374
michael@0 375 Classify();
michael@0 376
michael@0 377 NS_ADDREF(*responseBody = mPipeIn);
michael@0 378 return NS_OK;
michael@0 379 }
michael@0 380
michael@0 381 // This method should only be used on the socket thread
michael@0 382 nsAHttpConnection *
michael@0 383 nsHttpTransaction::Connection()
michael@0 384 {
michael@0 385 return mConnection;
michael@0 386 }
michael@0 387
michael@0 388 already_AddRefed<nsAHttpConnection>
michael@0 389 nsHttpTransaction::GetConnectionReference()
michael@0 390 {
michael@0 391 MutexAutoLock lock(mLock);
michael@0 392 nsRefPtr<nsAHttpConnection> connection = mConnection;
michael@0 393 return connection.forget();
michael@0 394 }
michael@0 395
michael@0 396 nsHttpResponseHead *
michael@0 397 nsHttpTransaction::TakeResponseHead()
michael@0 398 {
michael@0 399 MOZ_ASSERT(!mResponseHeadTaken, "TakeResponseHead called 2x");
michael@0 400
michael@0 401 // Lock RestartInProgress() and TakeResponseHead() against main thread
michael@0 402 MutexAutoLock lock(*nsHttp::GetLock());
michael@0 403
michael@0 404 mResponseHeadTaken = true;
michael@0 405
michael@0 406 // Prefer mForTakeResponseHead over mResponseHead. It is always a complete
michael@0 407 // set of headers.
michael@0 408 nsHttpResponseHead *head;
michael@0 409 if (mForTakeResponseHead) {
michael@0 410 head = mForTakeResponseHead;
michael@0 411 mForTakeResponseHead = nullptr;
michael@0 412 return head;
michael@0 413 }
michael@0 414
michael@0 415 // Even in OnStartRequest() the headers won't be available if we were
michael@0 416 // canceled
michael@0 417 if (!mHaveAllHeaders) {
michael@0 418 NS_WARNING("response headers not available or incomplete");
michael@0 419 return nullptr;
michael@0 420 }
michael@0 421
michael@0 422 head = mResponseHead;
michael@0 423 mResponseHead = nullptr;
michael@0 424 return head;
michael@0 425 }
michael@0 426
michael@0 427 void
michael@0 428 nsHttpTransaction::SetProxyConnectFailed()
michael@0 429 {
michael@0 430 mProxyConnectFailed = true;
michael@0 431 }
michael@0 432
michael@0 433 nsHttpRequestHead *
michael@0 434 nsHttpTransaction::RequestHead()
michael@0 435 {
michael@0 436 return mRequestHead;
michael@0 437 }
michael@0 438
michael@0 439 uint32_t
michael@0 440 nsHttpTransaction::Http1xTransactionCount()
michael@0 441 {
michael@0 442 return 1;
michael@0 443 }
michael@0 444
michael@0 445 nsresult
michael@0 446 nsHttpTransaction::TakeSubTransactions(
michael@0 447 nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
michael@0 448 {
michael@0 449 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 450 }
michael@0 451
michael@0 452 //----------------------------------------------------------------------------
michael@0 453 // nsHttpTransaction::nsAHttpTransaction
michael@0 454 //----------------------------------------------------------------------------
michael@0 455
michael@0 456 void
michael@0 457 nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
michael@0 458 {
michael@0 459 {
michael@0 460 MutexAutoLock lock(mLock);
michael@0 461 NS_IF_RELEASE(mConnection);
michael@0 462 NS_IF_ADDREF(mConnection = conn);
michael@0 463 }
michael@0 464
michael@0 465 if (conn) {
michael@0 466 MOZ_EVENT_TRACER_EXEC(static_cast<nsAHttpTransaction*>(this),
michael@0 467 "net::http::transaction");
michael@0 468 }
michael@0 469 }
michael@0 470
michael@0 471 void
michael@0 472 nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
michael@0 473 {
michael@0 474 MutexAutoLock lock(mLock);
michael@0 475 NS_IF_ADDREF(*cb = mCallbacks);
michael@0 476 }
michael@0 477
michael@0 478 void
michael@0 479 nsHttpTransaction::SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks)
michael@0 480 {
michael@0 481 {
michael@0 482 MutexAutoLock lock(mLock);
michael@0 483 mCallbacks = aCallbacks;
michael@0 484 }
michael@0 485
michael@0 486 if (gSocketTransportService) {
michael@0 487 nsRefPtr<UpdateSecurityCallbacks> event = new UpdateSecurityCallbacks(this, aCallbacks);
michael@0 488 gSocketTransportService->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL);
michael@0 489 }
michael@0 490 }
michael@0 491
michael@0 492 void
michael@0 493 nsHttpTransaction::OnTransportStatus(nsITransport* transport,
michael@0 494 nsresult status, uint64_t progress)
michael@0 495 {
michael@0 496 LOG(("nsHttpTransaction::OnSocketStatus [this=%p status=%x progress=%llu]\n",
michael@0 497 this, status, progress));
michael@0 498
michael@0 499 if (TimingEnabled()) {
michael@0 500 if (status == NS_NET_STATUS_RESOLVING_HOST) {
michael@0 501 mTimings.domainLookupStart = TimeStamp::Now();
michael@0 502 } else if (status == NS_NET_STATUS_RESOLVED_HOST) {
michael@0 503 mTimings.domainLookupEnd = TimeStamp::Now();
michael@0 504 } else if (status == NS_NET_STATUS_CONNECTING_TO) {
michael@0 505 mTimings.connectStart = TimeStamp::Now();
michael@0 506 } else if (status == NS_NET_STATUS_CONNECTED_TO) {
michael@0 507 mTimings.connectEnd = TimeStamp::Now();
michael@0 508 }
michael@0 509 }
michael@0 510
michael@0 511 if (!mTransportSink)
michael@0 512 return;
michael@0 513
michael@0 514 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 515
michael@0 516 // Need to do this before the STATUS_RECEIVING_FROM check below, to make
michael@0 517 // sure that the activity distributor gets told about all status events.
michael@0 518 if (mActivityDistributor) {
michael@0 519 // upon STATUS_WAITING_FOR; report request body sent
michael@0 520 if ((mHasRequestBody) &&
michael@0 521 (status == NS_NET_STATUS_WAITING_FOR))
michael@0 522 mActivityDistributor->ObserveActivity(
michael@0 523 mChannel,
michael@0 524 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 525 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,
michael@0 526 PR_Now(), 0, EmptyCString());
michael@0 527
michael@0 528 // report the status and progress
michael@0 529 if (!mRestartInProgressVerifier.IsDiscardingContent())
michael@0 530 mActivityDistributor->ObserveActivity(
michael@0 531 mChannel,
michael@0 532 NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
michael@0 533 static_cast<uint32_t>(status),
michael@0 534 PR_Now(),
michael@0 535 progress,
michael@0 536 EmptyCString());
michael@0 537 }
michael@0 538
michael@0 539 // nsHttpChannel synthesizes progress events in OnDataAvailable
michael@0 540 if (status == NS_NET_STATUS_RECEIVING_FROM)
michael@0 541 return;
michael@0 542
michael@0 543 uint64_t progressMax;
michael@0 544
michael@0 545 if (status == NS_NET_STATUS_SENDING_TO) {
michael@0 546 // suppress progress when only writing request headers
michael@0 547 if (!mHasRequestBody)
michael@0 548 return;
michael@0 549
michael@0 550 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
michael@0 551 MOZ_ASSERT(seekable, "Request stream isn't seekable?!?");
michael@0 552
michael@0 553 int64_t prog = 0;
michael@0 554 seekable->Tell(&prog);
michael@0 555 progress = prog;
michael@0 556
michael@0 557 // when uploading, we include the request headers in the progress
michael@0 558 // notifications.
michael@0 559 progressMax = mRequestSize; // XXX mRequestSize is 32-bit!
michael@0 560 }
michael@0 561 else {
michael@0 562 progress = 0;
michael@0 563 progressMax = 0;
michael@0 564 }
michael@0 565
michael@0 566 mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
michael@0 567 }
michael@0 568
michael@0 569 bool
michael@0 570 nsHttpTransaction::IsDone()
michael@0 571 {
michael@0 572 return mTransactionDone;
michael@0 573 }
michael@0 574
michael@0 575 nsresult
michael@0 576 nsHttpTransaction::Status()
michael@0 577 {
michael@0 578 return mStatus;
michael@0 579 }
michael@0 580
michael@0 581 uint32_t
michael@0 582 nsHttpTransaction::Caps()
michael@0 583 {
michael@0 584 return mCaps & ~mCapsToClear;
michael@0 585 }
michael@0 586
michael@0 587 void
michael@0 588 nsHttpTransaction::SetDNSWasRefreshed()
michael@0 589 {
michael@0 590 MOZ_ASSERT(NS_IsMainThread(), "SetDNSWasRefreshed on main thread only!");
michael@0 591 mCapsToClear |= NS_HTTP_REFRESH_DNS;
michael@0 592 }
michael@0 593
michael@0 594 uint64_t
michael@0 595 nsHttpTransaction::Available()
michael@0 596 {
michael@0 597 uint64_t size;
michael@0 598 if (NS_FAILED(mRequestStream->Available(&size)))
michael@0 599 size = 0;
michael@0 600 return size;
michael@0 601 }
michael@0 602
michael@0 603 NS_METHOD
michael@0 604 nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream,
michael@0 605 void *closure,
michael@0 606 const char *buf,
michael@0 607 uint32_t offset,
michael@0 608 uint32_t count,
michael@0 609 uint32_t *countRead)
michael@0 610 {
michael@0 611 nsHttpTransaction *trans = (nsHttpTransaction *) closure;
michael@0 612 nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
michael@0 613 if (NS_FAILED(rv)) return rv;
michael@0 614
michael@0 615 if (trans->TimingEnabled() && trans->mTimings.requestStart.IsNull()) {
michael@0 616 // First data we're sending -> this is requestStart
michael@0 617 trans->mTimings.requestStart = TimeStamp::Now();
michael@0 618 }
michael@0 619
michael@0 620 if (!trans->mSentData) {
michael@0 621 MOZ_EVENT_TRACER_MARK(static_cast<nsAHttpTransaction*>(trans),
michael@0 622 "net::http::first-write");
michael@0 623 }
michael@0 624
michael@0 625 trans->CountSentBytes(*countRead);
michael@0 626 trans->mSentData = true;
michael@0 627 return NS_OK;
michael@0 628 }
michael@0 629
michael@0 630 nsresult
michael@0 631 nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
michael@0 632 uint32_t count, uint32_t *countRead)
michael@0 633 {
michael@0 634 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 635
michael@0 636 if (mTransactionDone) {
michael@0 637 *countRead = 0;
michael@0 638 return mStatus;
michael@0 639 }
michael@0 640
michael@0 641 if (!mConnected) {
michael@0 642 mConnected = true;
michael@0 643 mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
michael@0 644 }
michael@0 645
michael@0 646 mReader = reader;
michael@0 647
michael@0 648 nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
michael@0 649
michael@0 650 mReader = nullptr;
michael@0 651
michael@0 652 // if read would block then we need to AsyncWait on the request stream.
michael@0 653 // have callback occur on socket thread so we stay synchronized.
michael@0 654 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
michael@0 655 nsCOMPtr<nsIAsyncInputStream> asyncIn =
michael@0 656 do_QueryInterface(mRequestStream);
michael@0 657 if (asyncIn) {
michael@0 658 nsCOMPtr<nsIEventTarget> target;
michael@0 659 gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
michael@0 660 if (target)
michael@0 661 asyncIn->AsyncWait(this, 0, 0, target);
michael@0 662 else {
michael@0 663 NS_ERROR("no socket thread event target");
michael@0 664 rv = NS_ERROR_UNEXPECTED;
michael@0 665 }
michael@0 666 }
michael@0 667 }
michael@0 668
michael@0 669 return rv;
michael@0 670 }
michael@0 671
michael@0 672 NS_METHOD
michael@0 673 nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream,
michael@0 674 void *closure,
michael@0 675 char *buf,
michael@0 676 uint32_t offset,
michael@0 677 uint32_t count,
michael@0 678 uint32_t *countWritten)
michael@0 679 {
michael@0 680 nsHttpTransaction *trans = (nsHttpTransaction *) closure;
michael@0 681
michael@0 682 if (trans->mTransactionDone)
michael@0 683 return NS_BASE_STREAM_CLOSED; // stop iterating
michael@0 684
michael@0 685 if (trans->TimingEnabled() && trans->mTimings.responseStart.IsNull()) {
michael@0 686 trans->mTimings.responseStart = TimeStamp::Now();
michael@0 687 }
michael@0 688
michael@0 689 nsresult rv;
michael@0 690 //
michael@0 691 // OK, now let the caller fill this segment with data.
michael@0 692 //
michael@0 693 rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
michael@0 694 if (NS_FAILED(rv)) return rv; // caller didn't want to write anything
michael@0 695
michael@0 696 if (!trans->mReceivedData) {
michael@0 697 MOZ_EVENT_TRACER_MARK(static_cast<nsAHttpTransaction*>(trans),
michael@0 698 "net::http::first-read");
michael@0 699 }
michael@0 700
michael@0 701 MOZ_ASSERT(*countWritten > 0, "bad writer");
michael@0 702 trans->CountRecvBytes(*countWritten);
michael@0 703 trans->mReceivedData = true;
michael@0 704
michael@0 705 // Let the transaction "play" with the buffer. It is free to modify
michael@0 706 // the contents of the buffer and/or modify countWritten.
michael@0 707 // - Bytes in HTTP headers don't count towards countWritten, so the input
michael@0 708 // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
michael@0 709 // OnInputStreamReady until all headers have been parsed.
michael@0 710 //
michael@0 711 rv = trans->ProcessData(buf, *countWritten, countWritten);
michael@0 712 if (NS_FAILED(rv))
michael@0 713 trans->Close(rv);
michael@0 714
michael@0 715 return rv; // failure code only stops WriteSegments; it is not propagated.
michael@0 716 }
michael@0 717
michael@0 718 nsresult
michael@0 719 nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
michael@0 720 uint32_t count, uint32_t *countWritten)
michael@0 721 {
michael@0 722 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 723
michael@0 724 if (mTransactionDone)
michael@0 725 return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
michael@0 726
michael@0 727 mWriter = writer;
michael@0 728
michael@0 729 nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
michael@0 730
michael@0 731 mWriter = nullptr;
michael@0 732
michael@0 733 // if pipe would block then we need to AsyncWait on it. have callback
michael@0 734 // occur on socket thread so we stay synchronized.
michael@0 735 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
michael@0 736 nsCOMPtr<nsIEventTarget> target;
michael@0 737 gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
michael@0 738 if (target)
michael@0 739 mPipeOut->AsyncWait(this, 0, 0, target);
michael@0 740 else {
michael@0 741 NS_ERROR("no socket thread event target");
michael@0 742 rv = NS_ERROR_UNEXPECTED;
michael@0 743 }
michael@0 744 }
michael@0 745
michael@0 746 return rv;
michael@0 747 }
michael@0 748
michael@0 749 nsresult
michael@0 750 nsHttpTransaction::SaveNetworkStats(bool enforce)
michael@0 751 {
michael@0 752 #ifdef MOZ_WIDGET_GONK
michael@0 753 // Check if active network and appid are valid.
michael@0 754 if (!mActiveNetwork || mAppId == NECKO_NO_APP_ID) {
michael@0 755 return NS_OK;
michael@0 756 }
michael@0 757
michael@0 758 if (mCountRecv <= 0 && mCountSent <= 0) {
michael@0 759 // There is no traffic, no need to save.
michael@0 760 return NS_OK;
michael@0 761 }
michael@0 762
michael@0 763 // If |enforce| is false, the traffic amount is saved
michael@0 764 // only when the total amount exceeds the predefined
michael@0 765 // threshold.
michael@0 766 uint64_t totalBytes = mCountRecv + mCountSent;
michael@0 767 if (!enforce && totalBytes < NETWORK_STATS_THRESHOLD) {
michael@0 768 return NS_OK;
michael@0 769 }
michael@0 770
michael@0 771 // Create the event to save the network statistics.
michael@0 772 // the event is then dispathed to the main thread.
michael@0 773 nsRefPtr<nsRunnable> event =
michael@0 774 new SaveNetworkStatsEvent(mAppId, mActiveNetwork,
michael@0 775 mCountRecv, mCountSent, false);
michael@0 776 NS_DispatchToMainThread(event);
michael@0 777
michael@0 778 // Reset the counters after saving.
michael@0 779 mCountSent = 0;
michael@0 780 mCountRecv = 0;
michael@0 781
michael@0 782 return NS_OK;
michael@0 783 #else
michael@0 784 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 785 #endif
michael@0 786 }
michael@0 787
michael@0 788 void
michael@0 789 nsHttpTransaction::Close(nsresult reason)
michael@0 790 {
michael@0 791 LOG(("nsHttpTransaction::Close [this=%p reason=%x]\n", this, reason));
michael@0 792
michael@0 793 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 794
michael@0 795 if (mClosed) {
michael@0 796 LOG((" already closed\n"));
michael@0 797 return;
michael@0 798 }
michael@0 799
michael@0 800 if (mTokenBucketCancel) {
michael@0 801 mTokenBucketCancel->Cancel(reason);
michael@0 802 mTokenBucketCancel = nullptr;
michael@0 803 }
michael@0 804
michael@0 805 if (mActivityDistributor) {
michael@0 806 // report the reponse is complete if not already reported
michael@0 807 if (!mResponseIsComplete)
michael@0 808 mActivityDistributor->ObserveActivity(
michael@0 809 mChannel,
michael@0 810 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 811 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
michael@0 812 PR_Now(),
michael@0 813 static_cast<uint64_t>(mContentRead),
michael@0 814 EmptyCString());
michael@0 815
michael@0 816 // report that this transaction is closing
michael@0 817 mActivityDistributor->ObserveActivity(
michael@0 818 mChannel,
michael@0 819 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 820 NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
michael@0 821 PR_Now(), 0, EmptyCString());
michael@0 822 }
michael@0 823
michael@0 824 // we must no longer reference the connection! find out if the
michael@0 825 // connection was being reused before letting it go.
michael@0 826 bool connReused = false;
michael@0 827 if (mConnection)
michael@0 828 connReused = mConnection->IsReused();
michael@0 829 mConnected = false;
michael@0 830
michael@0 831 //
michael@0 832 // if the connection was reset or closed before we wrote any part of the
michael@0 833 // request or if we wrote the request but didn't receive any part of the
michael@0 834 // response and the connection was being reused, then we can (and really
michael@0 835 // should) assume that we wrote to a stale connection and we must therefore
michael@0 836 // repeat the request over a new connection.
michael@0 837 //
michael@0 838 // NOTE: the conditions under which we will automatically retry the HTTP
michael@0 839 // request have to be carefully selected to avoid duplication of the
michael@0 840 // request from the point-of-view of the server. such duplication could
michael@0 841 // have dire consequences including repeated purchases, etc.
michael@0 842 //
michael@0 843 // NOTE: because of the way SSL proxy CONNECT is implemented, it is
michael@0 844 // possible that the transaction may have received data without having
michael@0 845 // sent any data. for this reason, mSendData == FALSE does not imply
michael@0 846 // mReceivedData == FALSE. (see bug 203057 for more info.)
michael@0 847 //
michael@0 848 if (reason == NS_ERROR_NET_RESET || reason == NS_OK) {
michael@0 849
michael@0 850 // reallySentData is meant to separate the instances where data has
michael@0 851 // been sent by this transaction but buffered at a higher level while
michael@0 852 // a TLS session (perhaps via a tunnel) is setup.
michael@0 853 bool reallySentData =
michael@0 854 mSentData && (!mConnection || mConnection->BytesWritten());
michael@0 855
michael@0 856 if (!mReceivedData &&
michael@0 857 (!reallySentData || connReused || mPipelinePosition)) {
michael@0 858 // if restarting fails, then we must proceed to close the pipe,
michael@0 859 // which will notify the channel that the transaction failed.
michael@0 860
michael@0 861 if (mPipelinePosition) {
michael@0 862 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 863 mConnInfo, nsHttpConnectionMgr::RedCanceledPipeline,
michael@0 864 nullptr, 0);
michael@0 865 }
michael@0 866 if (NS_SUCCEEDED(Restart()))
michael@0 867 return;
michael@0 868 }
michael@0 869 else if (!mResponseIsComplete && mPipelinePosition &&
michael@0 870 reason == NS_ERROR_NET_RESET) {
michael@0 871 // due to unhandled rst on a pipeline - safe to
michael@0 872 // restart as only idempotent is found there
michael@0 873
michael@0 874 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 875 mConnInfo, nsHttpConnectionMgr::RedCorruptedContent, nullptr, 0);
michael@0 876 if (NS_SUCCEEDED(RestartInProgress()))
michael@0 877 return;
michael@0 878 }
michael@0 879 }
michael@0 880
michael@0 881 bool relConn = true;
michael@0 882 if (NS_SUCCEEDED(reason)) {
michael@0 883 if (!mResponseIsComplete) {
michael@0 884 // The response has not been delimited with a high-confidence
michael@0 885 // algorithm like Content-Length or Chunked Encoding. We
michael@0 886 // need to use a strong framing mechanism to pipeline.
michael@0 887 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 888 mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
michael@0 889 nullptr, mClassification);
michael@0 890 }
michael@0 891 else if (mPipelinePosition) {
michael@0 892 // report this success as feedback
michael@0 893 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 894 mConnInfo, nsHttpConnectionMgr::GoodCompletedOK,
michael@0 895 nullptr, mPipelinePosition);
michael@0 896 }
michael@0 897
michael@0 898 // the server has not sent the final \r\n terminating the header
michael@0 899 // section, and there may still be a header line unparsed. let's make
michael@0 900 // sure we parse the remaining header line, and then hopefully, the
michael@0 901 // response will be usable (see bug 88792).
michael@0 902 if (!mHaveAllHeaders) {
michael@0 903 char data = '\n';
michael@0 904 uint32_t unused;
michael@0 905 ParseHead(&data, 1, &unused);
michael@0 906
michael@0 907 if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) {
michael@0 908 // Reject 0 byte HTTP/0.9 Responses - bug 423506
michael@0 909 LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
michael@0 910 reason = NS_ERROR_NET_RESET;
michael@0 911 }
michael@0 912 }
michael@0 913
michael@0 914 // honor the sticky connection flag...
michael@0 915 if (mCaps & NS_HTTP_STICKY_CONNECTION)
michael@0 916 relConn = false;
michael@0 917 }
michael@0 918
michael@0 919 // mTimings.responseEnd is normally recorded based on the end of a
michael@0 920 // HTTP delimiter such as chunked-encodings or content-length. However,
michael@0 921 // EOF or an error still require an end time be recorded.
michael@0 922 if (TimingEnabled() &&
michael@0 923 mTimings.responseEnd.IsNull() && !mTimings.responseStart.IsNull())
michael@0 924 mTimings.responseEnd = TimeStamp::Now();
michael@0 925
michael@0 926 if (relConn && mConnection) {
michael@0 927 MutexAutoLock lock(mLock);
michael@0 928 NS_RELEASE(mConnection);
michael@0 929 }
michael@0 930
michael@0 931 // save network statistics in the end of transaction
michael@0 932 SaveNetworkStats(true);
michael@0 933
michael@0 934 mStatus = reason;
michael@0 935 mTransactionDone = true; // forcibly flag the transaction as complete
michael@0 936 mClosed = true;
michael@0 937 ReleaseBlockingTransaction();
michael@0 938
michael@0 939 // release some resources that we no longer need
michael@0 940 mRequestStream = nullptr;
michael@0 941 mReqHeaderBuf.Truncate();
michael@0 942 mLineBuf.Truncate();
michael@0 943 if (mChunkedDecoder) {
michael@0 944 delete mChunkedDecoder;
michael@0 945 mChunkedDecoder = nullptr;
michael@0 946 }
michael@0 947
michael@0 948 // closing this pipe triggers the channel's OnStopRequest method.
michael@0 949 mPipeOut->CloseWithStatus(reason);
michael@0 950
michael@0 951 MOZ_EVENT_TRACER_DONE(static_cast<nsAHttpTransaction*>(this),
michael@0 952 "net::http::transaction");
michael@0 953 }
michael@0 954
michael@0 955 nsresult
michael@0 956 nsHttpTransaction::AddTransaction(nsAHttpTransaction *trans)
michael@0 957 {
michael@0 958 return NS_ERROR_NOT_IMPLEMENTED;
michael@0 959 }
michael@0 960
michael@0 961 uint32_t
michael@0 962 nsHttpTransaction::PipelineDepth()
michael@0 963 {
michael@0 964 return IsDone() ? 0 : 1;
michael@0 965 }
michael@0 966
michael@0 967 nsresult
michael@0 968 nsHttpTransaction::SetPipelinePosition(int32_t position)
michael@0 969 {
michael@0 970 mPipelinePosition = position;
michael@0 971 return NS_OK;
michael@0 972 }
michael@0 973
michael@0 974 int32_t
michael@0 975 nsHttpTransaction::PipelinePosition()
michael@0 976 {
michael@0 977 return mPipelinePosition;
michael@0 978 }
michael@0 979
michael@0 980 bool // NOTE BASE CLASS
michael@0 981 nsAHttpTransaction::ResponseTimeoutEnabled() const
michael@0 982 {
michael@0 983 return false;
michael@0 984 }
michael@0 985
michael@0 986 PRIntervalTime // NOTE BASE CLASS
michael@0 987 nsAHttpTransaction::ResponseTimeout()
michael@0 988 {
michael@0 989 return gHttpHandler->ResponseTimeout();
michael@0 990 }
michael@0 991
michael@0 992 bool
michael@0 993 nsHttpTransaction::ResponseTimeoutEnabled() const
michael@0 994 {
michael@0 995 return mResponseTimeoutEnabled;
michael@0 996 }
michael@0 997
michael@0 998 //-----------------------------------------------------------------------------
michael@0 999 // nsHttpTransaction <private>
michael@0 1000 //-----------------------------------------------------------------------------
michael@0 1001
michael@0 1002 nsresult
michael@0 1003 nsHttpTransaction::RestartInProgress()
michael@0 1004 {
michael@0 1005 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1006
michael@0 1007 if ((mRestartCount + 1) >= gHttpHandler->MaxRequestAttempts()) {
michael@0 1008 LOG(("nsHttpTransaction::RestartInProgress() "
michael@0 1009 "reached max request attempts, failing transaction %p\n", this));
michael@0 1010 return NS_ERROR_NET_RESET;
michael@0 1011 }
michael@0 1012
michael@0 1013 // Lock RestartInProgress() and TakeResponseHead() against main thread
michael@0 1014 MutexAutoLock lock(*nsHttp::GetLock());
michael@0 1015
michael@0 1016 // Don't try and RestartInProgress() things that haven't gotten a response
michael@0 1017 // header yet. Those should be handled under the normal restart() path if
michael@0 1018 // they are eligible.
michael@0 1019 if (!mHaveAllHeaders)
michael@0 1020 return NS_ERROR_NET_RESET;
michael@0 1021
michael@0 1022 // don't try and restart 0.9 or non 200/Get HTTP/1
michael@0 1023 if (!mRestartInProgressVerifier.IsSetup())
michael@0 1024 return NS_ERROR_NET_RESET;
michael@0 1025
michael@0 1026 LOG(("Will restart transaction %p and skip first %lld bytes, "
michael@0 1027 "old Content-Length %lld",
michael@0 1028 this, mContentRead, mContentLength));
michael@0 1029
michael@0 1030 mRestartInProgressVerifier.SetAlreadyProcessed(
michael@0 1031 std::max(mRestartInProgressVerifier.AlreadyProcessed(), mContentRead));
michael@0 1032
michael@0 1033 if (!mResponseHeadTaken && !mForTakeResponseHead) {
michael@0 1034 // TakeResponseHeader() has not been called yet and this
michael@0 1035 // is the first restart. Store the resp headers exclusively
michael@0 1036 // for TakeResponseHead() which is called from the main thread and
michael@0 1037 // could happen at any time - so we can't continue to modify those
michael@0 1038 // headers (which restarting will effectively do)
michael@0 1039 mForTakeResponseHead = mResponseHead;
michael@0 1040 mResponseHead = nullptr;
michael@0 1041 }
michael@0 1042
michael@0 1043 if (mResponseHead) {
michael@0 1044 mResponseHead->Reset();
michael@0 1045 }
michael@0 1046
michael@0 1047 mContentRead = 0;
michael@0 1048 mContentLength = -1;
michael@0 1049 delete mChunkedDecoder;
michael@0 1050 mChunkedDecoder = nullptr;
michael@0 1051 mHaveStatusLine = false;
michael@0 1052 mHaveAllHeaders = false;
michael@0 1053 mHttpResponseMatched = false;
michael@0 1054 mResponseIsComplete = false;
michael@0 1055 mDidContentStart = false;
michael@0 1056 mNoContent = false;
michael@0 1057 mSentData = false;
michael@0 1058 mReceivedData = false;
michael@0 1059
michael@0 1060 return Restart();
michael@0 1061 }
michael@0 1062
michael@0 1063 nsresult
michael@0 1064 nsHttpTransaction::Restart()
michael@0 1065 {
michael@0 1066 MOZ_ASSERT(PR_GetCurrentThread() == gSocketThread);
michael@0 1067
michael@0 1068 // limit the number of restart attempts - bug 92224
michael@0 1069 if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
michael@0 1070 LOG(("reached max request attempts, failing transaction @%p\n", this));
michael@0 1071 return NS_ERROR_NET_RESET;
michael@0 1072 }
michael@0 1073
michael@0 1074 LOG(("restarting transaction @%p\n", this));
michael@0 1075
michael@0 1076 // rewind streams in case we already wrote out the request
michael@0 1077 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
michael@0 1078 if (seekable)
michael@0 1079 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
michael@0 1080
michael@0 1081 // clear old connection state...
michael@0 1082 mSecurityInfo = 0;
michael@0 1083 if (mConnection) {
michael@0 1084 MutexAutoLock lock(mLock);
michael@0 1085 NS_RELEASE(mConnection);
michael@0 1086 }
michael@0 1087
michael@0 1088 // disable pipelining for the next attempt in case pipelining caused the
michael@0 1089 // reset. this is being overly cautious since we don't know if pipelining
michael@0 1090 // was the problem here.
michael@0 1091 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
michael@0 1092 SetPipelinePosition(0);
michael@0 1093
michael@0 1094 return gHttpHandler->InitiateTransaction(this, mPriority);
michael@0 1095 }
michael@0 1096
michael@0 1097 char *
michael@0 1098 nsHttpTransaction::LocateHttpStart(char *buf, uint32_t len,
michael@0 1099 bool aAllowPartialMatch)
michael@0 1100 {
michael@0 1101 MOZ_ASSERT(!aAllowPartialMatch || mLineBuf.IsEmpty());
michael@0 1102
michael@0 1103 static const char HTTPHeader[] = "HTTP/1.";
michael@0 1104 static const uint32_t HTTPHeaderLen = sizeof(HTTPHeader) - 1;
michael@0 1105 static const char HTTP2Header[] = "HTTP/2.0";
michael@0 1106 static const uint32_t HTTP2HeaderLen = sizeof(HTTP2Header) - 1;
michael@0 1107 // ShoutCast ICY is treated as HTTP/1.0
michael@0 1108 static const char ICYHeader[] = "ICY ";
michael@0 1109 static const uint32_t ICYHeaderLen = sizeof(ICYHeader) - 1;
michael@0 1110
michael@0 1111 if (aAllowPartialMatch && (len < HTTPHeaderLen))
michael@0 1112 return (PL_strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nullptr;
michael@0 1113
michael@0 1114 // mLineBuf can contain partial match from previous search
michael@0 1115 if (!mLineBuf.IsEmpty()) {
michael@0 1116 MOZ_ASSERT(mLineBuf.Length() < HTTPHeaderLen);
michael@0 1117 int32_t checkChars = std::min(len, HTTPHeaderLen - mLineBuf.Length());
michael@0 1118 if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(),
michael@0 1119 checkChars) == 0) {
michael@0 1120 mLineBuf.Append(buf, checkChars);
michael@0 1121 if (mLineBuf.Length() == HTTPHeaderLen) {
michael@0 1122 // We've found whole HTTPHeader sequence. Return pointer at the
michael@0 1123 // end of matched sequence since it is stored in mLineBuf.
michael@0 1124 return (buf + checkChars);
michael@0 1125 }
michael@0 1126 // Response matches pattern but is still incomplete.
michael@0 1127 return 0;
michael@0 1128 }
michael@0 1129 // Previous partial match together with new data doesn't match the
michael@0 1130 // pattern. Start the search again.
michael@0 1131 mLineBuf.Truncate();
michael@0 1132 }
michael@0 1133
michael@0 1134 bool firstByte = true;
michael@0 1135 while (len > 0) {
michael@0 1136 if (PL_strncasecmp(buf, HTTPHeader, std::min<uint32_t>(len, HTTPHeaderLen)) == 0) {
michael@0 1137 if (len < HTTPHeaderLen) {
michael@0 1138 // partial HTTPHeader sequence found
michael@0 1139 // save partial match to mLineBuf
michael@0 1140 mLineBuf.Assign(buf, len);
michael@0 1141 return 0;
michael@0 1142 }
michael@0 1143
michael@0 1144 // whole HTTPHeader sequence found
michael@0 1145 return buf;
michael@0 1146 }
michael@0 1147
michael@0 1148 // At least "SmarterTools/2.0.3974.16813" generates nonsensical
michael@0 1149 // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
michael@0 1150 // it as HTTP/1.1 to be compatible with old versions of ourselves and
michael@0 1151 // other browsers
michael@0 1152
michael@0 1153 if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen &&
michael@0 1154 (PL_strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) {
michael@0 1155 LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
michael@0 1156 return buf;
michael@0 1157 }
michael@0 1158
michael@0 1159 // Treat ICY (AOL/Nullsoft ShoutCast) non-standard header in same fashion
michael@0 1160 // as HTTP/2.0 is treated above. This will allow "ICY " to be interpretted
michael@0 1161 // as HTTP/1.0 in nsHttpResponseHead::ParseVersion
michael@0 1162
michael@0 1163 if (firstByte && !mInvalidResponseBytesRead && len >= ICYHeaderLen &&
michael@0 1164 (PL_strncasecmp(buf, ICYHeader, ICYHeaderLen) == 0)) {
michael@0 1165 LOG(("nsHttpTransaction:: Identified ICY treating as HTTP/1.0\n"));
michael@0 1166 return buf;
michael@0 1167 }
michael@0 1168
michael@0 1169 if (!nsCRT::IsAsciiSpace(*buf))
michael@0 1170 firstByte = false;
michael@0 1171 buf++;
michael@0 1172 len--;
michael@0 1173 }
michael@0 1174 return 0;
michael@0 1175 }
michael@0 1176
michael@0 1177 nsresult
michael@0 1178 nsHttpTransaction::ParseLine(char *line)
michael@0 1179 {
michael@0 1180 LOG(("nsHttpTransaction::ParseLine [%s]\n", line));
michael@0 1181 nsresult rv = NS_OK;
michael@0 1182
michael@0 1183 if (!mHaveStatusLine) {
michael@0 1184 mResponseHead->ParseStatusLine(line);
michael@0 1185 mHaveStatusLine = true;
michael@0 1186 // XXX this should probably never happen
michael@0 1187 if (mResponseHead->Version() == NS_HTTP_VERSION_0_9)
michael@0 1188 mHaveAllHeaders = true;
michael@0 1189 }
michael@0 1190 else {
michael@0 1191 rv = mResponseHead->ParseHeaderLine(line);
michael@0 1192 }
michael@0 1193 return rv;
michael@0 1194 }
michael@0 1195
michael@0 1196 nsresult
michael@0 1197 nsHttpTransaction::ParseLineSegment(char *segment, uint32_t len)
michael@0 1198 {
michael@0 1199 NS_PRECONDITION(!mHaveAllHeaders, "already have all headers");
michael@0 1200
michael@0 1201 if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
michael@0 1202 // trim off the new line char, and if this segment is
michael@0 1203 // not a continuation of the previous or if we haven't
michael@0 1204 // parsed the status line yet, then parse the contents
michael@0 1205 // of mLineBuf.
michael@0 1206 mLineBuf.Truncate(mLineBuf.Length() - 1);
michael@0 1207 if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
michael@0 1208 nsresult rv = ParseLine(mLineBuf.BeginWriting());
michael@0 1209 mLineBuf.Truncate();
michael@0 1210 if (NS_FAILED(rv)) {
michael@0 1211 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 1212 mConnInfo, nsHttpConnectionMgr::RedCorruptedContent,
michael@0 1213 nullptr, 0);
michael@0 1214 return rv;
michael@0 1215 }
michael@0 1216 }
michael@0 1217 }
michael@0 1218
michael@0 1219 // append segment to mLineBuf...
michael@0 1220 mLineBuf.Append(segment, len);
michael@0 1221
michael@0 1222 // a line buf with only a new line char signifies the end of headers.
michael@0 1223 if (mLineBuf.First() == '\n') {
michael@0 1224 mLineBuf.Truncate();
michael@0 1225 // discard this response if it is a 100 continue or other 1xx status.
michael@0 1226 uint16_t status = mResponseHead->Status();
michael@0 1227 if ((status != 101) && (status / 100 == 1)) {
michael@0 1228 LOG(("ignoring 1xx response\n"));
michael@0 1229 mHaveStatusLine = false;
michael@0 1230 mHttpResponseMatched = false;
michael@0 1231 mConnection->SetLastTransactionExpectedNoContent(true);
michael@0 1232 mResponseHead->Reset();
michael@0 1233 return NS_OK;
michael@0 1234 }
michael@0 1235 mHaveAllHeaders = true;
michael@0 1236 }
michael@0 1237 return NS_OK;
michael@0 1238 }
michael@0 1239
michael@0 1240 nsresult
michael@0 1241 nsHttpTransaction::ParseHead(char *buf,
michael@0 1242 uint32_t count,
michael@0 1243 uint32_t *countRead)
michael@0 1244 {
michael@0 1245 nsresult rv;
michael@0 1246 uint32_t len;
michael@0 1247 char *eol;
michael@0 1248
michael@0 1249 LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
michael@0 1250
michael@0 1251 *countRead = 0;
michael@0 1252
michael@0 1253 NS_PRECONDITION(!mHaveAllHeaders, "oops");
michael@0 1254
michael@0 1255 // allocate the response head object if necessary
michael@0 1256 if (!mResponseHead) {
michael@0 1257 mResponseHead = new nsHttpResponseHead();
michael@0 1258 if (!mResponseHead)
michael@0 1259 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1260
michael@0 1261 // report that we have a least some of the response
michael@0 1262 if (mActivityDistributor && !mReportedStart) {
michael@0 1263 mReportedStart = true;
michael@0 1264 mActivityDistributor->ObserveActivity(
michael@0 1265 mChannel,
michael@0 1266 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 1267 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,
michael@0 1268 PR_Now(), 0, EmptyCString());
michael@0 1269 }
michael@0 1270 }
michael@0 1271
michael@0 1272 if (!mHttpResponseMatched) {
michael@0 1273 // Normally we insist on seeing HTTP/1.x in the first few bytes,
michael@0 1274 // but if we are on a persistent connection and the previous transaction
michael@0 1275 // was not supposed to have any content then we need to be prepared
michael@0 1276 // to skip over a response body that the server may have sent even
michael@0 1277 // though it wasn't allowed.
michael@0 1278 if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
michael@0 1279 // tolerate only minor junk before the status line
michael@0 1280 mHttpResponseMatched = true;
michael@0 1281 char *p = LocateHttpStart(buf, std::min<uint32_t>(count, 11), true);
michael@0 1282 if (!p) {
michael@0 1283 // Treat any 0.9 style response of a put as a failure.
michael@0 1284 if (mRequestHead->IsPut())
michael@0 1285 return NS_ERROR_ABORT;
michael@0 1286
michael@0 1287 mResponseHead->ParseStatusLine("");
michael@0 1288 mHaveStatusLine = true;
michael@0 1289 mHaveAllHeaders = true;
michael@0 1290 return NS_OK;
michael@0 1291 }
michael@0 1292 if (p > buf) {
michael@0 1293 // skip over the junk
michael@0 1294 mInvalidResponseBytesRead += p - buf;
michael@0 1295 *countRead = p - buf;
michael@0 1296 buf = p;
michael@0 1297 }
michael@0 1298 }
michael@0 1299 else {
michael@0 1300 char *p = LocateHttpStart(buf, count, false);
michael@0 1301 if (p) {
michael@0 1302 mInvalidResponseBytesRead += p - buf;
michael@0 1303 *countRead = p - buf;
michael@0 1304 buf = p;
michael@0 1305 mHttpResponseMatched = true;
michael@0 1306 } else {
michael@0 1307 mInvalidResponseBytesRead += count;
michael@0 1308 *countRead = count;
michael@0 1309 if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
michael@0 1310 LOG(("nsHttpTransaction::ParseHead() "
michael@0 1311 "Cannot find Response Header\n"));
michael@0 1312 // cannot go back and call this 0.9 anymore as we
michael@0 1313 // have thrown away a lot of the leading junk
michael@0 1314 return NS_ERROR_ABORT;
michael@0 1315 }
michael@0 1316 return NS_OK;
michael@0 1317 }
michael@0 1318 }
michael@0 1319 }
michael@0 1320 // otherwise we can assume that we don't have a HTTP/0.9 response.
michael@0 1321
michael@0 1322 MOZ_ASSERT (mHttpResponseMatched);
michael@0 1323 while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) != nullptr) {
michael@0 1324 // found line in range [buf:eol]
michael@0 1325 len = eol - buf + 1;
michael@0 1326
michael@0 1327 *countRead += len;
michael@0 1328
michael@0 1329 // actually, the line is in the range [buf:eol-1]
michael@0 1330 if ((eol > buf) && (*(eol-1) == '\r'))
michael@0 1331 len--;
michael@0 1332
michael@0 1333 buf[len-1] = '\n';
michael@0 1334 rv = ParseLineSegment(buf, len);
michael@0 1335 if (NS_FAILED(rv))
michael@0 1336 return rv;
michael@0 1337
michael@0 1338 if (mHaveAllHeaders)
michael@0 1339 return NS_OK;
michael@0 1340
michael@0 1341 // skip over line
michael@0 1342 buf = eol + 1;
michael@0 1343
michael@0 1344 if (!mHttpResponseMatched) {
michael@0 1345 // a 100 class response has caused us to throw away that set of
michael@0 1346 // response headers and look for the next response
michael@0 1347 return NS_ERROR_NET_INTERRUPT;
michael@0 1348 }
michael@0 1349 }
michael@0 1350
michael@0 1351 // do something about a partial header line
michael@0 1352 if (!mHaveAllHeaders && (len = count - *countRead)) {
michael@0 1353 *countRead = count;
michael@0 1354 // ignore a trailing carriage return, and don't bother calling
michael@0 1355 // ParseLineSegment if buf only contains a carriage return.
michael@0 1356 if ((buf[len-1] == '\r') && (--len == 0))
michael@0 1357 return NS_OK;
michael@0 1358 rv = ParseLineSegment(buf, len);
michael@0 1359 if (NS_FAILED(rv))
michael@0 1360 return rv;
michael@0 1361 }
michael@0 1362 return NS_OK;
michael@0 1363 }
michael@0 1364
michael@0 1365 // called on the socket thread
michael@0 1366 nsresult
michael@0 1367 nsHttpTransaction::HandleContentStart()
michael@0 1368 {
michael@0 1369 LOG(("nsHttpTransaction::HandleContentStart [this=%p]\n", this));
michael@0 1370
michael@0 1371 if (mResponseHead) {
michael@0 1372 #if defined(PR_LOGGING)
michael@0 1373 if (LOG3_ENABLED()) {
michael@0 1374 LOG3(("http response [\n"));
michael@0 1375 nsAutoCString headers;
michael@0 1376 mResponseHead->Flatten(headers, false);
michael@0 1377 LogHeaders(headers.get());
michael@0 1378 LOG3(("]\n"));
michael@0 1379 }
michael@0 1380 #endif
michael@0 1381 // Save http version, mResponseHead isn't available anymore after
michael@0 1382 // TakeResponseHead() is called
michael@0 1383 mHttpVersion = mResponseHead->Version();
michael@0 1384
michael@0 1385 // notify the connection, give it a chance to cause a reset.
michael@0 1386 bool reset = false;
michael@0 1387 if (!mRestartInProgressVerifier.IsSetup())
michael@0 1388 mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
michael@0 1389
michael@0 1390 // looks like we should ignore this response, resetting...
michael@0 1391 if (reset) {
michael@0 1392 LOG(("resetting transaction's response head\n"));
michael@0 1393 mHaveAllHeaders = false;
michael@0 1394 mHaveStatusLine = false;
michael@0 1395 mReceivedData = false;
michael@0 1396 mSentData = false;
michael@0 1397 mHttpResponseMatched = false;
michael@0 1398 mResponseHead->Reset();
michael@0 1399 // wait to be called again...
michael@0 1400 return NS_OK;
michael@0 1401 }
michael@0 1402
michael@0 1403 // check if this is a no-content response
michael@0 1404 switch (mResponseHead->Status()) {
michael@0 1405 case 101:
michael@0 1406 mPreserveStream = true; // fall through to other no content
michael@0 1407 case 204:
michael@0 1408 case 205:
michael@0 1409 case 304:
michael@0 1410 mNoContent = true;
michael@0 1411 LOG(("this response should not contain a body.\n"));
michael@0 1412 break;
michael@0 1413 }
michael@0 1414
michael@0 1415 if (mResponseHead->Status() == 200 &&
michael@0 1416 mConnection->IsProxyConnectInProgress()) {
michael@0 1417 // successful CONNECTs do not have response bodies
michael@0 1418 mNoContent = true;
michael@0 1419 }
michael@0 1420 mConnection->SetLastTransactionExpectedNoContent(mNoContent);
michael@0 1421 if (mInvalidResponseBytesRead)
michael@0 1422 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 1423 mConnInfo, nsHttpConnectionMgr::BadInsufficientFraming,
michael@0 1424 nullptr, mClassification);
michael@0 1425
michael@0 1426 if (mNoContent)
michael@0 1427 mContentLength = 0;
michael@0 1428 else {
michael@0 1429 // grab the content-length from the response headers
michael@0 1430 mContentLength = mResponseHead->ContentLength();
michael@0 1431
michael@0 1432 if ((mClassification != CLASS_SOLO) &&
michael@0 1433 (mContentLength > mMaxPipelineObjectSize))
michael@0 1434 CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
michael@0 1435
michael@0 1436 // handle chunked encoding here, so we'll know immediately when
michael@0 1437 // we're done with the socket. please note that _all_ other
michael@0 1438 // decoding is done when the channel receives the content data
michael@0 1439 // so as not to block the socket transport thread too much.
michael@0 1440 // ignore chunked responses from HTTP/1.0 servers and proxies.
michael@0 1441 if (mResponseHead->Version() >= NS_HTTP_VERSION_1_1 &&
michael@0 1442 mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
michael@0 1443 // we only support the "chunked" transfer encoding right now.
michael@0 1444 mChunkedDecoder = new nsHttpChunkedDecoder();
michael@0 1445 if (!mChunkedDecoder)
michael@0 1446 return NS_ERROR_OUT_OF_MEMORY;
michael@0 1447 LOG(("chunked decoder created\n"));
michael@0 1448 // Ignore server specified Content-Length.
michael@0 1449 mContentLength = -1;
michael@0 1450 }
michael@0 1451 #if defined(PR_LOGGING)
michael@0 1452 else if (mContentLength == int64_t(-1))
michael@0 1453 LOG(("waiting for the server to close the connection.\n"));
michael@0 1454 #endif
michael@0 1455 }
michael@0 1456 if (mRestartInProgressVerifier.IsSetup() &&
michael@0 1457 !mRestartInProgressVerifier.Verify(mContentLength, mResponseHead)) {
michael@0 1458 LOG(("Restart in progress subsequent transaction failed to match"));
michael@0 1459 return NS_ERROR_ABORT;
michael@0 1460 }
michael@0 1461 }
michael@0 1462
michael@0 1463 mDidContentStart = true;
michael@0 1464
michael@0 1465 // The verifier only initializes itself once (from the first iteration of
michael@0 1466 // a transaction that gets far enough to have response headers)
michael@0 1467 if (mRequestHead->IsGet())
michael@0 1468 mRestartInProgressVerifier.Set(mContentLength, mResponseHead);
michael@0 1469
michael@0 1470 return NS_OK;
michael@0 1471 }
michael@0 1472
michael@0 1473 // called on the socket thread
michael@0 1474 nsresult
michael@0 1475 nsHttpTransaction::HandleContent(char *buf,
michael@0 1476 uint32_t count,
michael@0 1477 uint32_t *contentRead,
michael@0 1478 uint32_t *contentRemaining)
michael@0 1479 {
michael@0 1480 nsresult rv;
michael@0 1481
michael@0 1482 LOG(("nsHttpTransaction::HandleContent [this=%p count=%u]\n", this, count));
michael@0 1483
michael@0 1484 *contentRead = 0;
michael@0 1485 *contentRemaining = 0;
michael@0 1486
michael@0 1487 MOZ_ASSERT(mConnection);
michael@0 1488
michael@0 1489 if (!mDidContentStart) {
michael@0 1490 rv = HandleContentStart();
michael@0 1491 if (NS_FAILED(rv)) return rv;
michael@0 1492 // Do not write content to the pipe if we haven't started streaming yet
michael@0 1493 if (!mDidContentStart)
michael@0 1494 return NS_OK;
michael@0 1495 }
michael@0 1496
michael@0 1497 if (mChunkedDecoder) {
michael@0 1498 // give the buf over to the chunked decoder so it can reformat the
michael@0 1499 // data and tell us how much is really there.
michael@0 1500 rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, contentRemaining);
michael@0 1501 if (NS_FAILED(rv)) return rv;
michael@0 1502 }
michael@0 1503 else if (mContentLength >= int64_t(0)) {
michael@0 1504 // HTTP/1.0 servers have been known to send erroneous Content-Length
michael@0 1505 // headers. So, unless the connection is persistent, we must make
michael@0 1506 // allowances for a possibly invalid Content-Length header. Thus, if
michael@0 1507 // NOT persistent, we simply accept everything in |buf|.
michael@0 1508 if (mConnection->IsPersistent() || mPreserveStream ||
michael@0 1509 mHttpVersion >= NS_HTTP_VERSION_1_1) {
michael@0 1510 int64_t remaining = mContentLength - mContentRead;
michael@0 1511 *contentRead = uint32_t(std::min<int64_t>(count, remaining));
michael@0 1512 *contentRemaining = count - *contentRead;
michael@0 1513 }
michael@0 1514 else {
michael@0 1515 *contentRead = count;
michael@0 1516 // mContentLength might need to be increased...
michael@0 1517 int64_t position = mContentRead + int64_t(count);
michael@0 1518 if (position > mContentLength) {
michael@0 1519 mContentLength = position;
michael@0 1520 //mResponseHead->SetContentLength(mContentLength);
michael@0 1521 }
michael@0 1522 }
michael@0 1523 }
michael@0 1524 else {
michael@0 1525 // when we are just waiting for the server to close the connection...
michael@0 1526 // (no explicit content-length given)
michael@0 1527 *contentRead = count;
michael@0 1528 }
michael@0 1529
michael@0 1530 int64_t toReadBeforeRestart =
michael@0 1531 mRestartInProgressVerifier.ToReadBeforeRestart();
michael@0 1532
michael@0 1533 if (toReadBeforeRestart && *contentRead) {
michael@0 1534 uint32_t ignore =
michael@0 1535 static_cast<uint32_t>(std::min<int64_t>(toReadBeforeRestart, UINT32_MAX));
michael@0 1536 ignore = std::min(*contentRead, ignore);
michael@0 1537 LOG(("Due To Restart ignoring %d of remaining %ld",
michael@0 1538 ignore, toReadBeforeRestart));
michael@0 1539 *contentRead -= ignore;
michael@0 1540 mContentRead += ignore;
michael@0 1541 mRestartInProgressVerifier.HaveReadBeforeRestart(ignore);
michael@0 1542 memmove(buf, buf + ignore, *contentRead + *contentRemaining);
michael@0 1543 }
michael@0 1544
michael@0 1545 if (*contentRead) {
michael@0 1546 // update count of content bytes read and report progress...
michael@0 1547 mContentRead += *contentRead;
michael@0 1548 /* when uncommenting, take care of 64-bit integers w/ std::max...
michael@0 1549 if (mProgressSink)
michael@0 1550 mProgressSink->OnProgress(nullptr, nullptr, mContentRead, std::max(0, mContentLength));
michael@0 1551 */
michael@0 1552 }
michael@0 1553
michael@0 1554 LOG(("nsHttpTransaction::HandleContent [this=%p count=%u read=%u mContentRead=%lld mContentLength=%lld]\n",
michael@0 1555 this, count, *contentRead, mContentRead, mContentLength));
michael@0 1556
michael@0 1557 // Check the size of chunked responses. If we exceed the max pipeline size
michael@0 1558 // for this response reschedule the pipeline
michael@0 1559 if ((mClassification != CLASS_SOLO) &&
michael@0 1560 mChunkedDecoder &&
michael@0 1561 ((mContentRead + mChunkedDecoder->GetChunkRemaining()) >
michael@0 1562 mMaxPipelineObjectSize)) {
michael@0 1563 CancelPipeline(nsHttpConnectionMgr::BadUnexpectedLarge);
michael@0 1564 }
michael@0 1565
michael@0 1566 // check for end-of-file
michael@0 1567 if ((mContentRead == mContentLength) ||
michael@0 1568 (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
michael@0 1569 // the transaction is done with a complete response.
michael@0 1570 mTransactionDone = true;
michael@0 1571 mResponseIsComplete = true;
michael@0 1572 ReleaseBlockingTransaction();
michael@0 1573
michael@0 1574 if (TimingEnabled())
michael@0 1575 mTimings.responseEnd = TimeStamp::Now();
michael@0 1576
michael@0 1577 // report the entire response has arrived
michael@0 1578 if (mActivityDistributor)
michael@0 1579 mActivityDistributor->ObserveActivity(
michael@0 1580 mChannel,
michael@0 1581 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 1582 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
michael@0 1583 PR_Now(),
michael@0 1584 static_cast<uint64_t>(mContentRead),
michael@0 1585 EmptyCString());
michael@0 1586 }
michael@0 1587
michael@0 1588 return NS_OK;
michael@0 1589 }
michael@0 1590
michael@0 1591 nsresult
michael@0 1592 nsHttpTransaction::ProcessData(char *buf, uint32_t count, uint32_t *countRead)
michael@0 1593 {
michael@0 1594 nsresult rv;
michael@0 1595
michael@0 1596 LOG(("nsHttpTransaction::ProcessData [this=%p count=%u]\n", this, count));
michael@0 1597
michael@0 1598 *countRead = 0;
michael@0 1599
michael@0 1600 // we may not have read all of the headers yet...
michael@0 1601 if (!mHaveAllHeaders) {
michael@0 1602 uint32_t bytesConsumed = 0;
michael@0 1603
michael@0 1604 do {
michael@0 1605 uint32_t localBytesConsumed = 0;
michael@0 1606 char *localBuf = buf + bytesConsumed;
michael@0 1607 uint32_t localCount = count - bytesConsumed;
michael@0 1608
michael@0 1609 rv = ParseHead(localBuf, localCount, &localBytesConsumed);
michael@0 1610 if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT)
michael@0 1611 return rv;
michael@0 1612 bytesConsumed += localBytesConsumed;
michael@0 1613 } while (rv == NS_ERROR_NET_INTERRUPT);
michael@0 1614
michael@0 1615 count -= bytesConsumed;
michael@0 1616
michael@0 1617 // if buf has some content in it, shift bytes to top of buf.
michael@0 1618 if (count && bytesConsumed)
michael@0 1619 memmove(buf, buf + bytesConsumed, count);
michael@0 1620
michael@0 1621 // report the completed response header
michael@0 1622 if (mActivityDistributor && mResponseHead && mHaveAllHeaders &&
michael@0 1623 !mReportedResponseHeader) {
michael@0 1624 mReportedResponseHeader = true;
michael@0 1625 nsAutoCString completeResponseHeaders;
michael@0 1626 mResponseHead->Flatten(completeResponseHeaders, false);
michael@0 1627 completeResponseHeaders.AppendLiteral("\r\n");
michael@0 1628 mActivityDistributor->ObserveActivity(
michael@0 1629 mChannel,
michael@0 1630 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
michael@0 1631 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER,
michael@0 1632 PR_Now(), 0,
michael@0 1633 completeResponseHeaders);
michael@0 1634 }
michael@0 1635 }
michael@0 1636
michael@0 1637 // even though count may be 0, we still want to call HandleContent
michael@0 1638 // so it can complete the transaction if this is a "no-content" response.
michael@0 1639 if (mHaveAllHeaders) {
michael@0 1640 uint32_t countRemaining = 0;
michael@0 1641 //
michael@0 1642 // buf layout:
michael@0 1643 //
michael@0 1644 // +--------------------------------------+----------------+-----+
michael@0 1645 // | countRead | countRemaining | |
michael@0 1646 // +--------------------------------------+----------------+-----+
michael@0 1647 //
michael@0 1648 // count : bytes read from the socket
michael@0 1649 // countRead : bytes corresponding to this transaction
michael@0 1650 // countRemaining : bytes corresponding to next pipelined transaction
michael@0 1651 //
michael@0 1652 // NOTE:
michael@0 1653 // count > countRead + countRemaining <==> chunked transfer encoding
michael@0 1654 //
michael@0 1655 rv = HandleContent(buf, count, countRead, &countRemaining);
michael@0 1656 if (NS_FAILED(rv)) return rv;
michael@0 1657 // we may have read more than our share, in which case we must give
michael@0 1658 // the excess bytes back to the connection
michael@0 1659 if (mResponseIsComplete && countRemaining) {
michael@0 1660 MOZ_ASSERT(mConnection);
michael@0 1661 mConnection->PushBack(buf + *countRead, countRemaining);
michael@0 1662 }
michael@0 1663 }
michael@0 1664
michael@0 1665 return NS_OK;
michael@0 1666 }
michael@0 1667
michael@0 1668 void
michael@0 1669 nsHttpTransaction::CancelPipeline(uint32_t reason)
michael@0 1670 {
michael@0 1671 // reason is casted through a uint to avoid compiler header deps
michael@0 1672 gHttpHandler->ConnMgr()->PipelineFeedbackInfo(
michael@0 1673 mConnInfo,
michael@0 1674 static_cast<nsHttpConnectionMgr::PipelineFeedbackInfoType>(reason),
michael@0 1675 nullptr, mClassification);
michael@0 1676
michael@0 1677 mConnection->CancelPipeline(NS_ERROR_ABORT);
michael@0 1678
michael@0 1679 // Avoid pipelining this transaction on restart by classifying it as solo.
michael@0 1680 // This also prevents BadUnexpectedLarge from being reported more
michael@0 1681 // than one time per transaction.
michael@0 1682 mClassification = CLASS_SOLO;
michael@0 1683 }
michael@0 1684
michael@0 1685 // Called when the transaction marked for blocking is associated with a connection
michael@0 1686 // (i.e. added to a spdy session, an idle http connection, or placed into
michael@0 1687 // a http pipeline). It is safe to call this multiple times with it only
michael@0 1688 // having an effect once.
michael@0 1689 void
michael@0 1690 nsHttpTransaction::DispatchedAsBlocking()
michael@0 1691 {
michael@0 1692 if (mDispatchedAsBlocking)
michael@0 1693 return;
michael@0 1694
michael@0 1695 LOG(("nsHttpTransaction %p dispatched as blocking\n", this));
michael@0 1696
michael@0 1697 if (!mLoadGroupCI)
michael@0 1698 return;
michael@0 1699
michael@0 1700 LOG(("nsHttpTransaction adding blocking channel %p from "
michael@0 1701 "loadgroup %p\n", this, mLoadGroupCI.get()));
michael@0 1702
michael@0 1703 mLoadGroupCI->AddBlockingTransaction();
michael@0 1704 mDispatchedAsBlocking = true;
michael@0 1705 }
michael@0 1706
michael@0 1707 void
michael@0 1708 nsHttpTransaction::RemoveDispatchedAsBlocking()
michael@0 1709 {
michael@0 1710 if (!mLoadGroupCI || !mDispatchedAsBlocking)
michael@0 1711 return;
michael@0 1712
michael@0 1713 uint32_t blockers = 0;
michael@0 1714 nsresult rv = mLoadGroupCI->RemoveBlockingTransaction(&blockers);
michael@0 1715
michael@0 1716 LOG(("nsHttpTransaction removing blocking channel %p from "
michael@0 1717 "loadgroup %p. %d blockers remain.\n", this,
michael@0 1718 mLoadGroupCI.get(), blockers));
michael@0 1719
michael@0 1720 if (NS_SUCCEEDED(rv) && !blockers) {
michael@0 1721 LOG(("nsHttpTransaction %p triggering release of blocked channels.\n",
michael@0 1722 this));
michael@0 1723 gHttpHandler->ConnMgr()->ProcessPendingQ();
michael@0 1724 }
michael@0 1725
michael@0 1726 mDispatchedAsBlocking = false;
michael@0 1727 }
michael@0 1728
michael@0 1729 void
michael@0 1730 nsHttpTransaction::ReleaseBlockingTransaction()
michael@0 1731 {
michael@0 1732 RemoveDispatchedAsBlocking();
michael@0 1733 mLoadGroupCI = nullptr;
michael@0 1734 }
michael@0 1735
michael@0 1736 //-----------------------------------------------------------------------------
michael@0 1737 // nsHttpTransaction deletion event
michael@0 1738 //-----------------------------------------------------------------------------
michael@0 1739
michael@0 1740 class nsDeleteHttpTransaction : public nsRunnable {
michael@0 1741 public:
michael@0 1742 nsDeleteHttpTransaction(nsHttpTransaction *trans)
michael@0 1743 : mTrans(trans)
michael@0 1744 {}
michael@0 1745
michael@0 1746 NS_IMETHOD Run()
michael@0 1747 {
michael@0 1748 delete mTrans;
michael@0 1749 return NS_OK;
michael@0 1750 }
michael@0 1751 private:
michael@0 1752 nsHttpTransaction *mTrans;
michael@0 1753 };
michael@0 1754
michael@0 1755 void
michael@0 1756 nsHttpTransaction::DeleteSelfOnConsumerThread()
michael@0 1757 {
michael@0 1758 LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%p]\n", this));
michael@0 1759
michael@0 1760 bool val;
michael@0 1761 if (!mConsumerTarget ||
michael@0 1762 (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
michael@0 1763 delete this;
michael@0 1764 } else {
michael@0 1765 LOG(("proxying delete to consumer thread...\n"));
michael@0 1766 nsCOMPtr<nsIRunnable> event = new nsDeleteHttpTransaction(this);
michael@0 1767 if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL)))
michael@0 1768 NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
michael@0 1769 }
michael@0 1770 }
michael@0 1771
michael@0 1772 bool
michael@0 1773 nsHttpTransaction::TryToRunPacedRequest()
michael@0 1774 {
michael@0 1775 if (mSubmittedRatePacing)
michael@0 1776 return mPassedRatePacing;
michael@0 1777
michael@0 1778 mSubmittedRatePacing = true;
michael@0 1779 mSynchronousRatePaceRequest = true;
michael@0 1780 gHttpHandler->SubmitPacedRequest(this, getter_AddRefs(mTokenBucketCancel));
michael@0 1781 mSynchronousRatePaceRequest = false;
michael@0 1782 return mPassedRatePacing;
michael@0 1783 }
michael@0 1784
michael@0 1785 void
michael@0 1786 nsHttpTransaction::OnTokenBucketAdmitted()
michael@0 1787 {
michael@0 1788 mPassedRatePacing = true;
michael@0 1789 mTokenBucketCancel = nullptr;
michael@0 1790
michael@0 1791 if (!mSynchronousRatePaceRequest)
michael@0 1792 gHttpHandler->ConnMgr()->ProcessPendingQ(mConnInfo);
michael@0 1793 }
michael@0 1794
michael@0 1795 void
michael@0 1796 nsHttpTransaction::CancelPacing(nsresult reason)
michael@0 1797 {
michael@0 1798 if (mTokenBucketCancel) {
michael@0 1799 mTokenBucketCancel->Cancel(reason);
michael@0 1800 mTokenBucketCancel = nullptr;
michael@0 1801 }
michael@0 1802 }
michael@0 1803
michael@0 1804 //-----------------------------------------------------------------------------
michael@0 1805 // nsHttpTransaction::nsISupports
michael@0 1806 //-----------------------------------------------------------------------------
michael@0 1807
michael@0 1808 NS_IMPL_ADDREF(nsHttpTransaction)
michael@0 1809
michael@0 1810 NS_IMETHODIMP_(MozExternalRefCountType)
michael@0 1811 nsHttpTransaction::Release()
michael@0 1812 {
michael@0 1813 nsrefcnt count;
michael@0 1814 NS_PRECONDITION(0 != mRefCnt, "dup release");
michael@0 1815 count = --mRefCnt;
michael@0 1816 NS_LOG_RELEASE(this, count, "nsHttpTransaction");
michael@0 1817 if (0 == count) {
michael@0 1818 mRefCnt = 1; /* stablize */
michael@0 1819 // it is essential that the transaction be destroyed on the consumer
michael@0 1820 // thread (we could be holding the last reference to our consumer).
michael@0 1821 DeleteSelfOnConsumerThread();
michael@0 1822 return 0;
michael@0 1823 }
michael@0 1824 return count;
michael@0 1825 }
michael@0 1826
michael@0 1827 NS_IMPL_QUERY_INTERFACE(nsHttpTransaction,
michael@0 1828 nsIInputStreamCallback,
michael@0 1829 nsIOutputStreamCallback)
michael@0 1830
michael@0 1831 //-----------------------------------------------------------------------------
michael@0 1832 // nsHttpTransaction::nsIInputStreamCallback
michael@0 1833 //-----------------------------------------------------------------------------
michael@0 1834
michael@0 1835 // called on the socket thread
michael@0 1836 NS_IMETHODIMP
michael@0 1837 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out)
michael@0 1838 {
michael@0 1839 if (mConnection) {
michael@0 1840 mConnection->TransactionHasDataToWrite(this);
michael@0 1841 nsresult rv = mConnection->ResumeSend();
michael@0 1842 if (NS_FAILED(rv))
michael@0 1843 NS_ERROR("ResumeSend failed");
michael@0 1844 }
michael@0 1845 return NS_OK;
michael@0 1846 }
michael@0 1847
michael@0 1848 //-----------------------------------------------------------------------------
michael@0 1849 // nsHttpTransaction::nsIOutputStreamCallback
michael@0 1850 //-----------------------------------------------------------------------------
michael@0 1851
michael@0 1852 // called on the socket thread
michael@0 1853 NS_IMETHODIMP
michael@0 1854 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out)
michael@0 1855 {
michael@0 1856 if (mConnection) {
michael@0 1857 nsresult rv = mConnection->ResumeRecv();
michael@0 1858 if (NS_FAILED(rv))
michael@0 1859 NS_ERROR("ResumeRecv failed");
michael@0 1860 }
michael@0 1861 return NS_OK;
michael@0 1862 }
michael@0 1863
michael@0 1864 // nsHttpTransaction::RestartVerifier
michael@0 1865
michael@0 1866 static bool
michael@0 1867 matchOld(nsHttpResponseHead *newHead, nsCString &old,
michael@0 1868 nsHttpAtom headerAtom)
michael@0 1869 {
michael@0 1870 const char *val;
michael@0 1871
michael@0 1872 val = newHead->PeekHeader(headerAtom);
michael@0 1873 if (val && old.IsEmpty())
michael@0 1874 return false;
michael@0 1875 if (!val && !old.IsEmpty())
michael@0 1876 return false;
michael@0 1877 if (val && !old.Equals(val))
michael@0 1878 return false;
michael@0 1879 return true;
michael@0 1880 }
michael@0 1881
michael@0 1882 bool
michael@0 1883 nsHttpTransaction::RestartVerifier::Verify(int64_t contentLength,
michael@0 1884 nsHttpResponseHead *newHead)
michael@0 1885 {
michael@0 1886 if (mContentLength != contentLength)
michael@0 1887 return false;
michael@0 1888
michael@0 1889 if (newHead->Status() != 200)
michael@0 1890 return false;
michael@0 1891
michael@0 1892 if (!matchOld(newHead, mContentRange, nsHttp::Content_Range))
michael@0 1893 return false;
michael@0 1894
michael@0 1895 if (!matchOld(newHead, mLastModified, nsHttp::Last_Modified))
michael@0 1896 return false;
michael@0 1897
michael@0 1898 if (!matchOld(newHead, mETag, nsHttp::ETag))
michael@0 1899 return false;
michael@0 1900
michael@0 1901 if (!matchOld(newHead, mContentEncoding, nsHttp::Content_Encoding))
michael@0 1902 return false;
michael@0 1903
michael@0 1904 if (!matchOld(newHead, mTransferEncoding, nsHttp::Transfer_Encoding))
michael@0 1905 return false;
michael@0 1906
michael@0 1907 return true;
michael@0 1908 }
michael@0 1909
michael@0 1910 void
michael@0 1911 nsHttpTransaction::RestartVerifier::Set(int64_t contentLength,
michael@0 1912 nsHttpResponseHead *head)
michael@0 1913 {
michael@0 1914 if (mSetup)
michael@0 1915 return;
michael@0 1916
michael@0 1917 // If mSetup does not transition to true RestartInPogress() is later
michael@0 1918 // forbidden
michael@0 1919
michael@0 1920 // Only RestartInProgress with 200 response code
michael@0 1921 if (head->Status() != 200)
michael@0 1922 return;
michael@0 1923
michael@0 1924 mContentLength = contentLength;
michael@0 1925
michael@0 1926 if (head) {
michael@0 1927 const char *val;
michael@0 1928 val = head->PeekHeader(nsHttp::ETag);
michael@0 1929 if (val)
michael@0 1930 mETag.Assign(val);
michael@0 1931 val = head->PeekHeader(nsHttp::Last_Modified);
michael@0 1932 if (val)
michael@0 1933 mLastModified.Assign(val);
michael@0 1934 val = head->PeekHeader(nsHttp::Content_Range);
michael@0 1935 if (val)
michael@0 1936 mContentRange.Assign(val);
michael@0 1937 val = head->PeekHeader(nsHttp::Content_Encoding);
michael@0 1938 if (val)
michael@0 1939 mContentEncoding.Assign(val);
michael@0 1940 val = head->PeekHeader(nsHttp::Transfer_Encoding);
michael@0 1941 if (val)
michael@0 1942 mTransferEncoding.Assign(val);
michael@0 1943
michael@0 1944 // We can only restart with any confidence if we have a stored etag or
michael@0 1945 // last-modified header
michael@0 1946 if (mETag.IsEmpty() && mLastModified.IsEmpty())
michael@0 1947 return;
michael@0 1948
michael@0 1949 mSetup = true;
michael@0 1950 }
michael@0 1951 }
michael@0 1952
michael@0 1953 } // namespace mozilla::net
michael@0 1954 } // namespace mozilla

mercurial