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