Bug 470455 - test_database_sync_embed_visits.js leaks, r=sdwilsh
[wine-gecko.git] / netwerk / base / src / nsBaseChannel.cpp
bloba24ccdc0c59dfc7ae7ad3922f2c5bf2bd81c5249
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
13 * License.
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.
21 * Contributor(s):
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);
57 return PL_DHASH_NEXT;
60 // This class is used to suspend a request across a function scope.
61 class ScopedRequestSuspender {
62 public:
63 ScopedRequestSuspender(nsIRequest *request)
64 : mRequest(request) {
65 if (mRequest && NS_FAILED(mRequest->Suspend())) {
66 NS_WARNING("Couldn't suspend pump");
67 mRequest = nsnull;
70 ~ScopedRequestSuspender() {
71 if (mRequest)
72 mRequest->Resume();
74 private:
75 nsIRequest *mRequest;
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 //-----------------------------------------------------------------------------
84 // nsBaseChannel
86 nsBaseChannel::nsBaseChannel()
87 : mLoadFlags(LOAD_NORMAL)
88 , mStatus(NS_OK)
89 , mQueriedProgressSink(PR_TRUE)
90 , mSynthProgressEvents(PR_FALSE)
91 , mWasOpened(PR_FALSE)
92 , mWaitingOnAsyncRedirect(PR_FALSE)
94 mContentType.AssignLiteral(UNKNOWN_CONTENT_TYPE);
97 nsresult
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);
110 if (bag)
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);
121 if (NS_FAILED(rv))
122 return rv;
124 // Backwards compat for non-internal redirects from a HTTP channel.
125 if (!(redirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL)) {
126 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface();
127 if (httpChannel) {
128 nsCOMPtr<nsIHttpEventSink> httpEventSink;
129 GetCallback(httpEventSink);
130 if (httpEventSink) {
131 rv = httpEventSink->OnRedirect(httpChannel, newChannel);
132 if (NS_FAILED(rv))
133 return rv;
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);
143 if (NS_FAILED(rv))
144 return rv;
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);
156 if (NS_FAILED(rv))
157 return rv;
160 // close down this channel
161 Cancel(NS_BINDING_REDIRECTED);
162 mListener = nsnull;
163 mListenerContext = nsnull;
165 return NS_OK;
168 PRBool
169 nsBaseChannel::HasContentTypeHint() const
171 NS_ASSERTION(!IsPending(), "HasContentTypeHint called too late");
172 return !mContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE);
175 void
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);
184 PRInt64
185 nsBaseChannel::ContentLength64()
187 PRInt64 len;
188 nsresult rv = GetPropertyAsInt64(NS_CHANNEL_PROP_CONTENT_LENGTH, &len);
189 return NS_SUCCEEDED(rv) ? len : -1;
192 nsresult
193 nsBaseChannel::PushStreamConverter(const char *fromType,
194 const char *toType,
195 PRBool invalidatesContentLength,
196 nsIStreamListener **result)
198 NS_ASSERTION(mListener, "no listener");
200 nsresult rv;
201 nsCOMPtr<nsIStreamConverterService> scs =
202 do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
203 if (NS_FAILED(rv))
204 return 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);
213 if (result) {
214 *result = nsnull;
215 converter.swap(*result);
218 return rv;
221 nsresult
222 nsBaseChannel::BeginPumpingData()
224 nsCOMPtr<nsIInputStream> stream;
225 nsCOMPtr<nsIChannel> channel;
226 nsresult rv = OpenContentStream(PR_TRUE, getter_AddRefs(stream),
227 getter_AddRefs(channel));
228 if (NS_FAILED(rv))
229 return rv;
231 NS_ASSERTION(!stream || !channel, "Got both a channel and a stream?");
233 if (channel) {
234 rv = NS_DispatchToCurrentThread(new RedirectRunnable(this, channel));
235 if (NS_SUCCEEDED(rv))
236 mWaitingOnAsyncRedirect = PR_TRUE;
237 return rv;
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,
247 PR_TRUE);
248 if (NS_SUCCEEDED(rv))
249 rv = mPump->AsyncRead(this, nsnull);
251 return rv;
254 void
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,
261 PR_TRUE);
262 if (NS_FAILED(rv))
263 Cancel(rv);
264 else
265 doNotify = PR_FALSE;
268 mWaitingOnAsyncRedirect = PR_FALSE;
270 if (doNotify) {
271 // Notify our consumer ourselves
272 mListener->OnStartRequest(this, mListenerContext);
273 mListener->OnStopRequest(this, mListenerContext, mStatus);
274 mListener = nsnull;
275 mListenerContext = nsnull;
278 if (mLoadGroup)
279 mLoadGroup->RemoveRequest(this, nsnull, mStatus);
281 // Drop notification callbacks to prevent cycles.
282 mCallbacks = nsnull;
283 CallbacksChanged();
286 //-----------------------------------------------------------------------------
287 // nsBaseChannel::nsISupports
289 NS_IMPL_ISUPPORTS_INHERITED6(nsBaseChannel,
290 nsHashPropertyBag,
291 nsIRequest,
292 nsIChannel,
293 nsIInterfaceRequestor,
294 nsITransportEventSink,
295 nsIRequestObserver,
296 nsIStreamListener)
298 //-----------------------------------------------------------------------------
299 // nsBaseChannel::nsIRequest
301 NS_IMETHODIMP
302 nsBaseChannel::GetName(nsACString &result)
304 if (!mURI) {
305 result.Truncate();
306 return NS_OK;
308 return mURI->GetSpec(result);
311 NS_IMETHODIMP
312 nsBaseChannel::IsPending(PRBool *result)
314 *result = IsPending();
315 return NS_OK;
318 NS_IMETHODIMP
319 nsBaseChannel::GetStatus(nsresult *status)
321 if (mPump && NS_SUCCEEDED(mStatus)) {
322 mPump->GetStatus(status);
323 } else {
324 *status = mStatus;
326 return NS_OK;
329 NS_IMETHODIMP
330 nsBaseChannel::Cancel(nsresult status)
332 // Ignore redundant cancelation
333 if (NS_FAILED(mStatus))
334 return NS_OK;
336 mStatus = status;
338 if (mPump)
339 mPump->Cancel(status);
341 return NS_OK;
344 NS_IMETHODIMP
345 nsBaseChannel::Suspend()
347 NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
348 return mPump->Suspend();
351 NS_IMETHODIMP
352 nsBaseChannel::Resume()
354 NS_ENSURE_TRUE(mPump, NS_ERROR_NOT_INITIALIZED);
355 return mPump->Resume();
358 NS_IMETHODIMP
359 nsBaseChannel::GetLoadFlags(nsLoadFlags *aLoadFlags)
361 *aLoadFlags = mLoadFlags;
362 return NS_OK;
365 NS_IMETHODIMP
366 nsBaseChannel::SetLoadFlags(nsLoadFlags aLoadFlags)
368 mLoadFlags = aLoadFlags;
369 return NS_OK;
372 NS_IMETHODIMP
373 nsBaseChannel::GetLoadGroup(nsILoadGroup **aLoadGroup)
375 NS_IF_ADDREF(*aLoadGroup = mLoadGroup);
376 return NS_OK;
379 NS_IMETHODIMP
380 nsBaseChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
382 mLoadGroup = aLoadGroup;
383 CallbacksChanged();
384 return NS_OK;
387 //-----------------------------------------------------------------------------
388 // nsBaseChannel::nsIChannel
390 NS_IMETHODIMP
391 nsBaseChannel::GetOriginalURI(nsIURI **aURI)
393 *aURI = OriginalURI();
394 NS_ADDREF(*aURI);
395 return NS_OK;
398 NS_IMETHODIMP
399 nsBaseChannel::SetOriginalURI(nsIURI *aURI)
401 NS_ENSURE_ARG_POINTER(aURI);
402 mOriginalURI = aURI;
403 return NS_OK;
406 NS_IMETHODIMP
407 nsBaseChannel::GetURI(nsIURI **aURI)
409 NS_IF_ADDREF(*aURI = mURI);
410 return NS_OK;
413 NS_IMETHODIMP
414 nsBaseChannel::GetOwner(nsISupports **aOwner)
416 NS_IF_ADDREF(*aOwner = mOwner);
417 return NS_OK;
420 NS_IMETHODIMP
421 nsBaseChannel::SetOwner(nsISupports *aOwner)
423 mOwner = aOwner;
424 return NS_OK;
427 NS_IMETHODIMP
428 nsBaseChannel::GetNotificationCallbacks(nsIInterfaceRequestor **aCallbacks)
430 NS_IF_ADDREF(*aCallbacks = mCallbacks);
431 return NS_OK;
434 NS_IMETHODIMP
435 nsBaseChannel::SetNotificationCallbacks(nsIInterfaceRequestor *aCallbacks)
437 mCallbacks = aCallbacks;
438 CallbacksChanged();
439 return NS_OK;
442 NS_IMETHODIMP
443 nsBaseChannel::GetSecurityInfo(nsISupports **aSecurityInfo)
445 NS_IF_ADDREF(*aSecurityInfo = mSecurityInfo);
446 return NS_OK;
449 NS_IMETHODIMP
450 nsBaseChannel::GetContentType(nsACString &aContentType)
452 aContentType = mContentType;
453 return NS_OK;
456 NS_IMETHODIMP
457 nsBaseChannel::SetContentType(const nsACString &aContentType)
459 // mContentCharset is unchanged if not parsed
460 PRBool dummy;
461 net_ParseContentType(aContentType, mContentType, mContentCharset, &dummy);
462 return NS_OK;
465 NS_IMETHODIMP
466 nsBaseChannel::GetContentCharset(nsACString &aContentCharset)
468 aContentCharset = mContentCharset;
469 return NS_OK;
472 NS_IMETHODIMP
473 nsBaseChannel::SetContentCharset(const nsACString &aContentCharset)
475 mContentCharset = aContentCharset;
476 return NS_OK;
479 NS_IMETHODIMP
480 nsBaseChannel::GetContentLength(PRInt32 *aContentLength)
482 PRInt64 len = ContentLength64();
483 if (len > PR_INT32_MAX || len < 0)
484 *aContentLength = -1;
485 else
486 *aContentLength = (PRInt32) len;
487 return NS_OK;
490 NS_IMETHODIMP
491 nsBaseChannel::SetContentLength(PRInt32 aContentLength)
493 SetContentLength64(aContentLength);
494 return NS_OK;
497 NS_IMETHODIMP
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);
509 if (NS_FAILED(rv))
510 return rv;
511 rv = chan->Open(result);
512 } else if (rv == NS_ERROR_NOT_IMPLEMENTED)
513 return NS_ImplementChannelOpen(this, result);
515 mWasOpened = NS_SUCCEEDED(rv);
517 return rv;
520 NS_IMETHODIMP
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);
530 if (NS_FAILED(rv)) {
531 mCallbacks = nsnull;
532 return rv;
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();
546 if (NS_FAILED(rv)) {
547 mPump = nsnull;
548 mListener = nsnull;
549 mListenerContext = nsnull;
550 mCallbacks = nsnull;
551 return rv;
554 // At this point, we are going to return success no matter what.
556 mWasOpened = PR_TRUE;
558 SUSPEND_PUMP_FOR_SCOPE();
560 if (mLoadGroup)
561 mLoadGroup->AddRequest(this, nsnull);
563 return NS_OK;
566 //-----------------------------------------------------------------------------
567 // nsBaseChannel::nsITransportEventSink
569 NS_IMETHODIMP
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))
576 return NS_OK;
578 SUSPEND_PUMP_FOR_SCOPE();
580 // Lazily fetch mProgressSink
581 if (!mProgressSink) {
582 if (mQueriedProgressSink)
583 return NS_OK;
584 GetCallback(mProgressSink);
585 mQueriedProgressSink = PR_TRUE;
586 if (!mProgressSink)
587 return NS_OK;
590 nsAutoString statusArg;
591 if (GetStatusArg(status, statusArg))
592 mProgressSink->OnStatus(this, mListenerContext, status, statusArg.get());
594 if (progress)
595 mProgressSink->OnProgress(this, mListenerContext, progress, progressMax);
597 return NS_OK;
600 //-----------------------------------------------------------------------------
601 // nsBaseChannel::nsIInterfaceRequestor
603 NS_IMETHODIMP
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
613 static void
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;
623 nsresult rv =
624 sniffers[i]->GetMIMETypeFromContent(chan, aData, aCount, newType);
625 if (NS_SUCCEEDED(rv) && !newType.IsEmpty()) {
626 chan->SetContentType(newType);
627 break;
632 static void
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);
639 if (!sniffer)
640 return;
642 nsCAutoString detected;
643 nsresult rv = sniffer->GetMIMETypeFromContent(chan, aData, aCount, detected);
644 if (NS_SUCCEEDED(rv))
645 chan->SetContentType(detected);
648 NS_IMETHODIMP
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);
667 NS_IMETHODIMP
668 nsBaseChannel::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
669 nsresult status)
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))
674 mStatus = status;
676 // Cause IsPending to return false.
677 mPump = nsnull;
679 mListener->OnStopRequest(this, mListenerContext, mStatus);
680 mListener = nsnull;
681 mListenerContext = nsnull;
683 // No need to suspend pump in this scope since we will not be receiving
684 // any more events from it.
686 if (mLoadGroup)
687 mLoadGroup->RemoveRequest(this, nsnull, mStatus);
689 // Drop notification callbacks to prevent cycles.
690 mCallbacks = nsnull;
691 CallbacksChanged();
693 return NS_OK;
696 //-----------------------------------------------------------------------------
697 // nsBaseChannel::nsIStreamListener
699 NS_IMETHODIMP
700 nsBaseChannel::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
701 nsIInputStream *stream, PRUint32 offset,
702 PRUint32 count)
704 SUSPEND_PUMP_FOR_SCOPE();
706 nsresult rv = mListener->OnDataAvailable(this, mListenerContext, stream,
707 offset, count);
708 if (mSynthProgressEvents && NS_SUCCEEDED(rv)) {
709 PRUint64 prog = PRUint64(offset) + count;
710 PRUint64 progMax = ContentLength64();
711 OnTransportStatus(nsnull, nsITransport::STATUS_READING, prog, progMax);
714 return rv;