Wed, 31 Dec 2014 06:09:35 +0100
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 |