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