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