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
)
92 , mWaitingOnAsyncRedirect(PR_FALSE
)
94 mContentType
.AssignLiteral(UNKNOWN_CONTENT_TYPE
);
98 nsBaseChannel::Redirect(nsIChannel
*newChannel
, PRUint32 redirectFlags
,
99 PRBool openNewChannel
)
101 SUSPEND_PUMP_FOR_SCOPE();
103 // Transfer properties
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 // Make sure to do this _after_ making all the OnChannelRedirect calls
148 newChannel
->SetOriginalURI(OriginalURI());
150 // If we fail to open the new channel, then we want to leave this channel
151 // unaffected, so we defer tearing down our channel until we have succeeded
152 // with the redirect.
154 if (openNewChannel
) {
155 rv
= newChannel
->AsyncOpen(mListener
, mListenerContext
);
160 // close down this channel
161 Cancel(NS_BINDING_REDIRECTED
);
163 mListenerContext
= nsnull
;
169 nsBaseChannel::HasContentTypeHint() const
171 NS_ASSERTION(!IsPending(), "HasContentTypeHint called too late");
172 return !mContentType
.EqualsLiteral(UNKNOWN_CONTENT_TYPE
);
176 nsBaseChannel::SetContentLength64(PRInt64 len
)
178 // XXX: Storing the content-length as a property may not be what we want.
179 // It has the drawback of being copied if we redirect this channel.
180 // Maybe it is time for nsIChannel2.
181 SetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH
, len
);
185 nsBaseChannel::ContentLength64()
188 nsresult rv
= GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH
, &len
);
189 return NS_SUCCEEDED(rv
) ? len
: -1;
193 nsBaseChannel::PushStreamConverter(const char *fromType
,
195 PRBool invalidatesContentLength
,
196 nsIStreamListener
**result
)
198 NS_ASSERTION(mListener
, "no listener");
201 nsCOMPtr
<nsIStreamConverterService
> scs
=
202 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID
, &rv
);
206 nsCOMPtr
<nsIStreamListener
> converter
;
207 rv
= scs
->AsyncConvertData(fromType
, toType
, mListener
, mListenerContext
,
208 getter_AddRefs(converter
));
209 if (NS_SUCCEEDED(rv
)) {
210 mListener
= converter
;
211 if (invalidatesContentLength
)
212 SetContentLength64(-1);
215 converter
.swap(*result
);
222 nsBaseChannel::BeginPumpingData()
224 nsCOMPtr
<nsIInputStream
> stream
;
225 nsCOMPtr
<nsIChannel
> channel
;
226 nsresult rv
= OpenContentStream(PR_TRUE
, getter_AddRefs(stream
),
227 getter_AddRefs(channel
));
231 NS_ASSERTION(!stream
|| !channel
, "Got both a channel and a stream?");
234 rv
= NS_DispatchToCurrentThread(new RedirectRunnable(this, channel
));
235 if (NS_SUCCEEDED(rv
))
236 mWaitingOnAsyncRedirect
= PR_TRUE
;
240 // By assigning mPump, we flag this channel as pending (see IsPending). It's
241 // important that the pending flag is set when we call into the stream (the
242 // call to AsyncRead results in the stream's AsyncWait method being called)
243 // and especially when we call into the loadgroup. Our caller takes care to
244 // release mPump if we return an error.
246 rv
= nsInputStreamPump::Create(getter_AddRefs(mPump
), stream
, -1, -1, 0, 0,
248 if (NS_SUCCEEDED(rv
))
249 rv
= mPump
->AsyncRead(this, nsnull
);
255 nsBaseChannel::HandleAsyncRedirect(nsIChannel
* newChannel
)
257 NS_ASSERTION(!mPump
, "Shouldn't have gotten here");
258 PRBool doNotify
= PR_TRUE
;
259 if (NS_SUCCEEDED(mStatus
)) {
260 nsresult rv
= Redirect(newChannel
, nsIChannelEventSink::REDIRECT_INTERNAL
,
268 mWaitingOnAsyncRedirect
= PR_FALSE
;
271 // Notify our consumer ourselves
272 mListener
->OnStartRequest(this, mListenerContext
);
273 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
275 mListenerContext
= nsnull
;
279 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
281 // Drop notification callbacks to prevent cycles.
286 //-----------------------------------------------------------------------------
287 // nsBaseChannel::nsISupports
289 NS_IMPL_ISUPPORTS_INHERITED6(nsBaseChannel
,
293 nsIInterfaceRequestor
,
294 nsITransportEventSink
,
298 //-----------------------------------------------------------------------------
299 // nsBaseChannel::nsIRequest
302 nsBaseChannel::GetName(nsACString
&result
)
308 return mURI
->GetSpec(result
);
312 nsBaseChannel::IsPending(PRBool
*result
)
314 *result
= IsPending();
319 nsBaseChannel::GetStatus(nsresult
*status
)
321 if (mPump
&& NS_SUCCEEDED(mStatus
)) {
322 mPump
->GetStatus(status
);
330 nsBaseChannel::Cancel(nsresult status
)
332 // Ignore redundant cancelation
333 if (NS_FAILED(mStatus
))
339 mPump
->Cancel(status
);
345 nsBaseChannel::Suspend()
347 NS_ENSURE_TRUE(mPump
, NS_ERROR_NOT_INITIALIZED
);
348 return mPump
->Suspend();
352 nsBaseChannel::Resume()
354 NS_ENSURE_TRUE(mPump
, NS_ERROR_NOT_INITIALIZED
);
355 return mPump
->Resume();
359 nsBaseChannel::GetLoadFlags(nsLoadFlags
*aLoadFlags
)
361 *aLoadFlags
= mLoadFlags
;
366 nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags
)
368 mLoadFlags
= aLoadFlags
;
373 nsBaseChannel::GetLoadGroup(nsILoadGroup
**aLoadGroup
)
375 NS_IF_ADDREF(*aLoadGroup
= mLoadGroup
);
380 nsBaseChannel::SetLoadGroup(nsILoadGroup
*aLoadGroup
)
382 mLoadGroup
= aLoadGroup
;
387 //-----------------------------------------------------------------------------
388 // nsBaseChannel::nsIChannel
391 nsBaseChannel::GetOriginalURI(nsIURI
**aURI
)
393 *aURI
= OriginalURI();
399 nsBaseChannel::SetOriginalURI(nsIURI
*aURI
)
401 NS_ENSURE_ARG_POINTER(aURI
);
407 nsBaseChannel::GetURI(nsIURI
**aURI
)
409 NS_IF_ADDREF(*aURI
= mURI
);
414 nsBaseChannel::GetOwner(nsISupports
**aOwner
)
416 NS_IF_ADDREF(*aOwner
= mOwner
);
421 nsBaseChannel::SetOwner(nsISupports
*aOwner
)
428 nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor
**aCallbacks
)
430 NS_IF_ADDREF(*aCallbacks
= mCallbacks
);
435 nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor
*aCallbacks
)
437 mCallbacks
= aCallbacks
;
443 nsBaseChannel::GetSecurityInfo(nsISupports
**aSecurityInfo
)
445 NS_IF_ADDREF(*aSecurityInfo
= mSecurityInfo
);
450 nsBaseChannel::GetContentType(nsACString
&aContentType
)
452 aContentType
= mContentType
;
457 nsBaseChannel::SetContentType(const nsACString
&aContentType
)
459 // mContentCharset is unchanged if not parsed
461 net_ParseContentType(aContentType
, mContentType
, mContentCharset
, &dummy
);
466 nsBaseChannel::GetContentCharset(nsACString
&aContentCharset
)
468 aContentCharset
= mContentCharset
;
473 nsBaseChannel::SetContentCharset(const nsACString
&aContentCharset
)
475 mContentCharset
= aContentCharset
;
480 nsBaseChannel::GetContentLength(PRInt32
*aContentLength
)
482 PRInt64 len
= ContentLength64();
483 if (len
> PR_INT32_MAX
|| len
< 0)
484 *aContentLength
= -1;
486 *aContentLength
= (PRInt32
) len
;
491 nsBaseChannel::SetContentLength(PRInt32 aContentLength
)
493 SetContentLength64(aContentLength
);
498 nsBaseChannel::Open(nsIInputStream
**result
)
500 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
501 NS_ENSURE_TRUE(!mPump
, NS_ERROR_IN_PROGRESS
);
502 NS_ENSURE_TRUE(!mWasOpened
, NS_ERROR_IN_PROGRESS
);
504 nsCOMPtr
<nsIChannel
> chan
;
505 nsresult rv
= OpenContentStream(PR_FALSE
, result
, getter_AddRefs(chan
));
506 NS_ASSERTION(!chan
|| !*result
, "Got both a channel and a stream?");
507 if (NS_SUCCEEDED(rv
) && chan
) {
508 rv
= Redirect(chan
, nsIChannelEventSink::REDIRECT_INTERNAL
, PR_FALSE
);
511 rv
= chan
->Open(result
);
512 } else if (rv
== NS_ERROR_NOT_IMPLEMENTED
)
513 return NS_ImplementChannelOpen(this, result
);
515 mWasOpened
= NS_SUCCEEDED(rv
);
521 nsBaseChannel::AsyncOpen(nsIStreamListener
*listener
, nsISupports
*ctxt
)
523 NS_ENSURE_TRUE(mURI
, NS_ERROR_NOT_INITIALIZED
);
524 NS_ENSURE_TRUE(!mPump
, NS_ERROR_IN_PROGRESS
);
525 NS_ENSURE_TRUE(!mWasOpened
, NS_ERROR_ALREADY_OPENED
);
526 NS_ENSURE_ARG(listener
);
528 // Ensure that this is an allowed port before proceeding.
529 nsresult rv
= NS_CheckPortSafety(mURI
);
535 // Store the listener and context early so that OpenContentStream and the
536 // stream's AsyncWait method (called by AsyncRead) can have access to them
537 // via PushStreamConverter and the StreamListener methods. However, since
538 // this typically introduces a reference cycle between this and the listener,
539 // we need to be sure to break the reference if this method does not succeed.
540 mListener
= listener
;
541 mListenerContext
= ctxt
;
543 // This method assigns mPump as a side-effect. We need to clear mPump if
544 // this method fails.
545 rv
= BeginPumpingData();
549 mListenerContext
= nsnull
;
554 // At this point, we are going to return success no matter what.
556 mWasOpened
= PR_TRUE
;
558 SUSPEND_PUMP_FOR_SCOPE();
561 mLoadGroup
->AddRequest(this, nsnull
);
566 //-----------------------------------------------------------------------------
567 // nsBaseChannel::nsITransportEventSink
570 nsBaseChannel::OnTransportStatus(nsITransport
*transport
, nsresult status
,
571 PRUint64 progress
, PRUint64 progressMax
)
573 // In some cases, we may wish to suppress transport-layer status events.
575 if (!mPump
|| NS_FAILED(mStatus
) || HasLoadFlag(LOAD_BACKGROUND
))
578 SUSPEND_PUMP_FOR_SCOPE();
580 // Lazily fetch mProgressSink
581 if (!mProgressSink
) {
582 if (mQueriedProgressSink
)
584 GetCallback(mProgressSink
);
585 mQueriedProgressSink
= PR_TRUE
;
590 nsAutoString statusArg
;
591 if (GetStatusArg(status
, statusArg
))
592 mProgressSink
->OnStatus(this, mListenerContext
, status
, statusArg
.get());
595 mProgressSink
->OnProgress(this, mListenerContext
, progress
, progressMax
);
600 //-----------------------------------------------------------------------------
601 // nsBaseChannel::nsIInterfaceRequestor
604 nsBaseChannel::GetInterface(const nsIID
&iid
, void **result
)
606 NS_QueryNotificationCallbacks(mCallbacks
, mLoadGroup
, iid
, result
);
607 return *result
? NS_OK
: NS_ERROR_NO_INTERFACE
;
610 //-----------------------------------------------------------------------------
611 // nsBaseChannel::nsIRequestObserver
614 CallTypeSniffers(void *aClosure
, const PRUint8
*aData
, PRUint32 aCount
)
616 nsIChannel
*chan
= static_cast<nsIChannel
*>(aClosure
);
618 const nsCOMArray
<nsIContentSniffer
>& sniffers
=
619 gIOService
->GetContentSniffers();
620 PRUint32 length
= sniffers
.Count();
621 for (PRUint32 i
= 0; i
< length
; ++i
) {
622 nsCAutoString newType
;
624 sniffers
[i
]->GetMIMETypeFromContent(chan
, aData
, aCount
, newType
);
625 if (NS_SUCCEEDED(rv
) && !newType
.IsEmpty()) {
626 chan
->SetContentType(newType
);
633 CallUnknownTypeSniffer(void *aClosure
, const PRUint8
*aData
, PRUint32 aCount
)
635 nsIChannel
*chan
= static_cast<nsIChannel
*>(aClosure
);
637 nsCOMPtr
<nsIContentSniffer
> sniffer
=
638 do_CreateInstance(NS_GENERIC_CONTENT_SNIFFER
);
642 nsCAutoString detected
;
643 nsresult rv
= sniffer
->GetMIMETypeFromContent(chan
, aData
, aCount
, detected
);
644 if (NS_SUCCEEDED(rv
))
645 chan
->SetContentType(detected
);
649 nsBaseChannel::OnStartRequest(nsIRequest
*request
, nsISupports
*ctxt
)
651 // If our content type is unknown, then use the content type sniffer. If the
652 // sniffer is not available for some reason, then we just keep going as-is.
653 if (NS_SUCCEEDED(mStatus
) && mContentType
.EqualsLiteral(UNKNOWN_CONTENT_TYPE
)) {
654 mPump
->PeekStream(CallUnknownTypeSniffer
, static_cast<nsIChannel
*>(this));
657 // Now, the general type sniffers. Skip this if we have none.
658 if ((mLoadFlags
& LOAD_CALL_CONTENT_SNIFFERS
) &&
659 gIOService
->GetContentSniffers().Count() != 0)
660 mPump
->PeekStream(CallTypeSniffers
, static_cast<nsIChannel
*>(this));
662 SUSPEND_PUMP_FOR_SCOPE();
664 return mListener
->OnStartRequest(this, mListenerContext
);
668 nsBaseChannel::OnStopRequest(nsIRequest
*request
, nsISupports
*ctxt
,
671 // If both mStatus and status are failure codes, we keep mStatus as-is since
672 // that is consistent with our GetStatus and Cancel methods.
673 if (NS_SUCCEEDED(mStatus
))
676 // Cause IsPending to return false.
679 mListener
->OnStopRequest(this, mListenerContext
, mStatus
);
681 mListenerContext
= nsnull
;
683 // No need to suspend pump in this scope since we will not be receiving
684 // any more events from it.
687 mLoadGroup
->RemoveRequest(this, nsnull
, mStatus
);
689 // Drop notification callbacks to prevent cycles.
696 //-----------------------------------------------------------------------------
697 // nsBaseChannel::nsIStreamListener
700 nsBaseChannel::OnDataAvailable(nsIRequest
*request
, nsISupports
*ctxt
,
701 nsIInputStream
*stream
, PRUint32 offset
,
704 SUSPEND_PUMP_FOR_SCOPE();
706 nsresult rv
= mListener
->OnDataAvailable(this, mListenerContext
, stream
,
708 if (mSynthProgressEvents
&& NS_SUCCEEDED(rv
)) {
709 PRUint64 prog
= PRUint64(offset
) + count
;
710 PRUint64 progMax
= ContentLength64();
711 OnTransportStatus(nsnull
, nsITransport::STATUS_READING
, prog
, progMax
);