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 : /* ***** BEGIN LICENSE BLOCK *****
4 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 : *
6 : * The contents of this file are subject to the Mozilla Public License Version
7 : * 1.1 (the "License"); you may not use this file except in compliance with
8 : * the License. You may obtain a copy of the License at
9 : * http://www.mozilla.org/MPL/
10 : *
11 : * Software distributed under the License is distributed on an "AS IS" basis,
12 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 : * for the specific language governing rights and limitations under the
14 : * License.
15 : *
16 : * The Original Code is Mozilla.
17 : *
18 : * The Initial Developer of the Original Code is
19 : * Netscape Communications.
20 : * Portions created by the Initial Developer are Copyright (C) 2001
21 : * the Initial Developer. All Rights Reserved.
22 : *
23 : * Contributor(s):
24 : * Darin Fisher <darin@netscape.com> (original author)
25 : * Andreas M. Schneider <clarence@clarence.de>
26 : *
27 : * Alternatively, the contents of this file may be used under the terms of
28 : * either the GNU General Public License Version 2 or later (the "GPL"), or
29 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 : * in which case the provisions of the GPL or the LGPL are applicable instead
31 : * of those above. If you wish to allow use of your version of this file only
32 : * under the terms of either the GPL or the LGPL, and not to allow others to
33 : * use your version of this file under the terms of the MPL, indicate your
34 : * decision by deleting the provisions above and replace them with the notice
35 : * and other provisions required by the GPL or the LGPL. If you do not delete
36 : * the provisions above, a recipient may use your version of this file under
37 : * the terms of any one of the MPL, the GPL or the LGPL.
38 : *
39 : * ***** END LICENSE BLOCK ***** */
40 :
41 : #include "base/basictypes.h"
42 :
43 : #include "nsIOService.h"
44 : #include "nsHttpHandler.h"
45 : #include "nsHttpTransaction.h"
46 : #include "nsHttpConnection.h"
47 : #include "nsHttpRequestHead.h"
48 : #include "nsHttpResponseHead.h"
49 : #include "nsHttpChunkedDecoder.h"
50 : #include "nsTransportUtils.h"
51 : #include "nsNetUtil.h"
52 : #include "nsProxyRelease.h"
53 : #include "nsIOService.h"
54 : #include "nsAtomicRefcnt.h"
55 :
56 : #include "nsISeekableStream.h"
57 : #include "nsISocketTransport.h"
58 : #include "nsMultiplexInputStream.h"
59 : #include "nsStringStream.h"
60 :
61 : #include "nsComponentManagerUtils.h" // do_CreateInstance
62 : #include "nsServiceManagerUtils.h" // do_GetService
63 : #include "nsIHttpActivityObserver.h"
64 :
65 : #include "mozilla/FunctionTimer.h"
66 :
67 : //-----------------------------------------------------------------------------
68 :
69 : #ifdef DEBUG
70 : // defined by the socket transport service while active
71 : extern PRThread *gSocketThread;
72 : #endif
73 :
74 : //-----------------------------------------------------------------------------
75 :
76 : static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
77 :
78 : // Place a limit on how much non-compliant HTTP can be skipped while
79 : // looking for a response header
80 : #define MAX_INVALID_RESPONSE_BODY_SIZE (1024 * 128)
81 :
82 : //-----------------------------------------------------------------------------
83 : // helpers
84 : //-----------------------------------------------------------------------------
85 :
86 : #if defined(PR_LOGGING)
87 : static void
88 0 : LogHeaders(const char *lines)
89 : {
90 0 : nsCAutoString buf;
91 : char *p;
92 0 : while ((p = PL_strstr(lines, "\r\n")) != nsnull) {
93 0 : buf.Assign(lines, p - lines);
94 0 : if (PL_strcasestr(buf.get(), "authorization: ") != nsnull) {
95 0 : char *p = PL_strchr(PL_strchr(buf.get(), ' ')+1, ' ');
96 0 : while (*++p) *p = '*';
97 : }
98 0 : LOG3((" %s\n", buf.get()));
99 0 : lines = p + 2;
100 : }
101 0 : }
102 : #endif
103 :
104 : //-----------------------------------------------------------------------------
105 : // nsHttpTransaction <public>
106 : //-----------------------------------------------------------------------------
107 :
108 2989 : nsHttpTransaction::nsHttpTransaction()
109 : : mRequestSize(0)
110 : , mConnection(nsnull)
111 : , mConnInfo(nsnull)
112 : , mRequestHead(nsnull)
113 : , mResponseHead(nsnull)
114 : , mContentLength(-1)
115 : , mContentRead(0)
116 : , mInvalidResponseBytesRead(0)
117 : , mChunkedDecoder(nsnull)
118 : , mStatus(NS_OK)
119 : , mPriority(0)
120 : , mRestartCount(0)
121 : , mCaps(0)
122 : , mClosed(false)
123 : , mConnected(false)
124 : , mHaveStatusLine(false)
125 : , mHaveAllHeaders(false)
126 : , mTransactionDone(false)
127 : , mResponseIsComplete(false)
128 : , mDidContentStart(false)
129 : , mNoContent(false)
130 : , mSentData(false)
131 : , mReceivedData(false)
132 : , mStatusEventPending(false)
133 : , mHasRequestBody(false)
134 : , mSSLConnectFailed(false)
135 : , mHttpResponseMatched(false)
136 2989 : , mPreserveStream(false)
137 : {
138 2989 : LOG(("Creating nsHttpTransaction @%x\n", this));
139 2989 : }
140 :
141 8931 : nsHttpTransaction::~nsHttpTransaction()
142 : {
143 2977 : LOG(("Destroying nsHttpTransaction @%x\n", this));
144 :
145 2977 : NS_IF_RELEASE(mConnection);
146 2977 : NS_IF_RELEASE(mConnInfo);
147 :
148 2977 : delete mResponseHead;
149 2977 : delete mChunkedDecoder;
150 11908 : }
151 :
152 : nsresult
153 2989 : nsHttpTransaction::Init(PRUint8 caps,
154 : nsHttpConnectionInfo *cinfo,
155 : nsHttpRequestHead *requestHead,
156 : nsIInputStream *requestBody,
157 : bool requestBodyHasHeaders,
158 : nsIEventTarget *target,
159 : nsIInterfaceRequestor *callbacks,
160 : nsITransportEventSink *eventsink,
161 : nsIAsyncInputStream **responseBody)
162 : {
163 : NS_TIME_FUNCTION;
164 :
165 : nsresult rv;
166 :
167 2989 : LOG(("nsHttpTransaction::Init [this=%x caps=%x]\n", this, caps));
168 :
169 2989 : NS_ASSERTION(cinfo, "ouch");
170 2989 : NS_ASSERTION(requestHead, "ouch");
171 2989 : NS_ASSERTION(target, "ouch");
172 :
173 2989 : mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
174 2989 : if (NS_FAILED(rv)) return rv;
175 :
176 : bool activityDistributorActive;
177 2989 : rv = mActivityDistributor->GetIsActive(&activityDistributorActive);
178 2989 : if (NS_SUCCEEDED(rv) && activityDistributorActive) {
179 : // there are some observers registered at activity distributor, gather
180 : // nsISupports for the channel that called Init()
181 8 : mChannel = do_QueryInterface(eventsink);
182 8 : LOG(("nsHttpTransaction::Init() " \
183 : "mActivityDistributor is active " \
184 : "this=%x", this));
185 : } else {
186 : // there is no observer, so don't use it
187 2981 : activityDistributorActive = false;
188 2981 : mActivityDistributor = nsnull;
189 : }
190 :
191 : // create transport event sink proxy. it coalesces all events if and only
192 : // if the activity observer is not active. when the observer is active
193 : // we need not to coalesce any events to get all expected notifications
194 : // of the transaction state, necessary for correct debugging and logging.
195 2989 : rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink),
196 : eventsink, target,
197 2989 : !activityDistributorActive);
198 2989 : if (NS_FAILED(rv)) return rv;
199 :
200 2989 : NS_ADDREF(mConnInfo = cinfo);
201 2989 : mCallbacks = callbacks;
202 2989 : mConsumerTarget = target;
203 2989 : mCaps = caps;
204 :
205 2989 : if (requestHead->Method() == nsHttp::Head)
206 14 : mNoContent = true;
207 :
208 : // Make sure that there is "Content-Length: 0" header in the requestHead
209 : // in case of POST and PUT methods when there is no requestBody and
210 : // requestHead doesn't contain "Transfer-Encoding" header.
211 : //
212 : // RFC1945 section 7.2.2:
213 : // HTTP/1.0 requests containing an entity body must include a valid
214 : // Content-Length header field.
215 : //
216 : // RFC2616 section 4.4:
217 : // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
218 : // containing a message-body MUST include a valid Content-Length header
219 : // field unless the server is known to be HTTP/1.1 compliant.
220 3008 : if ((requestHead->Method() == nsHttp::Post || requestHead->Method() == nsHttp::Put) &&
221 19 : !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) {
222 19 : requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
223 : }
224 :
225 : // grab a weak reference to the request head
226 2989 : mRequestHead = requestHead;
227 :
228 : // make sure we eliminate any proxy specific headers from
229 : // the request if we are talking HTTPS via a SSL tunnel.
230 : bool pruneProxyHeaders =
231 2989 : cinfo->ShouldForceConnectMethod() ||
232 2989 : (cinfo->UsingSSL() && cinfo->UsingHttpProxy());
233 :
234 2989 : mReqHeaderBuf.Truncate();
235 2989 : requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders);
236 :
237 : #if defined(PR_LOGGING)
238 2989 : if (LOG3_ENABLED()) {
239 0 : LOG3(("http request [\n"));
240 0 : LogHeaders(mReqHeaderBuf.get());
241 0 : LOG3(("]\n"));
242 : }
243 : #endif
244 :
245 : // If the request body does not include headers or if there is no request
246 : // body, then we must add the header/body separator manually.
247 2989 : if (!requestBodyHasHeaders || !requestBody)
248 2986 : mReqHeaderBuf.AppendLiteral("\r\n");
249 :
250 : // report the request header
251 2989 : if (mActivityDistributor)
252 8 : mActivityDistributor->ObserveActivity(
253 : mChannel,
254 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
255 : NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,
256 : PR_Now(), LL_ZERO,
257 8 : mReqHeaderBuf);
258 :
259 : // Create a string stream for the request header buf (the stream holds
260 : // a non-owning reference to the request header data, so we MUST keep
261 : // mReqHeaderBuf around).
262 5978 : nsCOMPtr<nsIInputStream> headers;
263 2989 : rv = NS_NewByteInputStream(getter_AddRefs(headers),
264 : mReqHeaderBuf.get(),
265 2989 : mReqHeaderBuf.Length());
266 2989 : if (NS_FAILED(rv)) return rv;
267 :
268 2989 : if (requestBody) {
269 385 : mHasRequestBody = true;
270 :
271 : // wrap the headers and request body in a multiplexed input stream.
272 : nsCOMPtr<nsIMultiplexInputStream> multi =
273 770 : do_CreateInstance(kMultiplexInputStream, &rv);
274 385 : if (NS_FAILED(rv)) return rv;
275 :
276 385 : rv = multi->AppendStream(headers);
277 385 : if (NS_FAILED(rv)) return rv;
278 :
279 385 : rv = multi->AppendStream(requestBody);
280 385 : if (NS_FAILED(rv)) return rv;
281 :
282 : // wrap the multiplexed input stream with a buffered input stream, so
283 : // that we write data in the largest chunks possible. this is actually
284 : // necessary to workaround some common server bugs (see bug 137155).
285 385 : rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi,
286 385 : nsIOService::gDefaultSegmentSize);
287 385 : if (NS_FAILED(rv)) return rv;
288 : }
289 : else
290 2604 : mRequestStream = headers;
291 :
292 2989 : rv = mRequestStream->Available(&mRequestSize);
293 2989 : if (NS_FAILED(rv)) return rv;
294 :
295 : // create pipe for response stream
296 2989 : rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
297 2989 : getter_AddRefs(mPipeOut),
298 : true, true,
299 : nsIOService::gDefaultSegmentSize,
300 2989 : nsIOService::gDefaultSegmentCount);
301 2989 : if (NS_FAILED(rv)) return rv;
302 :
303 2989 : NS_ADDREF(*responseBody = mPipeIn);
304 2989 : return NS_OK;
305 : }
306 :
307 : nsAHttpConnection *
308 3173 : nsHttpTransaction::Connection()
309 : {
310 3173 : return mConnection;
311 : }
312 :
313 : nsHttpResponseHead *
314 2819 : nsHttpTransaction::TakeResponseHead()
315 : {
316 2819 : if (!mHaveAllHeaders) {
317 0 : NS_WARNING("response headers not available or incomplete");
318 0 : return nsnull;
319 : }
320 :
321 2819 : nsHttpResponseHead *head = mResponseHead;
322 2819 : mResponseHead = nsnull;
323 2819 : return head;
324 : }
325 :
326 : void
327 0 : nsHttpTransaction::SetSSLConnectFailed()
328 : {
329 0 : mSSLConnectFailed = true;
330 0 : }
331 :
332 : nsHttpRequestHead *
333 0 : nsHttpTransaction::RequestHead()
334 : {
335 0 : return mRequestHead;
336 : }
337 :
338 : PRUint32
339 2975 : nsHttpTransaction::Http1xTransactionCount()
340 : {
341 2975 : return 1;
342 : }
343 :
344 : nsresult
345 0 : nsHttpTransaction::TakeSubTransactions(
346 : nsTArray<nsRefPtr<nsAHttpTransaction> > &outTransactions)
347 : {
348 0 : return NS_ERROR_NOT_IMPLEMENTED;
349 : }
350 :
351 : //----------------------------------------------------------------------------
352 : // nsHttpTransaction::nsAHttpTransaction
353 : //----------------------------------------------------------------------------
354 :
355 : void
356 2975 : nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
357 : {
358 2975 : NS_IF_RELEASE(mConnection);
359 2975 : NS_IF_ADDREF(mConnection = conn);
360 2975 : }
361 :
362 : void
363 5951 : nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb,
364 : nsIEventTarget **target)
365 : {
366 5951 : NS_IF_ADDREF(*cb = mCallbacks);
367 5951 : if (target)
368 5951 : NS_IF_ADDREF(*target = mConsumerTarget);
369 5951 : }
370 :
371 : void
372 23147 : nsHttpTransaction::OnTransportStatus(nsITransport* transport,
373 : nsresult status, PRUint64 progress)
374 : {
375 23147 : LOG(("nsHttpTransaction::OnSocketStatus [this=%x status=%x progress=%llu]\n",
376 : this, status, progress));
377 :
378 23147 : if (TimingEnabled()) {
379 16 : if (status == nsISocketTransport::STATUS_RESOLVING) {
380 2 : mTimings.domainLookupStart = mozilla::TimeStamp::Now();
381 14 : } else if (status == nsISocketTransport::STATUS_RESOLVED) {
382 2 : mTimings.domainLookupEnd = mozilla::TimeStamp::Now();
383 12 : } else if (status == nsISocketTransport::STATUS_CONNECTING_TO) {
384 2 : mTimings.connectStart = mozilla::TimeStamp::Now();
385 10 : } else if (status == nsISocketTransport::STATUS_CONNECTED_TO) {
386 2 : mTimings.connectEnd = mozilla::TimeStamp::Now();
387 : }
388 : }
389 :
390 23147 : if (!mTransportSink)
391 0 : return;
392 :
393 23147 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
394 :
395 : // Need to do this before the STATUS_RECEIVING_FROM check below, to make
396 : // sure that the activity distributor gets told about all status events.
397 23147 : if (mActivityDistributor) {
398 : // upon STATUS_WAITING_FOR; report request body sent
399 60 : if ((mHasRequestBody) &&
400 : (status == nsISocketTransport::STATUS_WAITING_FOR))
401 0 : mActivityDistributor->ObserveActivity(
402 : mChannel,
403 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
404 : NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,
405 0 : PR_Now(), LL_ZERO, EmptyCString());
406 :
407 : // report the status and progress
408 60 : mActivityDistributor->ObserveActivity(
409 : mChannel,
410 : NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
411 : static_cast<PRUint32>(status),
412 : PR_Now(),
413 : progress,
414 60 : EmptyCString());
415 : }
416 :
417 : // nsHttpChannel synthesizes progress events in OnDataAvailable
418 23147 : if (status == nsISocketTransport::STATUS_RECEIVING_FROM)
419 5721 : return;
420 :
421 : PRUint64 progressMax;
422 :
423 17426 : if (status == nsISocketTransport::STATUS_SENDING_TO) {
424 : // suppress progress when only writing request headers
425 2836 : if (!mHasRequestBody)
426 2451 : return;
427 :
428 770 : nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
429 385 : NS_ASSERTION(seekable, "Request stream isn't seekable?!?");
430 :
431 385 : PRInt64 prog = 0;
432 385 : seekable->Tell(&prog);
433 385 : progress = prog;
434 :
435 : // when uploading, we include the request headers in the progress
436 : // notifications.
437 385 : progressMax = mRequestSize; // XXX mRequestSize is 32-bit!
438 : }
439 : else {
440 14590 : progress = LL_ZERO;
441 14590 : progressMax = 0;
442 : }
443 :
444 14975 : mTransportSink->OnTransportStatus(transport, status, progress, progressMax);
445 : }
446 :
447 : bool
448 10 : nsHttpTransaction::IsDone()
449 : {
450 10 : return mTransactionDone;
451 : }
452 :
453 : nsresult
454 2977 : nsHttpTransaction::Status()
455 : {
456 2977 : return mStatus;
457 : }
458 :
459 : PRUint32
460 0 : nsHttpTransaction::Available()
461 : {
462 : PRUint32 size;
463 0 : if (NS_FAILED(mRequestStream->Available(&size)))
464 0 : size = 0;
465 0 : return size;
466 : }
467 :
468 : NS_METHOD
469 2978 : nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream,
470 : void *closure,
471 : const char *buf,
472 : PRUint32 offset,
473 : PRUint32 count,
474 : PRUint32 *countRead)
475 : {
476 2978 : nsHttpTransaction *trans = (nsHttpTransaction *) closure;
477 2978 : nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
478 2978 : if (NS_FAILED(rv)) return rv;
479 :
480 2836 : if (trans->TimingEnabled() && trans->mTimings.requestStart.IsNull()) {
481 : // First data we're sending -> this is requestStart
482 2 : trans->mTimings.requestStart = mozilla::TimeStamp::Now();
483 : }
484 2836 : trans->mSentData = true;
485 2836 : return NS_OK;
486 : }
487 :
488 : nsresult
489 5812 : nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
490 : PRUint32 count, PRUint32 *countRead)
491 : {
492 5812 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
493 :
494 5812 : if (mTransactionDone) {
495 0 : *countRead = 0;
496 0 : return mStatus;
497 : }
498 :
499 5812 : if (!mConnected) {
500 2975 : mConnected = true;
501 2975 : mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
502 : }
503 :
504 5812 : mReader = reader;
505 :
506 5812 : nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
507 :
508 5812 : mReader = nsnull;
509 :
510 : // if read would block then we need to AsyncWait on the request stream.
511 : // have callback occur on socket thread so we stay synchronized.
512 5812 : if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
513 : nsCOMPtr<nsIAsyncInputStream> asyncIn =
514 0 : do_QueryInterface(mRequestStream);
515 0 : if (asyncIn) {
516 0 : nsCOMPtr<nsIEventTarget> target;
517 0 : gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
518 0 : if (target)
519 0 : asyncIn->AsyncWait(this, 0, 0, target);
520 : else {
521 0 : NS_ERROR("no socket thread event target");
522 0 : rv = NS_ERROR_UNEXPECTED;
523 : }
524 : }
525 : }
526 :
527 5812 : return rv;
528 : }
529 :
530 : NS_METHOD
531 11122 : nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream,
532 : void *closure,
533 : char *buf,
534 : PRUint32 offset,
535 : PRUint32 count,
536 : PRUint32 *countWritten)
537 : {
538 11122 : nsHttpTransaction *trans = (nsHttpTransaction *) closure;
539 :
540 11122 : if (trans->mTransactionDone)
541 2482 : return NS_BASE_STREAM_CLOSED; // stop iterating
542 :
543 8640 : if (trans->TimingEnabled() && trans->mTimings.responseStart.IsNull()) {
544 2 : trans->mTimings.responseStart = mozilla::TimeStamp::Now();
545 : }
546 :
547 : nsresult rv;
548 : //
549 : // OK, now let the caller fill this segment with data.
550 : //
551 8640 : rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
552 8640 : if (NS_FAILED(rv)) return rv; // caller didn't want to write anything
553 :
554 5721 : NS_ASSERTION(*countWritten > 0, "bad writer");
555 5721 : trans->mReceivedData = true;
556 :
557 : // Let the transaction "play" with the buffer. It is free to modify
558 : // the contents of the buffer and/or modify countWritten.
559 : // - Bytes in HTTP headers don't count towards countWritten, so the input
560 : // side of pipe (aka nsHttpChannel's mTransactionPump) won't hit
561 : // OnInputStreamReady until all headers have been parsed.
562 : //
563 5721 : rv = trans->ProcessData(buf, *countWritten, countWritten);
564 5721 : if (NS_FAILED(rv))
565 11 : trans->Close(rv);
566 :
567 5721 : return rv; // failure code only stops WriteSegments; it is not propagated.
568 : }
569 :
570 : nsresult
571 11025 : nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
572 : PRUint32 count, PRUint32 *countWritten)
573 : {
574 11025 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
575 :
576 11025 : if (mTransactionDone)
577 2811 : return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
578 :
579 8214 : mWriter = writer;
580 :
581 8214 : nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
582 :
583 8214 : mWriter = nsnull;
584 :
585 : // if pipe would block then we need to AsyncWait on it. have callback
586 : // occur on socket thread so we stay synchronized.
587 8214 : if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
588 0 : nsCOMPtr<nsIEventTarget> target;
589 0 : gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
590 0 : if (target)
591 0 : mPipeOut->AsyncWait(this, 0, 0, target);
592 : else {
593 0 : NS_ERROR("no socket thread event target");
594 0 : rv = NS_ERROR_UNEXPECTED;
595 : }
596 : }
597 :
598 8214 : return rv;
599 : }
600 :
601 : void
602 3141 : nsHttpTransaction::Close(nsresult reason)
603 : {
604 3141 : LOG(("nsHttpTransaction::Close [this=%x reason=%x]\n", this, reason));
605 :
606 3141 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
607 :
608 3141 : if (mClosed) {
609 164 : LOG((" already closed\n"));
610 164 : return;
611 : }
612 :
613 2977 : if (mActivityDistributor) {
614 : // report the reponse is complete if not already reported
615 8 : if (!mResponseIsComplete)
616 0 : mActivityDistributor->ObserveActivity(
617 : mChannel,
618 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
619 : NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
620 : PR_Now(),
621 : static_cast<PRUint64>(mContentRead),
622 0 : EmptyCString());
623 :
624 : // report that this transaction is closing
625 8 : mActivityDistributor->ObserveActivity(
626 : mChannel,
627 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
628 : NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
629 8 : PR_Now(), LL_ZERO, EmptyCString());
630 : }
631 :
632 : // we must no longer reference the connection! find out if the
633 : // connection was being reused before letting it go.
634 2977 : bool connReused = false;
635 2977 : if (mConnection)
636 2975 : connReused = mConnection->IsReused();
637 2977 : mConnected = false;
638 :
639 : //
640 : // if the connection was reset or closed before we wrote any part of the
641 : // request or if we wrote the request but didn't receive any part of the
642 : // response and the connection was being reused, then we can (and really
643 : // should) assume that we wrote to a stale connection and we must therefore
644 : // repeat the request over a new connection.
645 : //
646 : // NOTE: the conditions under which we will automatically retry the HTTP
647 : // request have to be carefully selected to avoid duplication of the
648 : // request from the point-of-view of the server. such duplication could
649 : // have dire consequences including repeated purchases, etc.
650 : //
651 : // NOTE: because of the way SSL proxy CONNECT is implemented, it is
652 : // possible that the transaction may have received data without having
653 : // sent any data. for this reason, mSendData == FALSE does not imply
654 : // mReceivedData == FALSE. (see bug 203057 for more info.)
655 : //
656 2977 : if (reason == NS_ERROR_NET_RESET || reason == NS_OK) {
657 2814 : if (!mReceivedData && (!mSentData || connReused)) {
658 : // if restarting fails, then we must proceed to close the pipe,
659 : // which will notify the channel that the transaction failed.
660 0 : if (NS_SUCCEEDED(Restart()))
661 0 : return;
662 : }
663 : }
664 :
665 2977 : bool relConn = true;
666 2977 : if (NS_SUCCEEDED(reason)) {
667 : // the server has not sent the final \r\n terminating the header
668 : // section, and there may still be a header line unparsed. let's make
669 : // sure we parse the remaining header line, and then hopefully, the
670 : // response will be usable (see bug 88792).
671 2814 : if (!mHaveAllHeaders) {
672 0 : char data = '\n';
673 : PRUint32 unused;
674 0 : ParseHead(&data, 1, &unused);
675 :
676 0 : if (mResponseHead->Version() == NS_HTTP_VERSION_0_9) {
677 : // Reject 0 byte HTTP/0.9 Responses - bug 423506
678 0 : LOG(("nsHttpTransaction::Close %p 0 Byte 0.9 Response", this));
679 0 : reason = NS_ERROR_NET_RESET;
680 : }
681 : }
682 :
683 : // honor the sticky connection flag...
684 2814 : if (mCaps & NS_HTTP_STICKY_CONNECTION)
685 23 : relConn = false;
686 : }
687 2977 : if (relConn && mConnection)
688 2952 : NS_RELEASE(mConnection);
689 :
690 2977 : mStatus = reason;
691 2977 : mTransactionDone = true; // forcibly flag the transaction as complete
692 2977 : mClosed = true;
693 :
694 : // release some resources that we no longer need
695 2977 : mRequestStream = nsnull;
696 2977 : mReqHeaderBuf.Truncate();
697 2977 : mLineBuf.Truncate();
698 2977 : if (mChunkedDecoder) {
699 4 : delete mChunkedDecoder;
700 4 : mChunkedDecoder = nsnull;
701 : }
702 :
703 : // closing this pipe triggers the channel's OnStopRequest method.
704 2977 : mPipeOut->CloseWithStatus(reason);
705 : }
706 :
707 : //-----------------------------------------------------------------------------
708 : // nsHttpTransaction <private>
709 : //-----------------------------------------------------------------------------
710 :
711 : nsresult
712 0 : nsHttpTransaction::Restart()
713 : {
714 0 : NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
715 :
716 : // limit the number of restart attempts - bug 92224
717 0 : if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
718 0 : LOG(("reached max request attempts, failing transaction @%x\n", this));
719 0 : return NS_ERROR_NET_RESET;
720 : }
721 :
722 0 : LOG(("restarting transaction @%x\n", this));
723 :
724 : // rewind streams in case we already wrote out the request
725 0 : nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
726 0 : if (seekable)
727 0 : seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
728 :
729 : // clear old connection state...
730 0 : mSecurityInfo = 0;
731 0 : NS_IF_RELEASE(mConnection);
732 :
733 : // disable pipelining for the next attempt in case pipelining caused the
734 : // reset. this is being overly cautious since we don't know if pipelining
735 : // was the problem here.
736 0 : mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
737 :
738 0 : return gHttpHandler->InitiateTransaction(this, mPriority);
739 : }
740 :
741 : char *
742 2830 : nsHttpTransaction::LocateHttpStart(char *buf, PRUint32 len,
743 : bool aAllowPartialMatch)
744 : {
745 2830 : NS_ASSERTION(!aAllowPartialMatch || mLineBuf.IsEmpty(), "ouch");
746 :
747 : static const char HTTPHeader[] = "HTTP/1.";
748 : static const PRUint32 HTTPHeaderLen = sizeof(HTTPHeader) - 1;
749 : static const char HTTP2Header[] = "HTTP/2.0";
750 : static const PRUint32 HTTP2HeaderLen = sizeof(HTTP2Header) - 1;
751 :
752 2830 : if (aAllowPartialMatch && (len < HTTPHeaderLen))
753 0 : return (PL_strncasecmp(buf, HTTPHeader, len) == 0) ? buf : nsnull;
754 :
755 : // mLineBuf can contain partial match from previous search
756 2830 : if (!mLineBuf.IsEmpty()) {
757 0 : NS_ASSERTION(mLineBuf.Length() < HTTPHeaderLen, "ouch");
758 0 : PRInt32 checkChars = NS_MIN(len, HTTPHeaderLen - mLineBuf.Length());
759 0 : if (PL_strncasecmp(buf, HTTPHeader + mLineBuf.Length(),
760 0 : checkChars) == 0) {
761 0 : mLineBuf.Append(buf, checkChars);
762 0 : if (mLineBuf.Length() == HTTPHeaderLen) {
763 : // We've found whole HTTPHeader sequence. Return pointer at the
764 : // end of matched sequence since it is stored in mLineBuf.
765 0 : return (buf + checkChars);
766 : }
767 : // Response matches pattern but is still incomplete.
768 0 : return 0;
769 : }
770 : // Previous partial match together with new data doesn't match the
771 : // pattern. Start the search again.
772 0 : mLineBuf.Truncate();
773 : }
774 :
775 2830 : bool firstByte = true;
776 5671 : while (len > 0) {
777 2840 : if (PL_strncasecmp(buf, HTTPHeader, NS_MIN<PRUint32>(len, HTTPHeaderLen)) == 0) {
778 2829 : if (len < HTTPHeaderLen) {
779 : // partial HTTPHeader sequence found
780 : // save partial match to mLineBuf
781 0 : mLineBuf.Assign(buf, len);
782 0 : return 0;
783 : }
784 :
785 : // whole HTTPHeader sequence found
786 2829 : return buf;
787 : }
788 :
789 : // At least "SmarterTools/2.0.3974.16813" generates nonsensical
790 : // HTTP/2.0 responses to our HTTP/1 requests. Treat the minimal case of
791 : // it as HTTP/1.1 to be compatible with old versions of ourselves and
792 : // other browsers
793 :
794 12 : if (firstByte && !mInvalidResponseBytesRead && len >= HTTP2HeaderLen &&
795 1 : (PL_strncasecmp(buf, HTTP2Header, HTTP2HeaderLen) == 0)) {
796 0 : LOG(("nsHttpTransaction:: Identified HTTP/2.0 treating as 1.x\n"));
797 0 : return buf;
798 : }
799 :
800 11 : if (!nsCRT::IsAsciiSpace(*buf))
801 11 : firstByte = false;
802 11 : buf++;
803 11 : len--;
804 : }
805 1 : return 0;
806 : }
807 :
808 : nsresult
809 19019 : nsHttpTransaction::ParseLine(char *line)
810 : {
811 19019 : LOG(("nsHttpTransaction::ParseLine [%s]\n", line));
812 19019 : nsresult rv = NS_OK;
813 :
814 19019 : if (!mHaveStatusLine) {
815 2829 : mResponseHead->ParseStatusLine(line);
816 2829 : mHaveStatusLine = true;
817 : // XXX this should probably never happen
818 2829 : if (mResponseHead->Version() == NS_HTTP_VERSION_0_9)
819 0 : mHaveAllHeaders = true;
820 : }
821 : else {
822 16190 : rv = mResponseHead->ParseHeaderLine(line);
823 : }
824 19019 : return rv;
825 : }
826 :
827 : nsresult
828 21861 : nsHttpTransaction::ParseLineSegment(char *segment, PRUint32 len)
829 : {
830 21861 : NS_PRECONDITION(!mHaveAllHeaders, "already have all headers");
831 :
832 21861 : if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
833 : // trim off the new line char, and if this segment is
834 : // not a continuation of the previous or if we haven't
835 : // parsed the status line yet, then parse the contents
836 : // of mLineBuf.
837 19019 : mLineBuf.Truncate(mLineBuf.Length() - 1);
838 19019 : if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
839 19019 : nsresult rv = ParseLine(mLineBuf.BeginWriting());
840 19019 : mLineBuf.Truncate();
841 19019 : if (NS_FAILED(rv)) {
842 11 : return rv;
843 : }
844 : }
845 : }
846 :
847 : // append segment to mLineBuf...
848 21850 : mLineBuf.Append(segment, len);
849 :
850 : // a line buf with only a new line char signifies the end of headers.
851 21850 : if (mLineBuf.First() == '\n') {
852 2818 : mLineBuf.Truncate();
853 : // discard this response if it is a 100 continue or other 1xx status.
854 2818 : PRUint16 status = mResponseHead->Status();
855 2818 : if ((status != 101) && (status / 100 == 1)) {
856 0 : LOG(("ignoring 1xx response\n"));
857 0 : mHaveStatusLine = false;
858 0 : mHttpResponseMatched = false;
859 0 : mConnection->SetLastTransactionExpectedNoContent(true);
860 0 : mResponseHead->Reset();
861 0 : return NS_OK;
862 : }
863 2818 : mHaveAllHeaders = true;
864 : }
865 21850 : return NS_OK;
866 : }
867 :
868 : nsresult
869 2843 : nsHttpTransaction::ParseHead(char *buf,
870 : PRUint32 count,
871 : PRUint32 *countRead)
872 : {
873 : nsresult rv;
874 : PRUint32 len;
875 : char *eol;
876 :
877 2843 : LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
878 :
879 2843 : *countRead = 0;
880 :
881 2843 : NS_PRECONDITION(!mHaveAllHeaders, "oops");
882 :
883 : // allocate the response head object if necessary
884 2843 : if (!mResponseHead) {
885 2830 : mResponseHead = new nsHttpResponseHead();
886 2830 : if (!mResponseHead)
887 0 : return NS_ERROR_OUT_OF_MEMORY;
888 :
889 : // report that we have a least some of the response
890 2830 : if (mActivityDistributor)
891 8 : mActivityDistributor->ObserveActivity(
892 : mChannel,
893 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
894 : NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,
895 8 : PR_Now(), LL_ZERO, EmptyCString());
896 : }
897 :
898 2843 : if (!mHttpResponseMatched) {
899 : // Normally we insist on seeing HTTP/1.x in the first few bytes,
900 : // but if we are on a persistent connection and the previous transaction
901 : // was not supposed to have any content then we need to be prepared
902 : // to skip over a response body that the server may have sent even
903 : // though it wasn't allowed.
904 2830 : if (!mConnection || !mConnection->LastTransactionExpectedNoContent()) {
905 : // tolerate only minor junk before the status line
906 2830 : mHttpResponseMatched = true;
907 2830 : char *p = LocateHttpStart(buf, NS_MIN<PRUint32>(count, 11), true);
908 2830 : if (!p) {
909 : // Treat any 0.9 style response of a put as a failure.
910 1 : if (mRequestHead->Method() == nsHttp::Put)
911 0 : return NS_ERROR_ABORT;
912 :
913 1 : mResponseHead->ParseStatusLine("");
914 1 : mHaveStatusLine = true;
915 1 : mHaveAllHeaders = true;
916 1 : return NS_OK;
917 : }
918 2829 : if (p > buf) {
919 : // skip over the junk
920 0 : mInvalidResponseBytesRead += p - buf;
921 0 : *countRead = p - buf;
922 0 : buf = p;
923 : }
924 : }
925 : else {
926 0 : char *p = LocateHttpStart(buf, count, false);
927 0 : if (p) {
928 0 : mInvalidResponseBytesRead += p - buf;
929 0 : *countRead = p - buf;
930 0 : buf = p;
931 0 : mHttpResponseMatched = true;
932 : } else {
933 0 : mInvalidResponseBytesRead += count;
934 0 : *countRead = count;
935 0 : if (mInvalidResponseBytesRead > MAX_INVALID_RESPONSE_BODY_SIZE) {
936 0 : LOG(("nsHttpTransaction::ParseHead() "
937 : "Cannot find Response Header\n"));
938 : // cannot go back and call this 0.9 anymore as we
939 : // have thrown away a lot of the leading junk
940 0 : return NS_ERROR_ABORT;
941 : }
942 0 : return NS_OK;
943 : }
944 : }
945 : }
946 : // otherwise we can assume that we don't have a HTTP/0.9 response.
947 :
948 2842 : NS_ABORT_IF_FALSE (mHttpResponseMatched, "inconsistent");
949 24703 : while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) != nsnull) {
950 : // found line in range [buf:eol]
951 21848 : len = eol - buf + 1;
952 :
953 21848 : *countRead += len;
954 :
955 : // actually, the line is in the range [buf:eol-1]
956 21848 : if ((eol > buf) && (*(eol-1) == '\r'))
957 21848 : len--;
958 :
959 21848 : buf[len-1] = '\n';
960 21848 : rv = ParseLineSegment(buf, len);
961 21848 : if (NS_FAILED(rv))
962 11 : return rv;
963 :
964 21837 : if (mHaveAllHeaders)
965 2818 : return NS_OK;
966 :
967 : // skip over line
968 19019 : buf = eol + 1;
969 :
970 19019 : if (!mHttpResponseMatched) {
971 : // a 100 class response has caused us to throw away that set of
972 : // response headers and look for the next response
973 0 : return NS_ERROR_NET_INTERRUPT;
974 : }
975 : }
976 :
977 : // do something about a partial header line
978 13 : if (!mHaveAllHeaders && (len = count - *countRead)) {
979 13 : *countRead = count;
980 : // ignore a trailing carriage return, and don't bother calling
981 : // ParseLineSegment if buf only contains a carriage return.
982 13 : if ((buf[len-1] == '\r') && (--len == 0))
983 0 : return NS_OK;
984 13 : rv = ParseLineSegment(buf, len);
985 13 : if (NS_FAILED(rv))
986 0 : return rv;
987 : }
988 13 : return NS_OK;
989 : }
990 :
991 : // called on the socket thread
992 : nsresult
993 2819 : nsHttpTransaction::HandleContentStart()
994 : {
995 2819 : LOG(("nsHttpTransaction::HandleContentStart [this=%x]\n", this));
996 :
997 2819 : if (mResponseHead) {
998 : #if defined(PR_LOGGING)
999 2819 : if (LOG3_ENABLED()) {
1000 0 : LOG3(("http response [\n"));
1001 0 : nsCAutoString headers;
1002 0 : mResponseHead->Flatten(headers, false);
1003 0 : LogHeaders(headers.get());
1004 0 : LOG3(("]\n"));
1005 : }
1006 : #endif
1007 : // notify the connection, give it a chance to cause a reset.
1008 2819 : bool reset = false;
1009 2819 : mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
1010 :
1011 : // looks like we should ignore this response, resetting...
1012 2819 : if (reset) {
1013 0 : LOG(("resetting transaction's response head\n"));
1014 0 : mHaveAllHeaders = false;
1015 0 : mHaveStatusLine = false;
1016 0 : mReceivedData = false;
1017 0 : mSentData = false;
1018 0 : mHttpResponseMatched = false;
1019 0 : mResponseHead->Reset();
1020 : // wait to be called again...
1021 0 : return NS_OK;
1022 : }
1023 :
1024 : // check if this is a no-content response
1025 2819 : switch (mResponseHead->Status()) {
1026 : case 101:
1027 0 : mPreserveStream = true; // fall through to other no content
1028 : case 204:
1029 : case 205:
1030 : case 304:
1031 26 : mNoContent = true;
1032 26 : LOG(("this response should not contain a body.\n"));
1033 26 : break;
1034 : }
1035 2819 : mConnection->SetLastTransactionExpectedNoContent(mNoContent);
1036 :
1037 2819 : if (mNoContent)
1038 40 : mContentLength = 0;
1039 : else {
1040 : // grab the content-length from the response headers
1041 2779 : mContentLength = mResponseHead->ContentLength();
1042 :
1043 : // handle chunked encoding here, so we'll know immediately when
1044 : // we're done with the socket. please note that _all_ other
1045 : // decoding is done when the channel receives the content data
1046 : // so as not to block the socket transport thread too much.
1047 : // ignore chunked responses from HTTP/1.0 servers and proxies.
1048 5526 : if (mResponseHead->Version() >= NS_HTTP_VERSION_1_1 &&
1049 2747 : mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
1050 : // we only support the "chunked" transfer encoding right now.
1051 4 : mChunkedDecoder = new nsHttpChunkedDecoder();
1052 4 : if (!mChunkedDecoder)
1053 0 : return NS_ERROR_OUT_OF_MEMORY;
1054 4 : LOG(("chunked decoder created\n"));
1055 : // Ignore server specified Content-Length.
1056 4 : mContentLength = -1;
1057 : }
1058 : #if defined(PR_LOGGING)
1059 2775 : else if (mContentLength == PRInt64(-1))
1060 9 : LOG(("waiting for the server to close the connection.\n"));
1061 : #endif
1062 : }
1063 : }
1064 :
1065 2819 : mDidContentStart = true;
1066 2819 : return NS_OK;
1067 : }
1068 :
1069 : // called on the socket thread
1070 : nsresult
1071 5697 : nsHttpTransaction::HandleContent(char *buf,
1072 : PRUint32 count,
1073 : PRUint32 *contentRead,
1074 : PRUint32 *contentRemaining)
1075 : {
1076 : nsresult rv;
1077 :
1078 5697 : LOG(("nsHttpTransaction::HandleContent [this=%x count=%u]\n", this, count));
1079 :
1080 5697 : *contentRead = 0;
1081 5697 : *contentRemaining = 0;
1082 :
1083 5697 : NS_ASSERTION(mConnection, "no connection");
1084 :
1085 5697 : if (!mDidContentStart) {
1086 2819 : rv = HandleContentStart();
1087 2819 : if (NS_FAILED(rv)) return rv;
1088 : // Do not write content to the pipe if we haven't started streaming yet
1089 2819 : if (!mDidContentStart)
1090 0 : return NS_OK;
1091 : }
1092 :
1093 5697 : if (mChunkedDecoder) {
1094 : // give the buf over to the chunked decoder so it can reformat the
1095 : // data and tell us how much is really there.
1096 8 : rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, contentRemaining);
1097 8 : if (NS_FAILED(rv)) return rv;
1098 : }
1099 5689 : else if (mContentLength >= PRInt64(0)) {
1100 : // HTTP/1.0 servers have been known to send erroneous Content-Length
1101 : // headers. So, unless the connection is persistent, we must make
1102 : // allowances for a possibly invalid Content-Length header. Thus, if
1103 : // NOT persistent, we simply accept everything in |buf|.
1104 5530 : if (mConnection->IsPersistent() || mPreserveStream) {
1105 0 : PRInt64 remaining = mContentLength - mContentRead;
1106 0 : *contentRead = PRUint32(NS_MIN<PRInt64>(count, remaining));
1107 0 : *contentRemaining = count - *contentRead;
1108 : }
1109 : else {
1110 5530 : *contentRead = count;
1111 : // mContentLength might need to be increased...
1112 5530 : PRInt64 position = mContentRead + PRInt64(count);
1113 5530 : if (position > mContentLength) {
1114 0 : mContentLength = position;
1115 : //mResponseHead->SetContentLength(mContentLength);
1116 : }
1117 : }
1118 : }
1119 : else {
1120 : // when we are just waiting for the server to close the connection...
1121 : // (no explicit content-length given)
1122 159 : *contentRead = count;
1123 : }
1124 :
1125 5697 : if (*contentRead) {
1126 : // update count of content bytes read and report progress...
1127 2908 : mContentRead += *contentRead;
1128 : /* when uncommenting, take care of 64-bit integers w/ NS_MAX...
1129 : if (mProgressSink)
1130 : mProgressSink->OnProgress(nsnull, nsnull, mContentRead, NS_MAX(0, mContentLength));
1131 : */
1132 : }
1133 :
1134 5697 : LOG(("nsHttpTransaction::HandleContent [this=%x count=%u read=%u mContentRead=%lld mContentLength=%lld]\n",
1135 : this, count, *contentRead, mContentRead, mContentLength));
1136 :
1137 : // check for end-of-file
1138 5705 : if ((mContentRead == mContentLength) ||
1139 8 : (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
1140 : // the transaction is done with a complete response.
1141 2800 : mTransactionDone = true;
1142 2800 : mResponseIsComplete = true;
1143 :
1144 2800 : if (TimingEnabled())
1145 2 : mTimings.responseEnd = mozilla::TimeStamp::Now();
1146 :
1147 : // report the entire response has arrived
1148 2800 : if (mActivityDistributor)
1149 8 : mActivityDistributor->ObserveActivity(
1150 : mChannel,
1151 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
1152 : NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
1153 : PR_Now(),
1154 : static_cast<PRUint64>(mContentRead),
1155 8 : EmptyCString());
1156 : }
1157 :
1158 5697 : return NS_OK;
1159 : }
1160 :
1161 : nsresult
1162 5721 : nsHttpTransaction::ProcessData(char *buf, PRUint32 count, PRUint32 *countRead)
1163 : {
1164 : nsresult rv;
1165 :
1166 5721 : LOG(("nsHttpTransaction::ProcessData [this=%x count=%u]\n", this, count));
1167 :
1168 5721 : *countRead = 0;
1169 :
1170 : // we may not have read all of the headers yet...
1171 5721 : if (!mHaveAllHeaders) {
1172 2843 : PRUint32 bytesConsumed = 0;
1173 :
1174 2832 : do {
1175 2843 : PRUint32 localBytesConsumed = 0;
1176 2843 : char *localBuf = buf + bytesConsumed;
1177 2843 : PRUint32 localCount = count - bytesConsumed;
1178 :
1179 2843 : rv = ParseHead(localBuf, localCount, &localBytesConsumed);
1180 2843 : if (NS_FAILED(rv) && rv != NS_ERROR_NET_INTERRUPT)
1181 11 : return rv;
1182 2832 : bytesConsumed += localBytesConsumed;
1183 : } while (rv == NS_ERROR_NET_INTERRUPT);
1184 :
1185 2832 : count -= bytesConsumed;
1186 :
1187 : // if buf has some content in it, shift bytes to top of buf.
1188 2832 : if (count && bytesConsumed)
1189 29 : memmove(buf, buf + bytesConsumed, count);
1190 :
1191 : // report the completed response header
1192 2832 : if (mActivityDistributor && mResponseHead && mHaveAllHeaders) {
1193 16 : nsCAutoString completeResponseHeaders;
1194 8 : mResponseHead->Flatten(completeResponseHeaders, false);
1195 8 : completeResponseHeaders.AppendLiteral("\r\n");
1196 8 : mActivityDistributor->ObserveActivity(
1197 : mChannel,
1198 : NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
1199 : NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER,
1200 : PR_Now(), LL_ZERO,
1201 8 : completeResponseHeaders);
1202 : }
1203 : }
1204 :
1205 : // even though count may be 0, we still want to call HandleContent
1206 : // so it can complete the transaction if this is a "no-content" response.
1207 5710 : if (mHaveAllHeaders) {
1208 5697 : PRUint32 countRemaining = 0;
1209 : //
1210 : // buf layout:
1211 : //
1212 : // +--------------------------------------+----------------+-----+
1213 : // | countRead | countRemaining | |
1214 : // +--------------------------------------+----------------+-----+
1215 : //
1216 : // count : bytes read from the socket
1217 : // countRead : bytes corresponding to this transaction
1218 : // countRemaining : bytes corresponding to next pipelined transaction
1219 : //
1220 : // NOTE:
1221 : // count > countRead + countRemaining <==> chunked transfer encoding
1222 : //
1223 5697 : rv = HandleContent(buf, count, countRead, &countRemaining);
1224 5697 : if (NS_FAILED(rv)) return rv;
1225 : // we may have read more than our share, in which case we must give
1226 : // the excess bytes back to the connection
1227 5697 : if (mResponseIsComplete && countRemaining) {
1228 0 : NS_ASSERTION(mConnection, "no connection");
1229 0 : mConnection->PushBack(buf + *countRead, countRemaining);
1230 : }
1231 : }
1232 :
1233 5710 : return NS_OK;
1234 : }
1235 :
1236 : //-----------------------------------------------------------------------------
1237 : // nsHttpTransaction deletion event
1238 : //-----------------------------------------------------------------------------
1239 :
1240 28 : class nsDeleteHttpTransaction : public nsRunnable {
1241 : public:
1242 7 : nsDeleteHttpTransaction(nsHttpTransaction *trans)
1243 7 : : mTrans(trans)
1244 7 : {}
1245 :
1246 7 : NS_IMETHOD Run()
1247 : {
1248 7 : delete mTrans;
1249 7 : return NS_OK;
1250 : }
1251 : private:
1252 : nsHttpTransaction *mTrans;
1253 : };
1254 :
1255 : void
1256 2977 : nsHttpTransaction::DeleteSelfOnConsumerThread()
1257 : {
1258 2977 : LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%x]\n", this));
1259 :
1260 : bool val;
1261 5954 : if (!mConsumerTarget ||
1262 2977 : (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)) {
1263 2970 : delete this;
1264 : } else {
1265 7 : LOG(("proxying delete to consumer thread...\n"));
1266 14 : nsCOMPtr<nsIRunnable> event = new nsDeleteHttpTransaction(this);
1267 7 : if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL)))
1268 0 : NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
1269 : }
1270 2977 : }
1271 :
1272 : //-----------------------------------------------------------------------------
1273 : // nsHttpTransaction::nsISupports
1274 : //-----------------------------------------------------------------------------
1275 :
1276 15069 : NS_IMPL_THREADSAFE_ADDREF(nsHttpTransaction)
1277 :
1278 : NS_IMETHODIMP_(nsrefcnt)
1279 15057 : nsHttpTransaction::Release()
1280 : {
1281 : nsrefcnt count;
1282 15057 : NS_PRECONDITION(0 != mRefCnt, "dup release");
1283 15057 : count = NS_AtomicDecrementRefcnt(mRefCnt);
1284 15057 : NS_LOG_RELEASE(this, count, "nsHttpTransaction");
1285 15057 : if (0 == count) {
1286 2977 : mRefCnt = 1; /* stablize */
1287 : // it is essential that the transaction be destroyed on the consumer
1288 : // thread (we could be holding the last reference to our consumer).
1289 2977 : DeleteSelfOnConsumerThread();
1290 2977 : return 0;
1291 : }
1292 12080 : return count;
1293 : }
1294 :
1295 0 : NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsHttpTransaction,
1296 : nsIInputStreamCallback,
1297 : nsIOutputStreamCallback)
1298 :
1299 : //-----------------------------------------------------------------------------
1300 : // nsHttpTransaction::nsIInputStreamCallback
1301 : //-----------------------------------------------------------------------------
1302 :
1303 : // called on the socket thread
1304 : NS_IMETHODIMP
1305 0 : nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out)
1306 : {
1307 0 : if (mConnection) {
1308 0 : mConnection->TransactionHasDataToWrite(this);
1309 0 : nsresult rv = mConnection->ResumeSend();
1310 0 : if (NS_FAILED(rv))
1311 0 : NS_ERROR("ResumeSend failed");
1312 : }
1313 0 : return NS_OK;
1314 : }
1315 :
1316 : //-----------------------------------------------------------------------------
1317 : // nsHttpTransaction::nsIOutputStreamCallback
1318 : //-----------------------------------------------------------------------------
1319 :
1320 : // called on the socket thread
1321 : NS_IMETHODIMP
1322 0 : nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out)
1323 : {
1324 0 : if (mConnection) {
1325 0 : nsresult rv = mConnection->ResumeRecv();
1326 0 : if (NS_FAILED(rv))
1327 0 : NS_ERROR("ResumeRecv failed");
1328 : }
1329 0 : return NS_OK;
1330 : }
|