Bug 458256. Use LoadLibraryW instead of LoadLibrary (patch by DougT). r+sr=vlad
[wine-gecko.git] / netwerk / protocol / http / src / nsHttpTransaction.cpp
blobb02a2b27b55cdffa88f2a9d2813ff8452d3f3e72
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
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/
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.
16 * The Original Code is Mozilla.
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.
23 * Contributor(s):
24 * Darin Fisher <darin@netscape.com> (original author)
25 * Andreas M. Schneider <clarence@clarence.de>
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.
39 * ***** END LICENSE BLOCK ***** */
41 #include "nsHttpHandler.h"
42 #include "nsHttpTransaction.h"
43 #include "nsHttpConnection.h"
44 #include "nsHttpRequestHead.h"
45 #include "nsHttpResponseHead.h"
46 #include "nsHttpChunkedDecoder.h"
47 #include "nsNetSegmentUtils.h"
48 #include "nsTransportUtils.h"
49 #include "nsNetUtil.h"
50 #include "nsProxyRelease.h"
51 #include "nsIOService.h"
52 #include "nsAutoLock.h"
53 #include "pratom.h"
55 #include "nsISeekableStream.h"
56 #include "nsISocketTransport.h"
57 #include "nsMultiplexInputStream.h"
58 #include "nsStringStream.h"
60 #include "nsComponentManagerUtils.h" // do_CreateInstance
61 #include "nsServiceManagerUtils.h" // do_GetService
62 #include "nsIHttpActivityObserver.h"
64 //-----------------------------------------------------------------------------
66 #ifdef DEBUG
67 // defined by the socket transport service while active
68 extern PRThread *gSocketThread;
69 #endif
71 //-----------------------------------------------------------------------------
73 static NS_DEFINE_CID(kMultiplexInputStream, NS_MULTIPLEXINPUTSTREAM_CID);
75 // mLineBuf is limited to this number of bytes.
76 #define MAX_LINEBUF_LENGTH (1024 * 10)
78 //-----------------------------------------------------------------------------
79 // helpers
80 //-----------------------------------------------------------------------------
82 static char *
83 LocateHttpStart(char *buf, PRUint32 len)
85 // if we have received less than 4 bytes of data, then we'll have to
86 // just accept a partial match, which may not be correct.
87 if (len < 4)
88 return (PL_strncasecmp(buf, "HTTP", len) == 0) ? buf : 0;
90 // PL_strncasestr would be perfect for this, but unfortunately bug 96571
91 // prevents its use here.
92 while (len >= 4) {
93 if (PL_strncasecmp(buf, "HTTP", 4) == 0)
94 return buf;
95 buf++;
96 len--;
98 return 0;
101 #if defined(PR_LOGGING)
102 static void
103 LogHeaders(const char *lines)
105 nsCAutoString buf;
106 char *p;
107 while ((p = PL_strstr(lines, "\r\n")) != nsnull) {
108 buf.Assign(lines, p - lines);
109 if (PL_strcasestr(buf.get(), "authorization: ") != nsnull) {
110 char *p = PL_strchr(PL_strchr(buf.get(), ' ')+1, ' ');
111 while (*++p) *p = '*';
113 LOG3((" %s\n", buf.get()));
114 lines = p + 2;
117 #endif
119 //-----------------------------------------------------------------------------
120 // nsHttpTransaction <public>
121 //-----------------------------------------------------------------------------
123 nsHttpTransaction::nsHttpTransaction()
124 : mRequestSize(0)
125 , mConnection(nsnull)
126 , mConnInfo(nsnull)
127 , mRequestHead(nsnull)
128 , mResponseHead(nsnull)
129 , mContentLength(-1)
130 , mContentRead(0)
131 , mChunkedDecoder(nsnull)
132 , mStatus(NS_OK)
133 , mPriority(0)
134 , mRestartCount(0)
135 , mCaps(0)
136 , mClosed(PR_FALSE)
137 , mConnected(PR_FALSE)
138 , mHaveStatusLine(PR_FALSE)
139 , mHaveAllHeaders(PR_FALSE)
140 , mTransactionDone(PR_FALSE)
141 , mResponseIsComplete(PR_FALSE)
142 , mDidContentStart(PR_FALSE)
143 , mNoContent(PR_FALSE)
144 , mSentData(PR_FALSE)
145 , mReceivedData(PR_FALSE)
146 , mStatusEventPending(PR_FALSE)
147 , mHasRequestBody(PR_FALSE)
148 , mSSLConnectFailed(PR_FALSE)
150 LOG(("Creating nsHttpTransaction @%x\n", this));
153 nsHttpTransaction::~nsHttpTransaction()
155 LOG(("Destroying nsHttpTransaction @%x\n", this));
157 NS_IF_RELEASE(mConnection);
158 NS_IF_RELEASE(mConnInfo);
160 delete mResponseHead;
161 delete mChunkedDecoder;
164 nsresult
165 nsHttpTransaction::Init(PRUint8 caps,
166 nsHttpConnectionInfo *cinfo,
167 nsHttpRequestHead *requestHead,
168 nsIInputStream *requestBody,
169 PRBool requestBodyHasHeaders,
170 nsIEventTarget *target,
171 nsIInterfaceRequestor *callbacks,
172 nsITransportEventSink *eventsink,
173 nsIAsyncInputStream **responseBody)
175 nsresult rv;
177 LOG(("nsHttpTransaction::Init [this=%x caps=%x]\n", this, caps));
179 NS_ASSERTION(cinfo, "ouch");
180 NS_ASSERTION(requestHead, "ouch");
181 NS_ASSERTION(target, "ouch");
183 // create transport event sink proxy that coalesces all events
184 rv = net_NewTransportEventSinkProxy(getter_AddRefs(mTransportSink),
185 eventsink, target, PR_TRUE);
186 if (NS_FAILED(rv)) return rv;
188 // try to get the nsIHttpActivityObserver distributor
189 mActivityDistributor = do_GetService(NS_HTTPACTIVITYDISTRIBUTOR_CONTRACTID, &rv);
191 // mActivityDistributor may not be valid
192 if (NS_SUCCEEDED(rv) && mActivityDistributor) {
193 // the service is valid, now check if it is active
194 PRBool active;
195 rv = mActivityDistributor->GetIsActive(&active);
196 if (NS_SUCCEEDED(rv) && active) {
197 // the service is valid and active, gather nsISupports
198 // for the channel that called Init()
199 mChannel = do_QueryInterface(eventsink);
200 LOG(("nsHttpTransaction::Init() " \
201 "mActivityDistributor is active " \
202 "this=%x", this));
203 } else
204 // the interface in valid but not active, so don't use it
205 mActivityDistributor = nsnull;
208 NS_ADDREF(mConnInfo = cinfo);
209 mCallbacks = callbacks;
210 mConsumerTarget = target;
211 mCaps = caps;
213 if (requestHead->Method() == nsHttp::Head)
214 mNoContent = PR_TRUE;
216 // Make sure that there is "Content-Length: 0" header in the requestHead
217 // in case of POST and PUT methods when there is no requestBody and
218 // requestHead doesn't contain "Transfer-Encoding" header.
220 // RFC1945 section 7.2.2:
221 // HTTP/1.0 requests containing an entity body must include a valid
222 // Content-Length header field.
224 // RFC2616 section 4.4:
225 // For compatibility with HTTP/1.0 applications, HTTP/1.1 requests
226 // containing a message-body MUST include a valid Content-Length header
227 // field unless the server is known to be HTTP/1.1 compliant.
228 if ((requestHead->Method() == nsHttp::Post || requestHead->Method() == nsHttp::Put) &&
229 !requestBody && !requestHead->PeekHeader(nsHttp::Transfer_Encoding)) {
230 requestHead->SetHeader(nsHttp::Content_Length, NS_LITERAL_CSTRING("0"));
233 // grab a weak reference to the request head
234 mRequestHead = requestHead;
236 // make sure we eliminate any proxy specific headers from
237 // the request if we are talking HTTPS via a SSL tunnel.
238 PRBool pruneProxyHeaders = cinfo->UsingSSL() &&
239 cinfo->UsingHttpProxy();
240 mReqHeaderBuf.Truncate();
241 requestHead->Flatten(mReqHeaderBuf, pruneProxyHeaders);
243 #if defined(PR_LOGGING)
244 if (LOG3_ENABLED()) {
245 LOG3(("http request [\n"));
246 LogHeaders(mReqHeaderBuf.get());
247 LOG3(("]\n"));
249 #endif
251 // If the request body does not include headers or if there is no request
252 // body, then we must add the header/body separator manually.
253 if (!requestBodyHasHeaders || !requestBody)
254 mReqHeaderBuf.AppendLiteral("\r\n");
256 // report the request header
257 if (mActivityDistributor)
258 mActivityDistributor->ObserveActivity(
259 mChannel,
260 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
261 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_HEADER,
262 LL_ZERO, LL_ZERO,
263 mReqHeaderBuf);
265 // Create a string stream for the request header buf (the stream holds
266 // a non-owning reference to the request header data, so we MUST keep
267 // mReqHeaderBuf around).
268 nsCOMPtr<nsIInputStream> headers;
269 rv = NS_NewByteInputStream(getter_AddRefs(headers),
270 mReqHeaderBuf.get(),
271 mReqHeaderBuf.Length());
272 if (NS_FAILED(rv)) return rv;
274 if (requestBody) {
275 mHasRequestBody = PR_TRUE;
277 // wrap the headers and request body in a multiplexed input stream.
278 nsCOMPtr<nsIMultiplexInputStream> multi =
279 do_CreateInstance(kMultiplexInputStream, &rv);
280 if (NS_FAILED(rv)) return rv;
282 rv = multi->AppendStream(headers);
283 if (NS_FAILED(rv)) return rv;
285 rv = multi->AppendStream(requestBody);
286 if (NS_FAILED(rv)) return rv;
288 // wrap the multiplexed input stream with a buffered input stream, so
289 // that we write data in the largest chunks possible. this is actually
290 // necessary to workaround some common server bugs (see bug 137155).
291 rv = NS_NewBufferedInputStream(getter_AddRefs(mRequestStream), multi,
292 NET_DEFAULT_SEGMENT_SIZE);
293 if (NS_FAILED(rv)) return rv;
295 else
296 mRequestStream = headers;
298 rv = mRequestStream->Available(&mRequestSize);
299 if (NS_FAILED(rv)) return rv;
301 // create pipe for response stream
302 rv = NS_NewPipe2(getter_AddRefs(mPipeIn),
303 getter_AddRefs(mPipeOut),
304 PR_TRUE, PR_TRUE,
305 NS_HTTP_SEGMENT_SIZE,
306 NS_HTTP_SEGMENT_COUNT,
307 nsIOService::gBufferCache);
308 if (NS_FAILED(rv)) return rv;
310 NS_ADDREF(*responseBody = mPipeIn);
311 return NS_OK;
314 nsHttpResponseHead *
315 nsHttpTransaction::TakeResponseHead()
317 if (!mHaveAllHeaders) {
318 NS_WARNING("response headers not available or incomplete");
319 return nsnull;
322 nsHttpResponseHead *head = mResponseHead;
323 mResponseHead = nsnull;
324 return head;
327 //----------------------------------------------------------------------------
328 // nsHttpTransaction::nsAHttpTransaction
329 //----------------------------------------------------------------------------
331 void
332 nsHttpTransaction::SetConnection(nsAHttpConnection *conn)
334 NS_IF_RELEASE(mConnection);
335 NS_IF_ADDREF(mConnection = conn);
338 void
339 nsHttpTransaction::GetSecurityCallbacks(nsIInterfaceRequestor **cb)
341 NS_IF_ADDREF(*cb = mCallbacks);
344 void
345 nsHttpTransaction::OnTransportStatus(nsresult status, PRUint64 progress)
347 LOG(("nsHttpTransaction::OnSocketStatus [this=%x status=%x progress=%llu]\n",
348 this, status, progress));
350 if (!mTransportSink)
351 return;
353 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
355 // nsHttpChannel synthesizes progress events in OnDataAvailable
356 if (status == nsISocketTransport::STATUS_RECEIVING_FROM)
357 return;
359 if (mActivityDistributor) {
360 // upon STATUS_WAITING_FOR; report request body sent
361 if ((mHasRequestBody) &&
362 (status == nsISocketTransport::STATUS_WAITING_FOR))
363 mActivityDistributor->ObserveActivity(
364 mChannel,
365 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
366 NS_HTTP_ACTIVITY_SUBTYPE_REQUEST_BODY_SENT,
367 LL_ZERO, LL_ZERO, EmptyCString());
369 // report the status and progress
370 mActivityDistributor->ObserveActivity(
371 mChannel,
372 NS_HTTP_ACTIVITY_TYPE_SOCKET_TRANSPORT,
373 static_cast<PRUint32>(status),
374 LL_ZERO,
375 progress,
376 EmptyCString());
379 nsUint64 progressMax;
381 if (status == nsISocketTransport::STATUS_SENDING_TO) {
382 // suppress progress when only writing request headers
383 if (!mHasRequestBody)
384 return;
386 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
387 NS_ASSERTION(seekable, "Request stream isn't seekable?!?");
389 PRInt64 prog = 0;
390 seekable->Tell(&prog);
391 progress = prog;
393 // when uploading, we include the request headers in the progress
394 // notifications.
395 progressMax = mRequestSize; // XXX mRequestSize is 32-bit!
397 else {
398 progress = LL_ZERO;
399 progressMax = 0;
402 mTransportSink->OnTransportStatus(nsnull, status, progress, progressMax);
405 PRBool
406 nsHttpTransaction::IsDone()
408 return mTransactionDone;
411 nsresult
412 nsHttpTransaction::Status()
414 return mStatus;
417 PRUint32
418 nsHttpTransaction::Available()
420 PRUint32 size;
421 if (NS_FAILED(mRequestStream->Available(&size)))
422 size = 0;
423 return size;
426 NS_METHOD
427 nsHttpTransaction::ReadRequestSegment(nsIInputStream *stream,
428 void *closure,
429 const char *buf,
430 PRUint32 offset,
431 PRUint32 count,
432 PRUint32 *countRead)
434 nsHttpTransaction *trans = (nsHttpTransaction *) closure;
435 nsresult rv = trans->mReader->OnReadSegment(buf, count, countRead);
436 if (NS_FAILED(rv)) return rv;
438 trans->mSentData = PR_TRUE;
439 return NS_OK;
442 nsresult
443 nsHttpTransaction::ReadSegments(nsAHttpSegmentReader *reader,
444 PRUint32 count, PRUint32 *countRead)
446 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
448 if (mTransactionDone) {
449 *countRead = 0;
450 return mStatus;
453 if (!mConnected) {
454 mConnected = PR_TRUE;
455 mConnection->GetSecurityInfo(getter_AddRefs(mSecurityInfo));
458 mReader = reader;
460 nsresult rv = mRequestStream->ReadSegments(ReadRequestSegment, this, count, countRead);
462 mReader = nsnull;
464 // if read would block then we need to AsyncWait on the request stream.
465 // have callback occur on socket thread so we stay synchronized.
466 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
467 nsCOMPtr<nsIAsyncInputStream> asyncIn =
468 do_QueryInterface(mRequestStream);
469 if (asyncIn) {
470 nsCOMPtr<nsIEventTarget> target;
471 gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
472 if (target)
473 asyncIn->AsyncWait(this, 0, 0, target);
474 else {
475 NS_ERROR("no socket thread event target");
476 rv = NS_ERROR_UNEXPECTED;
481 return rv;
484 NS_METHOD
485 nsHttpTransaction::WritePipeSegment(nsIOutputStream *stream,
486 void *closure,
487 char *buf,
488 PRUint32 offset,
489 PRUint32 count,
490 PRUint32 *countWritten)
492 nsHttpTransaction *trans = (nsHttpTransaction *) closure;
494 if (trans->mTransactionDone)
495 return NS_BASE_STREAM_CLOSED; // stop iterating
497 nsresult rv;
499 // OK, now let the caller fill this segment with data.
501 rv = trans->mWriter->OnWriteSegment(buf, count, countWritten);
502 if (NS_FAILED(rv)) return rv; // caller didn't want to write anything
504 NS_ASSERTION(*countWritten > 0, "bad writer");
505 trans->mReceivedData = PR_TRUE;
507 // now let the transaction "play" with the buffer. it is free to modify
508 // the contents of the buffer and/or modify countWritten.
509 rv = trans->ProcessData(buf, *countWritten, countWritten);
510 if (NS_FAILED(rv))
511 trans->Close(rv);
513 return rv; // failure code only stops WriteSegments; it is not propagated.
516 nsresult
517 nsHttpTransaction::WriteSegments(nsAHttpSegmentWriter *writer,
518 PRUint32 count, PRUint32 *countWritten)
520 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
522 if (mTransactionDone)
523 return NS_SUCCEEDED(mStatus) ? NS_BASE_STREAM_CLOSED : mStatus;
525 mWriter = writer;
527 nsresult rv = mPipeOut->WriteSegments(WritePipeSegment, this, count, countWritten);
529 mWriter = nsnull;
531 // if pipe would block then we need to AsyncWait on it. have callback
532 // occur on socket thread so we stay synchronized.
533 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
534 nsCOMPtr<nsIEventTarget> target;
535 gHttpHandler->GetSocketThreadTarget(getter_AddRefs(target));
536 if (target)
537 mPipeOut->AsyncWait(this, 0, 0, target);
538 else {
539 NS_ERROR("no socket thread event target");
540 rv = NS_ERROR_UNEXPECTED;
544 return rv;
547 void
548 nsHttpTransaction::Close(nsresult reason)
550 LOG(("nsHttpTransaction::Close [this=%x reason=%x]\n", this, reason));
552 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
554 if (mClosed) {
555 LOG((" already closed\n"));
556 return;
559 if (mActivityDistributor) {
560 // report the reponse is complete if not already reported
561 if (!mResponseIsComplete)
562 mActivityDistributor->ObserveActivity(
563 mChannel,
564 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
565 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
566 LL_ZERO,
567 static_cast<PRUint64>(mContentRead.mValue),
568 EmptyCString());
570 // report that this transaction is closing
571 mActivityDistributor->ObserveActivity(
572 mChannel,
573 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
574 NS_HTTP_ACTIVITY_SUBTYPE_TRANSACTION_CLOSE,
575 LL_ZERO, LL_ZERO, EmptyCString());
578 // we must no longer reference the connection! find out if the
579 // connection was being reused before letting it go.
580 PRBool connReused = PR_FALSE;
581 if (mConnection)
582 connReused = mConnection->IsReused();
583 mConnected = PR_FALSE;
586 // if the connection was reset or closed before we wrote any part of the
587 // request or if we wrote the request but didn't receive any part of the
588 // response and the connection was being reused, then we can (and really
589 // should) assume that we wrote to a stale connection and we must therefore
590 // repeat the request over a new connection.
592 // NOTE: the conditions under which we will automatically retry the HTTP
593 // request have to be carefully selected to avoid duplication of the
594 // request from the point-of-view of the server. such duplication could
595 // have dire consequences including repeated purchases, etc.
597 // NOTE: because of the way SSL proxy CONNECT is implemented, it is
598 // possible that the transaction may have received data without having
599 // sent any data. for this reason, mSendData == FALSE does not imply
600 // mReceivedData == FALSE. (see bug 203057 for more info.)
602 if (reason == NS_ERROR_NET_RESET || reason == NS_OK) {
603 if (!mReceivedData && (!mSentData || connReused)) {
604 // if restarting fails, then we must proceed to close the pipe,
605 // which will notify the channel that the transaction failed.
606 if (NS_SUCCEEDED(Restart()))
607 return;
611 PRBool relConn = PR_TRUE;
612 if (NS_SUCCEEDED(reason)) {
613 // the server has not sent the final \r\n terminating the header
614 // section, and there may still be a header line unparsed. let's make
615 // sure we parse the remaining header line, and then hopefully, the
616 // response will be usable (see bug 88792). related to that, we may
617 // also have an empty response containing no headers. we should treat
618 // that as an empty HTTP/0.9 response (see bug 300613).
619 if (!mHaveAllHeaders) {
620 char data = '\n';
621 PRUint32 unused;
622 ParseHead(&data, 1, &unused);
625 // honor the sticky connection flag...
626 if (mCaps & NS_HTTP_STICKY_CONNECTION)
627 relConn = PR_FALSE;
629 if (relConn && mConnection)
630 NS_RELEASE(mConnection);
632 mStatus = reason;
633 mTransactionDone = PR_TRUE; // forcibly flag the transaction as complete
634 mClosed = PR_TRUE;
636 // release some resources that we no longer need
637 mRequestStream = nsnull;
638 mReqHeaderBuf.Truncate();
639 mLineBuf.Truncate();
640 if (mChunkedDecoder) {
641 delete mChunkedDecoder;
642 mChunkedDecoder = nsnull;
645 // closing this pipe triggers the channel's OnStopRequest method.
646 mPipeOut->CloseWithStatus(reason);
649 //-----------------------------------------------------------------------------
650 // nsHttpTransaction <private>
651 //-----------------------------------------------------------------------------
653 nsresult
654 nsHttpTransaction::Restart()
656 NS_ASSERTION(PR_GetCurrentThread() == gSocketThread, "wrong thread");
658 // limit the number of restart attempts - bug 92224
659 if (++mRestartCount >= gHttpHandler->MaxRequestAttempts()) {
660 LOG(("reached max request attempts, failing transaction @%x\n", this));
661 return NS_ERROR_NET_RESET;
664 LOG(("restarting transaction @%x\n", this));
666 // rewind streams in case we already wrote out the request
667 nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(mRequestStream);
668 if (seekable)
669 seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0);
671 // clear old connection state...
672 mSecurityInfo = 0;
673 NS_IF_RELEASE(mConnection);
675 // disable pipelining for the next attempt in case pipelining caused the
676 // reset. this is being overly cautious since we don't know if pipelining
677 // was the problem here.
678 mCaps &= ~NS_HTTP_ALLOW_PIPELINING;
680 return gHttpHandler->InitiateTransaction(this, mPriority);
683 void
684 nsHttpTransaction::ParseLine(char *line)
686 LOG(("nsHttpTransaction::ParseLine [%s]\n", line));
688 if (!mHaveStatusLine) {
689 mResponseHead->ParseStatusLine(line);
690 mHaveStatusLine = PR_TRUE;
691 // XXX this should probably never happen
692 if (mResponseHead->Version() == NS_HTTP_VERSION_0_9)
693 mHaveAllHeaders = PR_TRUE;
695 else
696 mResponseHead->ParseHeaderLine(line);
699 nsresult
700 nsHttpTransaction::ParseLineSegment(char *segment, PRUint32 len)
702 NS_PRECONDITION(!mHaveAllHeaders, "already have all headers");
704 if (!mLineBuf.IsEmpty() && mLineBuf.Last() == '\n') {
705 // trim off the new line char, and if this segment is
706 // not a continuation of the previous or if we haven't
707 // parsed the status line yet, then parse the contents
708 // of mLineBuf.
709 mLineBuf.Truncate(mLineBuf.Length() - 1);
710 if (!mHaveStatusLine || (*segment != ' ' && *segment != '\t')) {
711 ParseLine(mLineBuf.BeginWriting());
712 mLineBuf.Truncate();
716 // append segment to mLineBuf...
717 if (mLineBuf.Length() + len > MAX_LINEBUF_LENGTH) {
718 LOG(("excessively long header received, canceling transaction [trans=%x]", this));
719 return NS_ERROR_ABORT;
721 mLineBuf.Append(segment, len);
723 // a line buf with only a new line char signifies the end of headers.
724 if (mLineBuf.First() == '\n') {
725 mLineBuf.Truncate();
726 // discard this response if it is a 100 continue or other 1xx status.
727 if (mResponseHead->Status() / 100 == 1) {
728 LOG(("ignoring 1xx response\n"));
729 mHaveStatusLine = PR_FALSE;
730 mResponseHead->Reset();
731 return NS_OK;
733 mHaveAllHeaders = PR_TRUE;
735 return NS_OK;
738 nsresult
739 nsHttpTransaction::ParseHead(char *buf,
740 PRUint32 count,
741 PRUint32 *countRead)
743 nsresult rv;
744 PRUint32 len;
745 char *eol;
747 LOG(("nsHttpTransaction::ParseHead [count=%u]\n", count));
749 *countRead = 0;
751 NS_PRECONDITION(!mHaveAllHeaders, "oops");
753 // allocate the response head object if necessary
754 if (!mResponseHead) {
755 mResponseHead = new nsHttpResponseHead();
756 if (!mResponseHead)
757 return NS_ERROR_OUT_OF_MEMORY;
759 // report that we have a least some of the response
760 if (mActivityDistributor)
761 mActivityDistributor->ObserveActivity(
762 mChannel,
763 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
764 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_START,
765 LL_ZERO, LL_ZERO, EmptyCString());
768 // if we don't have a status line and the line buf is empty, then
769 // this must be the first time we've been called.
770 if (!mHaveStatusLine && mLineBuf.IsEmpty()) {
771 // tolerate some junk before the status line
772 char *p = LocateHttpStart(buf, PR_MIN(count, 8));
773 if (!p) {
774 // Treat any 0.9 style response of a put as a failure.
775 if (mRequestHead->Method() == nsHttp::Put)
776 return NS_ERROR_ABORT;
778 mResponseHead->ParseStatusLine("");
779 mHaveStatusLine = PR_TRUE;
780 mHaveAllHeaders = PR_TRUE;
781 return NS_OK;
783 if (p > buf) {
784 // skip over the junk
785 *countRead = p - buf;
786 buf = p;
789 // otherwise we can assume that we don't have a HTTP/0.9 response.
791 while ((eol = static_cast<char *>(memchr(buf, '\n', count - *countRead))) != nsnull) {
792 // found line in range [buf:eol]
793 len = eol - buf + 1;
795 *countRead += len;
797 // actually, the line is in the range [buf:eol-1]
798 if ((eol > buf) && (*(eol-1) == '\r'))
799 len--;
801 buf[len-1] = '\n';
802 rv = ParseLineSegment(buf, len);
803 if (NS_FAILED(rv))
804 return rv;
806 if (mHaveAllHeaders)
807 return NS_OK;
809 // skip over line
810 buf = eol + 1;
813 // do something about a partial header line
814 if (!mHaveAllHeaders && (len = count - *countRead)) {
815 *countRead = count;
816 // ignore a trailing carriage return, and don't bother calling
817 // ParseLineSegment if buf only contains a carriage return.
818 if ((buf[len-1] == '\r') && (--len == 0))
819 return NS_OK;
820 rv = ParseLineSegment(buf, len);
821 if (NS_FAILED(rv))
822 return rv;
824 return NS_OK;
827 // called on the socket thread
828 nsresult
829 nsHttpTransaction::HandleContentStart()
831 LOG(("nsHttpTransaction::HandleContentStart [this=%x]\n", this));
833 if (mResponseHead) {
834 #if defined(PR_LOGGING)
835 if (LOG3_ENABLED()) {
836 LOG3(("http response [\n"));
837 nsCAutoString headers;
838 mResponseHead->Flatten(headers, PR_FALSE);
839 LogHeaders(headers.get());
840 LOG3(("]\n"));
842 #endif
843 // notify the connection, give it a chance to cause a reset.
844 PRBool reset = PR_FALSE;
845 mConnection->OnHeadersAvailable(this, mRequestHead, mResponseHead, &reset);
847 // looks like we should ignore this response, resetting...
848 if (reset) {
849 LOG(("resetting transaction's response head\n"));
850 mHaveAllHeaders = PR_FALSE;
851 mHaveStatusLine = PR_FALSE;
852 mReceivedData = PR_FALSE;
853 mSentData = PR_FALSE;
854 mResponseHead->Reset();
855 // wait to be called again...
856 return NS_OK;
859 // check if this is a no-content response
860 switch (mResponseHead->Status()) {
861 case 204:
862 case 205:
863 case 304:
864 mNoContent = PR_TRUE;
865 LOG(("this response should not contain a body.\n"));
866 break;
869 if (mNoContent)
870 mContentLength = 0;
871 else {
872 // grab the content-length from the response headers
873 mContentLength = mResponseHead->ContentLength();
875 // handle chunked encoding here, so we'll know immediately when
876 // we're done with the socket. please note that _all_ other
877 // decoding is done when the channel receives the content data
878 // so as not to block the socket transport thread too much.
879 // ignore chunked responses from HTTP/1.0 servers and proxies.
880 if (mResponseHead->Version() >= NS_HTTP_VERSION_1_1 &&
881 mResponseHead->HasHeaderValue(nsHttp::Transfer_Encoding, "chunked")) {
882 // we only support the "chunked" transfer encoding right now.
883 mChunkedDecoder = new nsHttpChunkedDecoder();
884 if (!mChunkedDecoder)
885 return NS_ERROR_OUT_OF_MEMORY;
886 LOG(("chunked decoder created\n"));
887 // Ignore server specified Content-Length.
888 mContentLength = -1;
890 #if defined(PR_LOGGING)
891 else if (mContentLength == nsInt64(-1))
892 LOG(("waiting for the server to close the connection.\n"));
893 #endif
897 mDidContentStart = PR_TRUE;
898 return NS_OK;
901 // called on the socket thread
902 nsresult
903 nsHttpTransaction::HandleContent(char *buf,
904 PRUint32 count,
905 PRUint32 *contentRead,
906 PRUint32 *contentRemaining)
908 nsresult rv;
910 LOG(("nsHttpTransaction::HandleContent [this=%x count=%u]\n", this, count));
912 *contentRead = 0;
913 *contentRemaining = 0;
915 NS_ASSERTION(mConnection, "no connection");
917 if (!mDidContentStart) {
918 rv = HandleContentStart();
919 if (NS_FAILED(rv)) return rv;
920 // Do not write content to the pipe if we haven't started streaming yet
921 if (!mDidContentStart)
922 return NS_OK;
925 if (mChunkedDecoder) {
926 // give the buf over to the chunked decoder so it can reformat the
927 // data and tell us how much is really there.
928 rv = mChunkedDecoder->HandleChunkedContent(buf, count, contentRead, contentRemaining);
929 if (NS_FAILED(rv)) return rv;
931 else if (mContentLength >= nsInt64(0)) {
932 // HTTP/1.0 servers have been known to send erroneous Content-Length
933 // headers. So, unless the connection is persistent, we must make
934 // allowances for a possibly invalid Content-Length header. Thus, if
935 // NOT persistent, we simply accept everything in |buf|.
936 if (mConnection->IsPersistent()) {
937 nsInt64 remaining = mContentLength - mContentRead;
938 nsInt64 count64 = count;
939 *contentRead = PR_MIN(count64, remaining);
940 *contentRemaining = count - *contentRead;
942 else {
943 *contentRead = count;
944 // mContentLength might need to be increased...
945 nsInt64 position = mContentRead + nsInt64(count);
946 if (position > mContentLength) {
947 mContentLength = position;
948 //mResponseHead->SetContentLength(mContentLength);
952 else {
953 // when we are just waiting for the server to close the connection...
954 // (no explicit content-length given)
955 *contentRead = count;
958 if (*contentRead) {
959 // update count of content bytes read and report progress...
960 mContentRead += *contentRead;
961 /* when uncommenting, take care of 64-bit integers w/ PR_MAX...
962 if (mProgressSink)
963 mProgressSink->OnProgress(nsnull, nsnull, mContentRead, PR_MAX(0, mContentLength));
967 LOG(("nsHttpTransaction::HandleContent [this=%x count=%u read=%u mContentRead=%lld mContentLength=%lld]\n",
968 this, count, *contentRead, mContentRead.mValue, mContentLength.mValue));
970 // check for end-of-file
971 if ((mContentRead == mContentLength) ||
972 (mChunkedDecoder && mChunkedDecoder->ReachedEOF())) {
973 // the transaction is done with a complete response.
974 mTransactionDone = PR_TRUE;
975 mResponseIsComplete = PR_TRUE;
977 // report the entire response has arrived
978 if (mActivityDistributor)
979 mActivityDistributor->ObserveActivity(
980 mChannel,
981 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
982 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
983 LL_ZERO,
984 static_cast<PRUint64>(mContentRead.mValue),
985 EmptyCString());
988 return NS_OK;
991 nsresult
992 nsHttpTransaction::ProcessData(char *buf, PRUint32 count, PRUint32 *countRead)
994 nsresult rv;
996 LOG(("nsHttpTransaction::ProcessData [this=%x count=%u]\n", this, count));
998 *countRead = 0;
1000 // we may not have read all of the headers yet...
1001 if (!mHaveAllHeaders) {
1002 PRUint32 bytesConsumed = 0;
1004 rv = ParseHead(buf, count, &bytesConsumed);
1005 if (NS_FAILED(rv)) return rv;
1007 count -= bytesConsumed;
1009 // if buf has some content in it, shift bytes to top of buf.
1010 if (count && bytesConsumed)
1011 memmove(buf, buf + bytesConsumed, count);
1013 // report the completed response header
1014 if (mActivityDistributor && mResponseHead && mHaveAllHeaders) {
1015 nsCAutoString completeResponseHeaders;
1016 mResponseHead->Flatten(completeResponseHeaders, PR_FALSE);
1017 completeResponseHeaders.AppendLiteral("\r\n");
1018 mActivityDistributor->ObserveActivity(
1019 mChannel,
1020 NS_HTTP_ACTIVITY_TYPE_HTTP_TRANSACTION,
1021 NS_HTTP_ACTIVITY_SUBTYPE_RESPONSE_HEADER,
1022 LL_ZERO, LL_ZERO,
1023 completeResponseHeaders);
1027 // even though count may be 0, we still want to call HandleContent
1028 // so it can complete the transaction if this is a "no-content" response.
1029 if (mHaveAllHeaders) {
1030 PRUint32 countRemaining = 0;
1032 // buf layout:
1034 // +--------------------------------------+----------------+-----+
1035 // | countRead | countRemaining | |
1036 // +--------------------------------------+----------------+-----+
1038 // count : bytes read from the socket
1039 // countRead : bytes corresponding to this transaction
1040 // countRemaining : bytes corresponding to next pipelined transaction
1042 // NOTE:
1043 // count > countRead + countRemaining <==> chunked transfer encoding
1045 rv = HandleContent(buf, count, countRead, &countRemaining);
1046 if (NS_FAILED(rv)) return rv;
1047 // we may have read more than our share, in which case we must give
1048 // the excess bytes back to the connection
1049 if (mResponseIsComplete && countRemaining) {
1050 NS_ASSERTION(mConnection, "no connection");
1051 mConnection->PushBack(buf + *countRead, countRemaining);
1055 return NS_OK;
1058 //-----------------------------------------------------------------------------
1059 // nsHttpTransaction deletion event
1060 //-----------------------------------------------------------------------------
1062 class nsDeleteHttpTransaction : public nsRunnable {
1063 public:
1064 nsDeleteHttpTransaction(nsHttpTransaction *trans)
1065 : mTrans(trans)
1068 NS_IMETHOD Run()
1070 delete mTrans;
1071 return NS_OK;
1073 private:
1074 nsHttpTransaction *mTrans;
1077 void
1078 nsHttpTransaction::DeleteSelfOnConsumerThread()
1080 LOG(("nsHttpTransaction::DeleteSelfOnConsumerThread [this=%x]\n", this));
1082 PRBool val;
1083 if (NS_SUCCEEDED(mConsumerTarget->IsOnCurrentThread(&val)) && val)
1084 delete this;
1085 else {
1086 LOG(("proxying delete to consumer thread...\n"));
1087 nsCOMPtr<nsIRunnable> event = new nsDeleteHttpTransaction(this);
1088 if (NS_FAILED(mConsumerTarget->Dispatch(event, NS_DISPATCH_NORMAL)))
1089 NS_WARNING("failed to dispatch nsHttpDeleteTransaction event");
1093 //-----------------------------------------------------------------------------
1094 // nsHttpTransaction::nsISupports
1095 //-----------------------------------------------------------------------------
1097 NS_IMPL_THREADSAFE_ADDREF(nsHttpTransaction)
1099 NS_IMETHODIMP_(nsrefcnt)
1100 nsHttpTransaction::Release()
1102 nsrefcnt count;
1103 NS_PRECONDITION(0 != mRefCnt, "dup release");
1104 count = PR_AtomicDecrement((PRInt32 *) &mRefCnt);
1105 NS_LOG_RELEASE(this, count, "nsHttpTransaction");
1106 if (0 == count) {
1107 mRefCnt = 1; /* stablize */
1108 // it is essential that the transaction be destroyed on the consumer
1109 // thread (we could be holding the last reference to our consumer).
1110 DeleteSelfOnConsumerThread();
1111 return 0;
1113 return count;
1116 NS_IMPL_THREADSAFE_QUERY_INTERFACE2(nsHttpTransaction,
1117 nsIInputStreamCallback,
1118 nsIOutputStreamCallback)
1120 //-----------------------------------------------------------------------------
1121 // nsHttpTransaction::nsIInputStreamCallback
1122 //-----------------------------------------------------------------------------
1124 // called on the socket thread
1125 NS_IMETHODIMP
1126 nsHttpTransaction::OnInputStreamReady(nsIAsyncInputStream *out)
1128 if (mConnection) {
1129 nsresult rv = mConnection->ResumeSend();
1130 if (NS_FAILED(rv))
1131 NS_ERROR("ResumeSend failed");
1133 return NS_OK;
1136 //-----------------------------------------------------------------------------
1137 // nsHttpTransaction::nsIOutputStreamCallback
1138 //-----------------------------------------------------------------------------
1140 // called on the socket thread
1141 NS_IMETHODIMP
1142 nsHttpTransaction::OnOutputStreamReady(nsIAsyncOutputStream *out)
1144 if (mConnection) {
1145 nsresult rv = mConnection->ResumeRecv();
1146 if (NS_FAILED(rv))
1147 NS_ERROR("ResumeRecv failed");
1149 return NS_OK;