michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsPluginStreamListenerPeer.h" michael@0: #include "nsIStreamConverterService.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsIFileChannel.h" michael@0: #include "nsMimeTypes.h" michael@0: #include "nsISupportsPrimitives.h" michael@0: #include "nsNetCID.h" michael@0: #include "nsPluginLogging.h" michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsPluginHost.h" michael@0: #include "nsIByteRangeRequest.h" michael@0: #include "nsIMultiPartChannel.h" michael@0: #include "nsIInputStreamTee.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsPluginNativeWindow.h" michael@0: #include "GeckoProfiler.h" michael@0: #include "nsPluginInstanceOwner.h" michael@0: #include "nsDataHashtable.h" michael@0: michael@0: #define MAGIC_REQUEST_CONTEXT 0x01020304 michael@0: michael@0: // nsPluginByteRangeStreamListener michael@0: michael@0: class nsPluginByteRangeStreamListener michael@0: : public nsIStreamListener michael@0: , public nsIInterfaceRequestor michael@0: { michael@0: public: michael@0: nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr); michael@0: virtual ~nsPluginByteRangeStreamListener(); michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: NS_DECL_NSIREQUESTOBSERVER michael@0: NS_DECL_NSISTREAMLISTENER michael@0: NS_DECL_NSIINTERFACEREQUESTOR michael@0: michael@0: private: michael@0: nsCOMPtr mStreamConverter; michael@0: nsWeakPtr mWeakPtrPluginStreamListenerPeer; michael@0: bool mRemoveMagicNumber; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsPluginByteRangeStreamListener, michael@0: nsIRequestObserver, michael@0: nsIStreamListener, michael@0: nsIInterfaceRequestor) michael@0: michael@0: nsPluginByteRangeStreamListener::nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr) michael@0: { michael@0: mWeakPtrPluginStreamListenerPeer = aWeakPtr; michael@0: mRemoveMagicNumber = false; michael@0: } michael@0: michael@0: nsPluginByteRangeStreamListener::~nsPluginByteRangeStreamListener() michael@0: { michael@0: mStreamConverter = 0; michael@0: mWeakPtrPluginStreamListenerPeer = 0; michael@0: } michael@0: michael@0: /** michael@0: * Unwrap any byte-range requests so that we can check whether the base channel michael@0: * is being tracked properly. michael@0: */ michael@0: static nsCOMPtr michael@0: GetBaseRequest(nsIRequest* r) michael@0: { michael@0: nsCOMPtr mp = do_QueryInterface(r); michael@0: if (!mp) michael@0: return r; michael@0: michael@0: nsCOMPtr base; michael@0: mp->GetBaseChannel(getter_AddRefs(base)); michael@0: return already_AddRefed(base.forget()); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginByteRangeStreamListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); michael@0: if (!finalStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsPluginStreamListenerPeer *pslp = michael@0: static_cast(finalStreamListener.get()); michael@0: michael@0: NS_ASSERTION(pslp->mRequests.IndexOfObject(GetBaseRequest(request)) != -1, michael@0: "Untracked byte-range request?"); michael@0: michael@0: nsCOMPtr serv = do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = serv->AsyncConvertData(MULTIPART_BYTERANGES, michael@0: "*/*", michael@0: finalStreamListener, michael@0: nullptr, michael@0: getter_AddRefs(mStreamConverter)); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mStreamConverter->OnStartRequest(request, ctxt); michael@0: if (NS_SUCCEEDED(rv)) michael@0: return rv; michael@0: } michael@0: } michael@0: mStreamConverter = 0; michael@0: michael@0: nsCOMPtr httpChannel(do_QueryInterface(request)); michael@0: if (!httpChannel) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint32_t responseCode = 0; michael@0: rv = httpChannel->GetResponseStatus(&responseCode); michael@0: if (NS_FAILED(rv)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (responseCode != 200) { michael@0: uint32_t wantsAllNetworkStreams = 0; michael@0: rv = pslp->GetPluginInstance()->GetValueFromPlugin(NPPVpluginWantsAllNetworkStreams, michael@0: &wantsAllNetworkStreams); michael@0: // If the call returned an error code make sure we still use our default value. michael@0: if (NS_FAILED(rv)) { michael@0: wantsAllNetworkStreams = 0; michael@0: } michael@0: michael@0: if (!wantsAllNetworkStreams){ michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // if server cannot continue with byte range (206 status) and sending us whole object (200 status) michael@0: // reset this seekable stream & try serve it to plugin instance as a file michael@0: mStreamConverter = finalStreamListener; michael@0: mRemoveMagicNumber = true; michael@0: michael@0: rv = pslp->ServeStreamAsFile(request, ctxt); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginByteRangeStreamListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt, michael@0: nsresult status) michael@0: { michael@0: if (!mStreamConverter) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); michael@0: if (!finalStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsPluginStreamListenerPeer *pslp = michael@0: static_cast(finalStreamListener.get()); michael@0: bool found = pslp->mRequests.RemoveObject(request); michael@0: if (!found) { michael@0: NS_ERROR("OnStopRequest received for untracked byte-range request!"); michael@0: } michael@0: michael@0: if (mRemoveMagicNumber) { michael@0: // remove magic number from container michael@0: nsCOMPtr container = do_QueryInterface(ctxt); michael@0: if (container) { michael@0: uint32_t magicNumber = 0; michael@0: container->GetData(&magicNumber); michael@0: if (magicNumber == MAGIC_REQUEST_CONTEXT) { michael@0: // to allow properly finish nsPluginStreamListenerPeer->OnStopRequest() michael@0: // set it to something that is not the magic number. michael@0: container->SetData(0); michael@0: } michael@0: } else { michael@0: NS_WARNING("Bad state of nsPluginByteRangeStreamListener"); michael@0: } michael@0: } michael@0: michael@0: return mStreamConverter->OnStopRequest(request, ctxt, status); michael@0: } michael@0: michael@0: // CachedFileHolder michael@0: michael@0: CachedFileHolder::CachedFileHolder(nsIFile* cacheFile) michael@0: : mFile(cacheFile) michael@0: { michael@0: NS_ASSERTION(mFile, "Empty CachedFileHolder"); michael@0: } michael@0: michael@0: CachedFileHolder::~CachedFileHolder() michael@0: { michael@0: mFile->Remove(false); michael@0: } michael@0: michael@0: void michael@0: CachedFileHolder::AddRef() michael@0: { michael@0: ++mRefCnt; michael@0: NS_LOG_ADDREF(this, mRefCnt, "CachedFileHolder", sizeof(*this)); michael@0: } michael@0: michael@0: void michael@0: CachedFileHolder::Release() michael@0: { michael@0: --mRefCnt; michael@0: NS_LOG_RELEASE(this, mRefCnt, "CachedFileHolder"); michael@0: if (0 == mRefCnt) michael@0: delete this; michael@0: } michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginByteRangeStreamListener::OnDataAvailable(nsIRequest *request, nsISupports *ctxt, michael@0: nsIInputStream *inStr, michael@0: uint64_t sourceOffset, michael@0: uint32_t count) michael@0: { michael@0: if (!mStreamConverter) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); michael@0: if (!finalStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return mStreamConverter->OnDataAvailable(request, ctxt, inStr, sourceOffset, count); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginByteRangeStreamListener::GetInterface(const nsIID& aIID, void** result) michael@0: { michael@0: // Forward interface requests to our parent michael@0: nsCOMPtr finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer); michael@0: if (!finalStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return finalStreamListener->GetInterface(aIID, result); michael@0: } michael@0: michael@0: // nsPluginStreamListenerPeer michael@0: michael@0: NS_IMPL_ISUPPORTS(nsPluginStreamListenerPeer, michael@0: nsIStreamListener, michael@0: nsIRequestObserver, michael@0: nsIHttpHeaderVisitor, michael@0: nsISupportsWeakReference, michael@0: nsIInterfaceRequestor, michael@0: nsIChannelEventSink) michael@0: michael@0: nsPluginStreamListenerPeer::nsPluginStreamListenerPeer() michael@0: { michael@0: mStreamType = NP_NORMAL; michael@0: mStartBinding = false; michael@0: mAbort = false; michael@0: mRequestFailed = false; michael@0: michael@0: mPendingRequests = 0; michael@0: mHaveFiredOnStartRequest = false; michael@0: mDataForwardToRequest = nullptr; michael@0: michael@0: mSeekable = false; michael@0: mModified = 0; michael@0: mStreamOffset = 0; michael@0: mStreamComplete = 0; michael@0: } michael@0: michael@0: nsPluginStreamListenerPeer::~nsPluginStreamListenerPeer() michael@0: { michael@0: #ifdef PLUGIN_LOGGING michael@0: PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, michael@0: ("nsPluginStreamListenerPeer::dtor this=%p, url=%s\n",this, mURLSpec.get())); michael@0: #endif michael@0: michael@0: if (mPStreamListener) { michael@0: mPStreamListener->SetStreamListenerPeer(nullptr); michael@0: } michael@0: michael@0: // close FD of mFileCacheOutputStream if it's still open michael@0: // or we won't be able to remove the cache file michael@0: if (mFileCacheOutputStream) michael@0: mFileCacheOutputStream = nullptr; michael@0: michael@0: delete mDataForwardToRequest; michael@0: michael@0: if (mPluginInstance) michael@0: mPluginInstance->FileCachedStreamListeners()->RemoveElement(this); michael@0: } michael@0: michael@0: // Called as a result of GetURL and PostURL, or by the host in the case of the michael@0: // initial plugin stream. michael@0: nsresult nsPluginStreamListenerPeer::Initialize(nsIURI *aURL, michael@0: nsNPAPIPluginInstance *aInstance, michael@0: nsNPAPIPluginStreamListener* aListener) michael@0: { michael@0: #ifdef PLUGIN_LOGGING michael@0: nsAutoCString urlSpec; michael@0: if (aURL != nullptr) aURL->GetSpec(urlSpec); michael@0: michael@0: PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, michael@0: ("nsPluginStreamListenerPeer::Initialize instance=%p, url=%s\n", aInstance, urlSpec.get())); michael@0: michael@0: PR_LogFlush(); michael@0: #endif michael@0: michael@0: // Not gonna work out michael@0: if (!aInstance) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mURL = aURL; michael@0: michael@0: NS_ASSERTION(mPluginInstance == nullptr, michael@0: "nsPluginStreamListenerPeer::Initialize mPluginInstance != nullptr"); michael@0: mPluginInstance = aInstance; michael@0: michael@0: // If the plugin did not request this stream, e.g. the initial stream, we wont michael@0: // have a nsNPAPIPluginStreamListener yet - this will be handled by michael@0: // SetUpStreamListener michael@0: if (aListener) { michael@0: mPStreamListener = aListener; michael@0: mPStreamListener->SetStreamListenerPeer(this); michael@0: } michael@0: michael@0: mPendingRequests = 1; michael@0: michael@0: mDataForwardToRequest = new nsDataHashtable(); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // SetupPluginCacheFile is called if we have to save the stream to disk. michael@0: // michael@0: // These files will be deleted when the host is destroyed. michael@0: // michael@0: // TODO? What if we fill up the the dest dir? michael@0: nsresult michael@0: nsPluginStreamListenerPeer::SetupPluginCacheFile(nsIChannel* channel) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: bool useExistingCacheFile = false; michael@0: nsRefPtr pluginHost = nsPluginHost::GetInst(); michael@0: michael@0: // Look for an existing cache file for the URI. michael@0: nsTArray< nsRefPtr > *instances = pluginHost->InstanceArray(); michael@0: for (uint32_t i = 0; i < instances->Length(); i++) { michael@0: // most recent streams are at the end of list michael@0: nsTArray *streamListeners = instances->ElementAt(i)->FileCachedStreamListeners(); michael@0: for (int32_t i = streamListeners->Length() - 1; i >= 0; --i) { michael@0: nsPluginStreamListenerPeer *lp = streamListeners->ElementAt(i); michael@0: if (lp && lp->mLocalCachedFileHolder) { michael@0: useExistingCacheFile = lp->UseExistingPluginCacheFile(this); michael@0: if (useExistingCacheFile) { michael@0: mLocalCachedFileHolder = lp->mLocalCachedFileHolder; michael@0: break; michael@0: } michael@0: } michael@0: if (useExistingCacheFile) michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // Create a new cache file if one could not be found. michael@0: if (!useExistingCacheFile) { michael@0: nsCOMPtr pluginTmp; michael@0: rv = nsPluginHost::GetPluginTempDir(getter_AddRefs(pluginTmp)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: // Get the filename from the channel michael@0: nsCOMPtr uri; michael@0: rv = channel->GetURI(getter_AddRefs(uri)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr url(do_QueryInterface(uri)); michael@0: if (!url) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoCString filename; michael@0: url->GetFileName(filename); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Create a file to save our stream into. Should we scramble the name? michael@0: filename.Insert(NS_LITERAL_CSTRING("plugin-"), 0); michael@0: rv = pluginTmp->AppendNative(filename); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // Yes, make it unique. michael@0: rv = pluginTmp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // create a file output stream to write to... michael@0: nsCOMPtr outstream; michael@0: rv = NS_NewLocalFileOutputStream(getter_AddRefs(mFileCacheOutputStream), pluginTmp, -1, 00600); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: // save the file. michael@0: mLocalCachedFileHolder = new CachedFileHolder(pluginTmp); michael@0: } michael@0: michael@0: // add this listenerPeer to list of stream peers for this instance michael@0: mPluginInstance->FileCachedStreamListeners()->AppendElement(this); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request, michael@0: nsISupports* aContext) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: PROFILER_LABEL("nsPluginStreamListenerPeer", "OnStartRequest"); michael@0: michael@0: if (mRequests.IndexOfObject(GetBaseRequest(request)) == -1) { michael@0: NS_ASSERTION(mRequests.Count() == 0, michael@0: "Only our initial stream should be unknown!"); michael@0: TrackRequest(request); michael@0: } michael@0: michael@0: if (mHaveFiredOnStartRequest) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: mHaveFiredOnStartRequest = true; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); michael@0: michael@0: // deal with 404 (Not Found) HTTP response, michael@0: // just return, this causes the request to be ignored. michael@0: nsCOMPtr httpChannel(do_QueryInterface(channel)); michael@0: if (httpChannel) { michael@0: uint32_t responseCode = 0; michael@0: rv = httpChannel->GetResponseStatus(&responseCode); michael@0: if (NS_FAILED(rv)) { michael@0: // NPP_Notify() will be called from OnStopRequest michael@0: // in nsNPAPIPluginStreamListener::CleanUpStream michael@0: // return error will cancel this request michael@0: // ...and we also need to tell the plugin that michael@0: mRequestFailed = true; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (responseCode > 206) { // not normal michael@0: uint32_t wantsAllNetworkStreams = 0; michael@0: michael@0: // We don't always have an instance here already, but if we do, check michael@0: // to see if it wants all streams. michael@0: if (mPluginInstance) { michael@0: rv = mPluginInstance->GetValueFromPlugin(NPPVpluginWantsAllNetworkStreams, michael@0: &wantsAllNetworkStreams); michael@0: // If the call returned an error code make sure we still use our default value. michael@0: if (NS_FAILED(rv)) { michael@0: wantsAllNetworkStreams = 0; michael@0: } michael@0: } michael@0: michael@0: if (!wantsAllNetworkStreams) { michael@0: mRequestFailed = true; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Get the notification callbacks from the channel and save it as michael@0: // week ref we'll use it in nsPluginStreamInfo::RequestRead() when michael@0: // we'll create channel for byte range request. michael@0: nsCOMPtr callbacks; michael@0: channel->GetNotificationCallbacks(getter_AddRefs(callbacks)); michael@0: if (callbacks) michael@0: mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks); michael@0: michael@0: nsCOMPtr loadGroup; michael@0: channel->GetLoadGroup(getter_AddRefs(loadGroup)); michael@0: if (loadGroup) michael@0: mWeakPtrChannelLoadGroup = do_GetWeakReference(loadGroup); michael@0: michael@0: int64_t length; michael@0: rv = channel->GetContentLength(&length); michael@0: michael@0: // it's possible for the server to not send a Content-Length. michael@0: // we should still work in this case. michael@0: if (NS_FAILED(rv) || length < 0 || length > UINT32_MAX) { michael@0: // check out if this is file channel michael@0: nsCOMPtr fileChannel = do_QueryInterface(channel); michael@0: if (fileChannel) { michael@0: // file does not exist michael@0: mRequestFailed = true; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: mLength = 0; michael@0: } michael@0: else { michael@0: mLength = uint32_t(length); michael@0: } michael@0: michael@0: nsAutoCString aContentType; // XXX but we already got the type above! michael@0: rv = channel->GetContentType(aContentType); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr aURL; michael@0: rv = channel->GetURI(getter_AddRefs(aURL)); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: aURL->GetSpec(mURLSpec); michael@0: michael@0: if (!aContentType.IsEmpty()) michael@0: mContentType = aContentType; michael@0: michael@0: #ifdef PLUGIN_LOGGING michael@0: PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NOISY, michael@0: ("nsPluginStreamListenerPeer::OnStartRequest this=%p request=%p mime=%s, url=%s\n", michael@0: this, request, aContentType.get(), mURLSpec.get())); michael@0: michael@0: PR_LogFlush(); michael@0: #endif michael@0: michael@0: // Set up the stream listener... michael@0: rv = SetUpStreamListener(request, aURL); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPluginStreamListenerPeer::OnProgress(nsIRequest *request, michael@0: nsISupports* aContext, michael@0: uint64_t aProgress, michael@0: uint64_t aProgressMax) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPluginStreamListenerPeer::OnStatus(nsIRequest *request, michael@0: nsISupports* aContext, michael@0: nsresult aStatus, michael@0: const char16_t* aStatusArg) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::GetContentType(char** result) michael@0: { michael@0: *result = const_cast(mContentType.get()); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::IsSeekable(bool* result) michael@0: { michael@0: *result = mSeekable; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::GetLength(uint32_t* result) michael@0: { michael@0: *result = mLength; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::GetLastModified(uint32_t* result) michael@0: { michael@0: *result = mModified; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::GetURL(const char** result) michael@0: { michael@0: *result = mURLSpec.get(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsPluginStreamListenerPeer::MakeByteRangeString(NPByteRange* aRangeList, nsACString &rangeRequest, michael@0: int32_t *numRequests) michael@0: { michael@0: rangeRequest.Truncate(); michael@0: *numRequests = 0; michael@0: //the string should look like this: bytes=500-700,601-999 michael@0: if (!aRangeList) michael@0: return; michael@0: michael@0: int32_t requestCnt = 0; michael@0: nsAutoCString string("bytes="); michael@0: michael@0: for (NPByteRange * range = aRangeList; range != nullptr; range = range->next) { michael@0: // XXX zero length? michael@0: if (!range->length) michael@0: continue; michael@0: michael@0: // XXX needs to be fixed for negative offsets michael@0: string.AppendInt(range->offset); michael@0: string.Append("-"); michael@0: string.AppendInt(range->offset + range->length - 1); michael@0: if (range->next) michael@0: string += ","; michael@0: michael@0: requestCnt++; michael@0: } michael@0: michael@0: // get rid of possible trailing comma michael@0: string.Trim(",", false); michael@0: michael@0: rangeRequest = string; michael@0: *numRequests = requestCnt; michael@0: return; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::RequestRead(NPByteRange* rangeList) michael@0: { michael@0: nsAutoCString rangeString; michael@0: int32_t numRequests; michael@0: michael@0: MakeByteRangeString(rangeList, rangeString, &numRequests); michael@0: michael@0: if (numRequests == 0) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsCOMPtr callbacks = do_QueryReferent(mWeakPtrChannelCallbacks); michael@0: nsCOMPtr loadGroup = do_QueryReferent(mWeakPtrChannelLoadGroup); michael@0: nsCOMPtr channel; michael@0: rv = NS_NewChannel(getter_AddRefs(channel), mURL, nullptr, loadGroup, callbacks); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr httpChannel(do_QueryInterface(channel)); michael@0: if (!httpChannel) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false); michael@0: michael@0: mAbort = true; // instruct old stream listener to cancel michael@0: // the request on the next ODA. michael@0: michael@0: nsCOMPtr converter; michael@0: michael@0: if (numRequests == 1) { michael@0: converter = this; michael@0: // set current stream offset equal to the first offset in the range list michael@0: // it will work for single byte range request michael@0: // for multy range we'll reset it in ODA michael@0: SetStreamOffset(rangeList->offset); michael@0: } else { michael@0: nsWeakPtr weakpeer = michael@0: do_GetWeakReference(static_cast(this)); michael@0: nsPluginByteRangeStreamListener *brrListener = michael@0: new nsPluginByteRangeStreamListener(weakpeer); michael@0: if (brrListener) michael@0: converter = brrListener; michael@0: else michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: mPendingRequests += numRequests; michael@0: michael@0: nsCOMPtr container = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: rv = container->SetData(MAGIC_REQUEST_CONTEXT); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: rv = channel->AsyncOpen(converter, container); michael@0: if (NS_SUCCEEDED(rv)) michael@0: TrackRequest(channel); michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::GetStreamOffset(int32_t* result) michael@0: { michael@0: *result = mStreamOffset; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::SetStreamOffset(int32_t value) michael@0: { michael@0: mStreamOffset = value; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsPluginStreamListenerPeer::ServeStreamAsFile(nsIRequest *request, michael@0: nsISupports* aContext) michael@0: { michael@0: if (!mPluginInstance) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // mPluginInstance->Stop calls mPStreamListener->CleanUpStream(), so stream will be properly clean up michael@0: mPluginInstance->Stop(); michael@0: mPluginInstance->Start(); michael@0: nsRefPtr owner = mPluginInstance->GetOwner(); michael@0: if (owner) { michael@0: NPWindow* window = nullptr; michael@0: owner->GetWindow(window); michael@0: #if (MOZ_WIDGET_GTK == 2) || defined(MOZ_WIDGET_QT) michael@0: // Should call GetPluginPort() here. michael@0: // This part is copied from nsPluginInstanceOwner::GetPluginPort(). michael@0: nsCOMPtr widget; michael@0: ((nsPluginNativeWindow*)window)->GetPluginWidget(getter_AddRefs(widget)); michael@0: if (widget) { michael@0: window->window = widget->GetNativeData(NS_NATIVE_PLUGIN_PORT); michael@0: } michael@0: #endif michael@0: owner->CallSetWindow(); michael@0: } michael@0: michael@0: mSeekable = false; michael@0: mPStreamListener->OnStartBinding(this); michael@0: mStreamOffset = 0; michael@0: michael@0: // force the plugin to use stream as file michael@0: mStreamType = NP_ASFILE; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: if (channel) { michael@0: SetupPluginCacheFile(channel); michael@0: } michael@0: michael@0: // unset mPendingRequests michael@0: mPendingRequests = 0; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsPluginStreamListenerPeer::UseExistingPluginCacheFile(nsPluginStreamListenerPeer* psi) michael@0: { michael@0: NS_ENSURE_TRUE(psi, false); michael@0: michael@0: if (psi->mLength == mLength && michael@0: psi->mModified == mModified && michael@0: mStreamComplete && michael@0: mURLSpec.Equals(psi->mURLSpec)) michael@0: { michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request, michael@0: nsISupports* aContext, michael@0: nsIInputStream *aIStream, michael@0: uint64_t sourceOffset, michael@0: uint32_t aLength) michael@0: { michael@0: if (mRequests.IndexOfObject(GetBaseRequest(request)) == -1) { michael@0: MOZ_ASSERT(false, "Received OnDataAvailable for untracked request."); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: if (mRequestFailed) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: if (mAbort) { michael@0: uint32_t magicNumber = 0; // set it to something that is not the magic number. michael@0: nsCOMPtr container = do_QueryInterface(aContext); michael@0: if (container) michael@0: container->GetData(&magicNumber); michael@0: michael@0: if (magicNumber != MAGIC_REQUEST_CONTEXT) { michael@0: // this is not one of our range requests michael@0: mAbort = false; michael@0: return NS_BINDING_ABORTED; michael@0: } michael@0: } michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: if (!mPStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: const char * url = nullptr; michael@0: GetURL(&url); michael@0: michael@0: PLUGIN_LOG(PLUGIN_LOG_NOISY, michael@0: ("nsPluginStreamListenerPeer::OnDataAvailable this=%p request=%p, offset=%llu, length=%u, url=%s\n", michael@0: this, request, sourceOffset, aLength, url ? url : "no url set")); michael@0: michael@0: // if the plugin has requested an AsFileOnly stream, then don't michael@0: // call OnDataAvailable michael@0: if (mStreamType != NP_ASFILEONLY) { michael@0: // get the absolute offset of the request, if one exists. michael@0: nsCOMPtr brr = do_QueryInterface(request); michael@0: if (brr) { michael@0: if (!mDataForwardToRequest) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: int64_t absoluteOffset64 = 0; michael@0: brr->GetStartRange(&absoluteOffset64); michael@0: michael@0: // XXX handle 64-bit for real michael@0: int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64); michael@0: michael@0: // we need to track how much data we have forwarded to the michael@0: // plugin. michael@0: michael@0: // FIXME: http://bugzilla.mozilla.org/show_bug.cgi?id=240130 michael@0: // michael@0: // Why couldn't this be tracked on the plugin info, and not in a michael@0: // *hash table*? michael@0: int32_t amtForwardToPlugin = mDataForwardToRequest->Get(absoluteOffset); michael@0: mDataForwardToRequest->Put(absoluteOffset, (amtForwardToPlugin + aLength)); michael@0: michael@0: SetStreamOffset(absoluteOffset + amtForwardToPlugin); michael@0: } michael@0: michael@0: nsCOMPtr stream = aIStream; michael@0: michael@0: // if we are caching the file ourselves to disk, we want to 'tee' off michael@0: // the data as the plugin read from the stream. We do this by the magic michael@0: // of an input stream tee. michael@0: michael@0: if (mFileCacheOutputStream) { michael@0: rv = NS_NewInputStreamTee(getter_AddRefs(stream), aIStream, mFileCacheOutputStream); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: } michael@0: michael@0: rv = mPStreamListener->OnDataAvailable(this, michael@0: stream, michael@0: aLength); michael@0: michael@0: // if a plugin returns an error, the peer must kill the stream michael@0: // else the stream and PluginStreamListener leak michael@0: if (NS_FAILED(rv)) michael@0: request->Cancel(rv); michael@0: } michael@0: else michael@0: { michael@0: // if we don't read from the stream, OnStopRequest will never be called michael@0: char* buffer = new char[aLength]; michael@0: uint32_t amountRead, amountWrote = 0; michael@0: rv = aIStream->Read(buffer, aLength, &amountRead); michael@0: michael@0: // if we are caching this to disk ourselves, lets write the bytes out. michael@0: if (mFileCacheOutputStream) { michael@0: while (amountWrote < amountRead && NS_SUCCEEDED(rv)) { michael@0: rv = mFileCacheOutputStream->Write(buffer, amountRead, &amountWrote); michael@0: } michael@0: } michael@0: delete [] buffer; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest *request, michael@0: nsISupports* aContext, michael@0: nsresult aStatus) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsCOMPtr mp = do_QueryInterface(request); michael@0: if (!mp) { michael@0: bool found = mRequests.RemoveObject(request); michael@0: if (!found) { michael@0: NS_ERROR("Received OnStopRequest for untracked request."); michael@0: } michael@0: } michael@0: michael@0: PLUGIN_LOG(PLUGIN_LOG_NOISY, michael@0: ("nsPluginStreamListenerPeer::OnStopRequest this=%p aStatus=%d request=%p\n", michael@0: this, aStatus, request)); michael@0: michael@0: // for ByteRangeRequest we're just updating the mDataForwardToRequest hash and return. michael@0: nsCOMPtr brr = do_QueryInterface(request); michael@0: if (brr) { michael@0: int64_t absoluteOffset64 = 0; michael@0: brr->GetStartRange(&absoluteOffset64); michael@0: // XXX support 64-bit offsets michael@0: int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64); michael@0: michael@0: // remove the request from our data forwarding count hash. michael@0: mDataForwardToRequest->Remove(absoluteOffset); michael@0: michael@0: michael@0: PLUGIN_LOG(PLUGIN_LOG_NOISY, michael@0: (" ::OnStopRequest for ByteRangeRequest Started=%d\n", michael@0: absoluteOffset)); michael@0: } else { michael@0: // if this is not byte range request and michael@0: // if we are writting the stream to disk ourselves, michael@0: // close & tear it down here michael@0: mFileCacheOutputStream = nullptr; michael@0: } michael@0: michael@0: // if we still have pending stuff to do, lets not close the plugin socket. michael@0: if (--mPendingRequests > 0) michael@0: return NS_OK; michael@0: michael@0: // we keep our connections around... michael@0: nsCOMPtr container = do_QueryInterface(aContext); michael@0: if (container) { michael@0: uint32_t magicNumber = 0; // set it to something that is not the magic number. michael@0: container->GetData(&magicNumber); michael@0: if (magicNumber == MAGIC_REQUEST_CONTEXT) { michael@0: // this is one of our range requests michael@0: return NS_OK; michael@0: } michael@0: } michael@0: michael@0: if (!mPStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: if (!channel) michael@0: return NS_ERROR_FAILURE; michael@0: // Set the content type to ensure we don't pass null to the plugin michael@0: nsAutoCString aContentType; michael@0: rv = channel->GetContentType(aContentType); michael@0: if (NS_FAILED(rv) && !mRequestFailed) michael@0: return rv; michael@0: michael@0: if (!aContentType.IsEmpty()) michael@0: mContentType = aContentType; michael@0: michael@0: // set error status if stream failed so we notify the plugin michael@0: if (mRequestFailed) michael@0: aStatus = NS_ERROR_FAILURE; michael@0: michael@0: if (NS_FAILED(aStatus)) { michael@0: // on error status cleanup the stream michael@0: // and return w/o OnFileAvailable() michael@0: mPStreamListener->OnStopBinding(this, aStatus); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // call OnFileAvailable if plugin requests stream type StreamType_AsFile or StreamType_AsFileOnly michael@0: if (mStreamType >= NP_ASFILE) { michael@0: nsCOMPtr localFile; michael@0: if (mLocalCachedFileHolder) michael@0: localFile = mLocalCachedFileHolder->file(); michael@0: else { michael@0: // see if it is a file channel. michael@0: nsCOMPtr fileChannel = do_QueryInterface(request); michael@0: if (fileChannel) { michael@0: fileChannel->GetFile(getter_AddRefs(localFile)); michael@0: } michael@0: } michael@0: michael@0: if (localFile) { michael@0: OnFileAvailable(localFile); michael@0: } michael@0: } michael@0: michael@0: if (mStartBinding) { michael@0: // On start binding has been called michael@0: mPStreamListener->OnStopBinding(this, aStatus); michael@0: } else { michael@0: // OnStartBinding hasn't been called, so complete the action. michael@0: mPStreamListener->OnStartBinding(this); michael@0: mPStreamListener->OnStopBinding(this, aStatus); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(aStatus)) { michael@0: mStreamComplete = true; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request, michael@0: nsIURI* aURL) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: // If we don't yet have a stream listener, we need to get michael@0: // one from the plugin. michael@0: // NOTE: this should only happen when a stream was NOT created michael@0: // with GetURL or PostURL (i.e. it's the initial stream we michael@0: // send to the plugin as determined by the SRC or DATA attribute) michael@0: if (!mPStreamListener) { michael@0: if (!mPluginInstance) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr streamListener; michael@0: rv = mPluginInstance->NewStreamListener(nullptr, nullptr, michael@0: getter_AddRefs(streamListener)); michael@0: if (NS_FAILED(rv) || !streamListener) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: mPStreamListener = static_cast(streamListener.get()); michael@0: } michael@0: michael@0: mPStreamListener->SetStreamListenerPeer(this); michael@0: michael@0: bool useLocalCache = false; michael@0: michael@0: // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: nsCOMPtr httpChannel = do_QueryInterface(channel); michael@0: michael@0: /* michael@0: * Assumption michael@0: * By the time nsPluginStreamListenerPeer::OnDataAvailable() gets michael@0: * called, all the headers have been read. michael@0: */ michael@0: if (httpChannel) { michael@0: // Reassemble the HTTP response status line and provide it to our michael@0: // listener. Would be nice if we could get the raw status line, michael@0: // but nsIHttpChannel doesn't currently provide that. michael@0: // Status code: required; the status line isn't useful without it. michael@0: uint32_t statusNum; michael@0: if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&statusNum)) && michael@0: statusNum < 1000) { michael@0: // HTTP version: provide if available. Defaults to empty string. michael@0: nsCString ver; michael@0: nsCOMPtr httpChannelInternal = michael@0: do_QueryInterface(channel); michael@0: if (httpChannelInternal) { michael@0: uint32_t major, minor; michael@0: if (NS_SUCCEEDED(httpChannelInternal->GetResponseVersion(&major, michael@0: &minor))) { michael@0: ver = nsPrintfCString("/%lu.%lu", major, minor); michael@0: } michael@0: } michael@0: michael@0: // Status text: provide if available. Defaults to "OK". michael@0: nsCString statusText; michael@0: if (NS_FAILED(httpChannel->GetResponseStatusText(statusText))) { michael@0: statusText = "OK"; michael@0: } michael@0: michael@0: // Assemble everything and pass to listener. michael@0: nsPrintfCString status("HTTP%s %lu %s", ver.get(), statusNum, michael@0: statusText.get()); michael@0: static_cast(mPStreamListener)->StatusLine(status.get()); michael@0: } michael@0: michael@0: // Also provide all HTTP response headers to our listener. michael@0: httpChannel->VisitResponseHeaders(this); michael@0: michael@0: mSeekable = false; michael@0: // first we look for a content-encoding header. If we find one, we tell the michael@0: // plugin that stream is not seekable, because the plugin always sees michael@0: // uncompressed data, so it can't make meaningful range requests on a michael@0: // compressed entity. Also, we force the plugin to use michael@0: // nsPluginStreamType_AsFile stream type and we have to save decompressed michael@0: // file into local plugin cache, because necko cache contains original michael@0: // compressed file. michael@0: nsAutoCString contentEncoding; michael@0: if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"), michael@0: contentEncoding))) { michael@0: useLocalCache = true; michael@0: } else { michael@0: // set seekability (seekable if the stream has a known length and if the michael@0: // http server accepts byte ranges). michael@0: uint32_t length; michael@0: GetLength(&length); michael@0: if (length) { michael@0: nsAutoCString range; michael@0: if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("accept-ranges"), range)) && michael@0: range.Equals(NS_LITERAL_CSTRING("bytes"), nsCaseInsensitiveCStringComparator())) { michael@0: mSeekable = true; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // we require a content len michael@0: // get Last-Modified header for plugin info michael@0: nsAutoCString lastModified; michael@0: if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("last-modified"), lastModified)) && michael@0: !lastModified.IsEmpty()) { michael@0: PRTime time64; michael@0: PR_ParseTimeString(lastModified.get(), true, &time64); //convert string time to integer time michael@0: michael@0: // Convert PRTime to unix-style time_t, i.e. seconds since the epoch michael@0: double fpTime = double(time64); michael@0: mModified = (uint32_t)(fpTime * 1e-6 + 0.5); michael@0: } michael@0: } michael@0: michael@0: rv = mPStreamListener->OnStartBinding(this); michael@0: michael@0: mStartBinding = true; michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: mPStreamListener->GetStreamType(&mStreamType); michael@0: michael@0: if (!useLocalCache && mStreamType >= NP_ASFILE) { michael@0: // check it out if this is not a file channel. michael@0: nsCOMPtr fileChannel = do_QueryInterface(request); michael@0: if (!fileChannel) { michael@0: useLocalCache = true; michael@0: } michael@0: } michael@0: michael@0: if (useLocalCache) { michael@0: SetupPluginCacheFile(channel); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::OnFileAvailable(nsIFile* aFile) michael@0: { michael@0: nsresult rv; michael@0: if (!mPStreamListener) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoCString path; michael@0: rv = aFile->GetNativePath(path); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (path.IsEmpty()) { michael@0: NS_WARNING("empty path"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: rv = mPStreamListener->OnFileAvailable(this, path.get()); michael@0: return rv; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginStreamListenerPeer::VisitHeader(const nsACString &header, const nsACString &value) michael@0: { michael@0: return mPStreamListener->NewResponseHeader(PromiseFlatCString(header).get(), michael@0: PromiseFlatCString(value).get()); michael@0: } michael@0: michael@0: nsresult michael@0: nsPluginStreamListenerPeer::GetInterfaceGlobal(const nsIID& aIID, void** result) michael@0: { michael@0: if (!mPluginInstance) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsRefPtr owner = mPluginInstance->GetOwner(); michael@0: if (owner) { michael@0: nsCOMPtr doc; michael@0: nsresult rv = owner->GetDocument(getter_AddRefs(doc)); michael@0: if (NS_SUCCEEDED(rv) && doc) { michael@0: nsPIDOMWindow *window = doc->GetWindow(); michael@0: if (window) { michael@0: nsCOMPtr webNav = do_GetInterface(window); michael@0: nsCOMPtr ir = do_QueryInterface(webNav); michael@0: return ir->GetInterface(aIID, result); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginStreamListenerPeer::GetInterface(const nsIID& aIID, void** result) michael@0: { michael@0: // Provide nsIChannelEventSink ourselves, otherwise let our document's michael@0: // script global object owner provide the interface. michael@0: if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { michael@0: return QueryInterface(aIID, result); michael@0: } michael@0: michael@0: return GetInterfaceGlobal(aIID, result); michael@0: } michael@0: michael@0: /** michael@0: * Proxy class which forwards async redirect notifications back to the necko michael@0: * callback, keeping nsPluginStreamListenerPeer::mRequests in sync with michael@0: * which channel is active. michael@0: */ michael@0: class ChannelRedirectProxyCallback : public nsIAsyncVerifyRedirectCallback michael@0: { michael@0: public: michael@0: ChannelRedirectProxyCallback(nsPluginStreamListenerPeer* listener, michael@0: nsIAsyncVerifyRedirectCallback* parent, michael@0: nsIChannel* oldChannel, michael@0: nsIChannel* newChannel) michael@0: : mWeakListener(do_GetWeakReference(static_cast(listener))) michael@0: , mParent(parent) michael@0: , mOldChannel(oldChannel) michael@0: , mNewChannel(newChannel) michael@0: { michael@0: } michael@0: michael@0: ChannelRedirectProxyCallback() {} michael@0: virtual ~ChannelRedirectProxyCallback() {} michael@0: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHODIMP OnRedirectVerifyCallback(nsresult result) michael@0: { michael@0: if (NS_SUCCEEDED(result)) { michael@0: nsCOMPtr listener = do_QueryReferent(mWeakListener); michael@0: if (listener) michael@0: static_cast(listener.get())->ReplaceRequest(mOldChannel, mNewChannel); michael@0: } michael@0: return mParent->OnRedirectVerifyCallback(result); michael@0: } michael@0: michael@0: private: michael@0: nsWeakPtr mWeakListener; michael@0: nsCOMPtr mParent; michael@0: nsCOMPtr mOldChannel; michael@0: nsCOMPtr mNewChannel; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(ChannelRedirectProxyCallback, nsIAsyncVerifyRedirectCallback) michael@0: michael@0: michael@0: NS_IMETHODIMP michael@0: nsPluginStreamListenerPeer::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel, michael@0: uint32_t flags, nsIAsyncVerifyRedirectCallback* callback) michael@0: { michael@0: // Disallow redirects if we don't have a stream listener. michael@0: if (!mPStreamListener) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr proxyCallback = michael@0: new ChannelRedirectProxyCallback(this, callback, oldChannel, newChannel); michael@0: michael@0: // Give NPAPI a chance to control redirects. michael@0: bool notificationHandled = mPStreamListener->HandleRedirectNotification(oldChannel, newChannel, proxyCallback); michael@0: if (notificationHandled) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Don't allow cross-origin 307 POST redirects. michael@0: nsCOMPtr oldHttpChannel(do_QueryInterface(oldChannel)); michael@0: if (oldHttpChannel) { michael@0: uint32_t responseStatus; michael@0: nsresult rv = oldHttpChannel->GetResponseStatus(&responseStatus); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (responseStatus == 307) { michael@0: nsAutoCString method; michael@0: rv = oldHttpChannel->GetRequestMethod(method); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (method.EqualsLiteral("POST")) { michael@0: rv = nsContentUtils::CheckSameOrigin(oldChannel, newChannel); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Fall back to channel event sink for window. michael@0: nsCOMPtr channelEventSink; michael@0: nsresult rv = GetInterfaceGlobal(NS_GET_IID(nsIChannelEventSink), getter_AddRefs(channelEventSink)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: return channelEventSink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, proxyCallback); michael@0: }