1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
15 * The Original Code is mozilla.org code.
17 * The Initial Developer of the Original Code is Google Inc.
18 * Portions created by the Initial Developer are Copyright (C) 2005
19 * the Initial Developer. All Rights Reserved.
22 * Darin Fisher <darin@meer.net>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either the GNU General Public License Version 2 or later (the "GPL"), or
26 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
38 #include "nsBaseChannel.h"
39 #include "nsChannelProperties.h"
40 #include "nsURLHelper.h"
41 #include "nsNetUtil.h"
42 #include "nsMimeTypes.h"
43 #include "nsIOService.h"
44 #include "nsIHttpEventSink.h"
45 #include "nsIHttpChannel.h"
46 #include "nsIChannelEventSink.h"
47 #include "nsIStreamConverterService.h"
48 #include "nsIContentSniffer.h"
50 static PLDHashOperator
51 CopyProperties(const nsAString
&key
, nsIVariant
*data
, void *closure
)
53 nsIWritablePropertyBag
*bag
=
54 static_cast<nsIWritablePropertyBag
*>(closure
);
56 bag
->SetProperty(key
, data
);
60 // This class is used to suspend a request across a function scope.
61 class ScopedRequestSuspender
{
63 ScopedRequestSuspender(nsIRequest
*request
)
65 if (mRequest
&& NS_FAILED(mRequest
->Suspend())) {
66 NS_WARNING("Couldn't suspend pump");
70 ~ScopedRequestSuspender() {
78 // Used to suspend data events from mPump within a function scope. This is
79 // usually needed when a function makes callbacks that could process events.
80 #define SUSPEND_PUMP_FOR_SCOPE() \
81 ScopedRequestSuspender pump_suspender__(mPump)
83 //-----------------------------------------------------------------------------
86 nsBaseChannel::nsBaseChannel()
87 : mLoadFlags(LOAD_NORMAL
)
89 , mQueriedProgressSink(PR_TRUE
)
90 , mSynthProgressEvents(PR_FALSE
)
91 , mWasOpened(PR_FALSE
)
93 mContentType
.AssignLiteral(UNKNOWN_CONTENT_TYPE
);
97 nsBaseChannel::Redirect(nsIChannel
*newChannel
, PRUint32 redirectFlags
,
98 PRBool openNewChannel
)
100 SUSPEND_PUMP_FOR_SCOPE();
102 // Transfer properties
104 newChannel
->SetOriginalURI(OriginalURI());
105 newChannel
->SetLoadGroup(mLoadGroup
);
106 newChannel
->SetNotificationCallbacks(mCallbacks
);
107 newChannel
->SetLoadFlags(mLoadFlags
| LOAD_REPLACE
);
109 nsCOMPtr
<nsIWritablePropertyBag
> bag
= ::do_QueryInterface(newChannel
);
111 mPropertyHash
.EnumerateRead(CopyProperties
, bag
.get());
113 // Notify consumer, giving chance to cancel redirect. For backwards compat,
114 // we support nsIHttpEventSink if we are an HTTP channel and if this is not
115 // an internal redirect.
117 // Global observers. These come first so that other observers don't see
118 // redirects that get aborted for security reasons anyway.
119 NS_ASSERTION(gIOService
, "Must have an IO service");
120 nsresult rv
= gIOService
->OnChannelRedirect(this, newChannel
, redirectFlags
);
124 // Backwards compat for non-internal redirects from a HTTP channel.
125 if (!(redirectFlags
& nsIChannelEventSink::REDIRECT_INTERNAL
)) {
126 nsCOMPtr
<nsIHttpChannel
> httpChannel
= do_QueryInterface();
128 nsCOMPtr
<nsIHttpEventSink
> httpEventSink
;
129 GetCallback(httpEventSink
);
131 rv
= httpEventSink
->OnRedirect(httpChannel
, newChannel
);
138 nsCOMPtr
<nsIChannelEventSink
> channelEventSink
;
139 // Give our consumer a chance to observe/block this redirect.
140 GetCallback(channelEventSink
);
141 if (channelEventSink
) {
142 rv
= channelEventSink
->OnChannelRedirect(this, newChannel
, redirectFlags
);
147 // If we fail to open the new channel, then we want to leave this channel
148 // unaffected, so we defer tearing down our channel until we have succeeded
149 // with the redirect.
151 if (openNewChannel
) {
152 rv
= newChannel
->AsyncOpen(mListener
, mListenerContext
);
157 // close down this channel
158 Cancel(NS_BINDING_REDIRECTED
);
160 mListenerContext
= nsnull
;
166 nsBaseChannel::HasContentTypeHint() const
168 NS_ASSERTION(!IsPending(), "HasContentTypeHint called too late");
169 return !mContentType
.EqualsLiteral(UNKNOWN_CONTENT_TYPE
);
173 nsBaseChannel::SetContentLength64(PRInt64 len
)
175 // XXX: Storing the content-length as a property may not be what we want.
176 // It has the drawback of being copied if we redirect this channel.
177 // Maybe it is time for nsIChannel2.
178 SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH
, len
);
182 nsBaseChannel::ContentLength64()
185 nsresult rv
= GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH
, &len
);
186 return NS_SUCCEEDED(rv
) ? len
: -1;
190 nsBaseChannel::PushStreamConverter(const char *fromType
,
192 PRBool invalidatesContentLength
,
193 nsIStreamListener
**result
)
195 NS_ASSERTION(mListener
, "no listener");
198 nsCOMPtr
<nsIStreamConverterService
> scs
=
199 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID
, &rv
);
203 nsCOMPtr
<nsIStreamListener
> converter
;
204 rv
= scs
->AsyncConvertData(fromType
, toType
, mListener
, mListenerContext
,
205 getter_AddRefs(converter
));
206 if (NS_SUCCEEDED(rv
)) {
207 mListener
= converter
;
208 if (invalidatesContentLength
)
209 SetContentLength64(-1);
212 converter
.swap(*result
);
219 nsBaseChannel::BeginPumpingData()
221 nsCOMPtr
<nsIInputStream
> stream
;
222 nsCOMPtr
<nsIChannel
> channel
;
223 nsresult rv
= OpenContentStream(PR_TRUE
, getter_AddRefs(stream
),
224 getter_AddRefs(channel
));
228 NS_ASSERTION(!stream
|| !channel
, "Got both a channel and a stream?");
231 return NS_DispatchToCurrentThread(new RedirectRunnable(this, channel
));
233 // By assigning mPump, we flag this channel as pending (see IsPending). It's
234 // important that the pending flag is set when we call into the stream (the
235 // call to AsyncRead results in the stream's AsyncWait method being called)
236 // and especially when we call into the loadgroup. Our caller takes care to
237 // release mPump if we return an error.
239 rv
= nsInputStreamPump::Create(getter_AddRefs(mPump
), stream
, -1, -1, 0, 0,
241 if (NS_SUCCEEDED(rv
))
242 rv
= mPump
->AsyncRead(this, nsnull
);
248 nsBaseChannel::HandleAsyncRedirect(nsIChannel
* newChannel
)
250 NS_ASSERTION(!mPump
, "Shouldn't have gotten here");
251 nsresult rv
= Redirect(newChannel
, nsIChannelEventSink::REDIRECT_INTERNAL
,
254 // Notify our consumer ourselves
256 mListener
->OnStartRequest(this, mListenerContext
);
257 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
259 mListenerContext
= nsnull
;
263 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
265 // Drop notification callbacks to prevent cycles.
270 //-----------------------------------------------------------------------------
271 // nsBaseChannel::nsISupports
273 NS_IMPL_ISUPPORTS_INHERITED6(nsBaseChannel
,
277 nsIInterfaceRequestor
,
278 nsITransportEventSink
,
282 //-----------------------------------------------------------------------------
283 // nsBaseChannel::nsIRequest
286 nsBaseChannel::GetName(nsACString
&result
)
292 return mURI
->GetSpec(result
);
296 nsBaseChannel::IsPending(PRBool
*result
)
298 *result
= IsPending();
303 nsBaseChannel::GetStatus(nsresult
*status
)
305 if (mPump
&& NS_SUCCEEDED(mStatus
)) {
306 mPump
->GetStatus(status
);
314 nsBaseChannel::Cancel(nsresult status
)
316 // Ignore redundant cancelation
317 if (NS_FAILED(mStatus
))
323 mPump
->Cancel(status
);
329 nsBaseChannel::Suspend()
331 NS_ENSURE_TRUE(mPump
, NS_ERROR_NOT_INITIALIZED
);
332 return mPump
->Suspend();
336 nsBaseChannel::Resume()
338 NS_ENSURE_TRUE(mPump
, NS_ERROR_NOT_INITIALIZED
);
339 return mPump
->Resume();
343 nsBaseChannel::GetLoadFlags(nsLoadFlags
*aLoadFlags
)
345 *aLoadFlags
= mLoadFlags
;
350 nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags
)
352 mLoadFlags
= aLoadFlags
;
357 nsBaseChannel::GetLoadGroup(nsILoadGroup
**aLoadGroup
)
359 NS_IF_ADDREF(*aLoadGroup
= mLoadGroup
);
364 nsBaseChannel::SetLoadGroup(nsILoadGroup
*aLoadGroup
)
366 mLoadGroup
= aLoadGroup
;
371 //-----------------------------------------------------------------------------
372 // nsBaseChannel::nsIChannel
375 nsBaseChannel::GetOriginalURI(nsIURI
**aURI
)
377 *aURI
= OriginalURI();
383 nsBaseChannel::SetOriginalURI(nsIURI
*aURI
)
385 NS_ENSURE_ARG_POINTER(aURI
);
391 nsBaseChannel::GetURI(nsIURI
**aURI
)
393 NS_IF_ADDREF(*aURI
= mURI
);
398 nsBaseChannel::GetOwner(nsISupports
**aOwner
)
400 NS_IF_ADDREF(*aOwner
= mOwner
);
405 nsBaseChannel::SetOwner(nsISupports
*aOwner
)
412 nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor
**aCallbacks
)
414 NS_IF_ADDREF(*aCallbacks
= mCallbacks
);
419 nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor
*aCallbacks
)
421 mCallbacks
= aCallbacks
;
427 nsBaseChannel::GetSecurityInfo(nsISupports
**aSecurityInfo
)
429 NS_IF_ADDREF(*aSecurityInfo
= mSecurityInfo
);
434 nsBaseChannel::GetContentType(nsACString
&aContentType
)
436 aContentType
= mContentType
;
441 nsBaseChannel::SetContentType(const nsACString
&aContentType
)
443 // mContentCharset is unchanged if not parsed
445 net_ParseContentType(aContentType
, mContentType
, mContentCharset
, &dummy
);
450 nsBaseChannel::GetContentCharset(nsACString
&aContentCharset
)
452 aContentCharset
= mContentCharset
;
457 nsBaseChannel::SetContentCharset(const nsACString
&aContentCharset
)
459 mContentCharset
= aContentCharset
;
464 nsBaseChannel::GetContentLength(PRInt32
*aContentLength
)
466 PRInt64 len
= ContentLength64();
467 if (len
> PR_INT32_MAX
|| len
< 0)
468 *aContentLength
= -1;
470 *aContentLength
= (PRInt32
) len
;
475 nsBaseChannel::SetContentLength(PRInt32 aContentLength
)
477 SetContentLength64(aContentLength
);
482 nsBaseChannel::Open(nsIInputStream
**result
)
484 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
485 NS_ENSURE_TRUE(!mPump
, NS_ERROR_IN_PROGRESS
);
486 NS_ENSURE_TRUE(!mWasOpened
, NS_ERROR_IN_PROGRESS
);
488 nsCOMPtr
<nsIChannel
> chan
;
489 nsresult rv
= OpenContentStream(PR_FALSE
, result
, getter_AddRefs(chan
));
490 NS_ASSERTION(!chan
|| !*result
, "Got both a channel and a stream?");
491 if (NS_SUCCEEDED(rv
) && chan
) {
492 rv
= Redirect(chan
, nsIChannelEventSink::REDIRECT_INTERNAL
, PR_FALSE
);
495 rv
= chan
->Open(result
);
496 } else if (rv
== NS_ERROR_NOT_IMPLEMENTED
)
497 return NS_ImplementChannelOpen(this, result
);
499 mWasOpened
= NS_SUCCEEDED(rv
);
505 nsBaseChannel::AsyncOpen(nsIStreamListener
*listener
, nsISupports
*ctxt
)
507 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
508 NS_ENSURE_TRUE(!mPump
, NS_ERROR_IN_PROGRESS
);
509 NS_ENSURE_TRUE(!mWasOpened
, NS_ERROR_ALREADY_OPENED
);
510 NS_ENSURE_ARG(listener
);
512 // Ensure that this is an allowed port before proceeding.
513 nsresult rv
= NS_CheckPortSafety(mURI
);
519 // Store the listener and context early so that OpenContentStream and the
520 // stream's AsyncWait method (called by AsyncRead) can have access to them
521 // via PushStreamConverter and the StreamListener methods. However, since
522 // this typically introduces a reference cycle between this and the listener,
523 // we need to be sure to break the reference if this method does not succeed.
524 mListener
= listener
;
525 mListenerContext
= ctxt
;
527 // This method assigns mPump as a side-effect. We need to clear mPump if
528 // this method fails.
529 rv
= BeginPumpingData();
533 mListenerContext
= nsnull
;
538 // At this point, we are going to return success no matter what.
540 mWasOpened
= PR_TRUE
;
542 SUSPEND_PUMP_FOR_SCOPE();
545 mLoadGroup
->AddRequest(this, nsnull
);
550 //-----------------------------------------------------------------------------
551 // nsBaseChannel::nsITransportEventSink
554 nsBaseChannel::OnTransportStatus(nsITransport
*transport
, nsresult status
,
555 PRUint64 progress
, PRUint64 progressMax
)
557 // In some cases, we may wish to suppress transport-layer status events.
559 if (!mPump
|| NS_FAILED(mStatus
) || HasLoadFlag(LOAD_BACKGROUND
))
562 SUSPEND_PUMP_FOR_SCOPE();
564 // Lazily fetch mProgressSink
565 if (!mProgressSink
) {
566 if (mQueriedProgressSink
)
568 GetCallback(mProgressSink
);
569 mQueriedProgressSink
= PR_TRUE
;
574 nsAutoString statusArg
;
575 if (GetStatusArg(status
, statusArg
))
576 mProgressSink
->OnStatus(this, mListenerContext
, status
, statusArg
.get());
579 mProgressSink
->OnProgress(this, mListenerContext
, progress
, progressMax
);
584 //-----------------------------------------------------------------------------
585 // nsBaseChannel::nsIInterfaceRequestor
588 nsBaseChannel::GetInterface(const nsIID
&iid
, void **result
)
590 NS_QueryNotificationCallbacks(mCallbacks
, mLoadGroup
, iid
, result
);
591 return *result
? NS_OK
: NS_ERROR_NO_INTERFACE
;
594 //-----------------------------------------------------------------------------
595 // nsBaseChannel::nsIRequestObserver
598 CallTypeSniffers(void *aClosure
, const PRUint8
*aData
, PRUint32 aCount
)
600 nsIChannel
*chan
= static_cast<nsIChannel
*>(aClosure
);
602 const nsCOMArray
<nsIContentSniffer
>& sniffers
=
603 gIOService
->GetContentSniffers();
604 PRUint32 length
= sniffers
.Count();
605 for (PRUint32 i
= 0; i
< length
; ++i
) {
606 nsCAutoString newType
;
608 sniffers
[i
]->GetMIMETypeFromContent(chan
, aData
, aCount
, newType
);
609 if (NS_SUCCEEDED(rv
) && !newType
.IsEmpty()) {
610 chan
->SetContentType(newType
);
617 CallUnknownTypeSniffer(void *aClosure
, const PRUint8
*aData
, PRUint32 aCount
)
619 nsIChannel
*chan
= static_cast<nsIChannel
*>(aClosure
);
621 nsCOMPtr
<nsIContentSniffer
> sniffer
=
622 do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER
);
626 nsCAutoString detected
;
627 nsresult rv
= sniffer
->GetMIMETypeFromContent(chan
, aData
, aCount
, detected
);
628 if (NS_SUCCEEDED(rv
))
629 chan
->SetContentType(detected
);
633 nsBaseChannel::OnStartRequest(nsIRequest
*request
, nsISupports
*ctxt
)
635 // If our content type is unknown, then use the content type sniffer. If the
636 // sniffer is not available for some reason, then we just keep going as-is.
637 if (NS_SUCCEEDED(mStatus
) && mContentType
.EqualsLiteral(UNKNOWN_CONTENT_TYPE
)) {
638 mPump
->PeekStream(CallUnknownTypeSniffer
, static_cast<nsIChannel
*>(this));
641 // Now, the general type sniffers. Skip this if we have none.
642 if ((mLoadFlags
& LOAD_CALL_CONTENT_SNIFFERS
) &&
643 gIOService
->GetContentSniffers().Count() != 0)
644 mPump
->PeekStream(CallTypeSniffers
, static_cast<nsIChannel
*>(this));
646 SUSPEND_PUMP_FOR_SCOPE();
648 return mListener
->OnStartRequest(this, mListenerContext
);
652 nsBaseChannel::OnStopRequest(nsIRequest
*request
, nsISupports
*ctxt
,
655 // If both mStatus and status are failure codes, we keep mStatus as-is since
656 // that is consistent with our GetStatus and Cancel methods.
657 if (NS_SUCCEEDED(mStatus
))
660 // Cause IsPending to return false.
663 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
665 mListenerContext
= nsnull
;
667 // No need to suspend pump in this scope since we will not be receiving
668 // any more events from it.
671 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
673 // Drop notification callbacks to prevent cycles.
680 //-----------------------------------------------------------------------------
681 // nsBaseChannel::nsIStreamListener
684 nsBaseChannel::OnDataAvailable(nsIRequest
*request
, nsISupports
*ctxt
,
685 nsIInputStream
*stream
, PRUint32 offset
,
688 SUSPEND_PUMP_FOR_SCOPE();
690 nsresult rv
= mListener
->OnDataAvailable(this, mListenerContext
, stream
,
692 if (mSynthProgressEvents
&& NS_SUCCEEDED(rv
)) {
693 PRUint64 prog
= PRUint64(offset
) + count
;
694 PRUint64 progMax
= ContentLength64();
695 OnTransportStatus(nsnull
, nsITransport::STATUS_READING
, prog
, progMax
);