summaryrefslogtreecommitdiffstats
path: root/dom/plugins/base/nsNPAPIPluginStreamListener.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/plugins/base/nsNPAPIPluginStreamListener.cpp')
-rw-r--r--dom/plugins/base/nsNPAPIPluginStreamListener.cpp959
1 files changed, 959 insertions, 0 deletions
diff --git a/dom/plugins/base/nsNPAPIPluginStreamListener.cpp b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp
new file mode 100644
index 000000000..0f500a1ae
--- /dev/null
+++ b/dom/plugins/base/nsNPAPIPluginStreamListener.cpp
@@ -0,0 +1,959 @@
+/* -*- 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 "nsNPAPIPluginStreamListener.h"
+#include "plstr.h"
+#include "prmem.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsPluginHost.h"
+#include "nsNPAPIPlugin.h"
+#include "nsPluginLogging.h"
+#include "nsPluginStreamListenerPeer.h"
+
+#include <stdint.h>
+#include <algorithm>
+
+nsNPAPIStreamWrapper::nsNPAPIStreamWrapper(nsIOutputStream *outputStream,
+ nsNPAPIPluginStreamListener *streamListener)
+{
+ mOutputStream = outputStream;
+ mStreamListener = streamListener;
+
+ memset(&mNPStream, 0, sizeof(mNPStream));
+ mNPStream.ndata = static_cast<void*>(this);
+}
+
+nsNPAPIStreamWrapper::~nsNPAPIStreamWrapper()
+{
+ if (mOutputStream) {
+ mOutputStream->Close();
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsPluginStreamToFile, nsIOutputStream)
+
+nsPluginStreamToFile::nsPluginStreamToFile(const char* target,
+ nsIPluginInstanceOwner* owner)
+: mTarget(PL_strdup(target)),
+mOwner(owner)
+{
+ nsresult rv;
+ nsCOMPtr<nsIFile> pluginTmp;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pluginTmp));
+ if (NS_FAILED(rv)) return;
+
+ mTempFile = do_QueryInterface(pluginTmp, &rv);
+ if (NS_FAILED(rv)) return;
+
+ // need to create a file with a unique name - use target as the basis
+ rv = mTempFile->AppendNative(nsDependentCString(target));
+ if (NS_FAILED(rv)) return;
+
+ // Yes, make it unique.
+ rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
+ if (NS_FAILED(rv)) return;
+
+ // create the file
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), mTempFile, -1, 00600);
+ if (NS_FAILED(rv))
+ return;
+
+ // construct the URL we'll use later in calls to GetURL()
+ NS_GetURLSpecFromFile(mTempFile, mFileURL);
+
+#ifdef DEBUG
+ printf("File URL = %s\n", mFileURL.get());
+#endif
+}
+
+nsPluginStreamToFile::~nsPluginStreamToFile()
+{
+ // should we be deleting mTempFile here?
+ if (nullptr != mTarget)
+ PL_strfree(mTarget);
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::Flush()
+{
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::Write(const char* aBuf, uint32_t aCount,
+ uint32_t *aWriteCount)
+{
+ mOutputStream->Write(aBuf, aCount, aWriteCount);
+ mOutputStream->Flush();
+ mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::WriteFrom(nsIInputStream *inStr, uint32_t count,
+ uint32_t *_retval)
+{
+ NS_NOTREACHED("WriteFrom");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::WriteSegments(nsReadSegmentFun reader, void * closure,
+ uint32_t count, uint32_t *_retval)
+{
+ NS_NOTREACHED("WriteSegments");
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::IsNonBlocking(bool *aNonBlocking)
+{
+ *aNonBlocking = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPluginStreamToFile::Close(void)
+{
+ mOutputStream->Close();
+ mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false);
+ return NS_OK;
+}
+
+// nsNPAPIPluginStreamListener Methods
+
+NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener,
+ nsITimerCallback, nsIHTTPHeaderListener)
+
+nsNPAPIPluginStreamListener::nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst,
+ void* notifyData,
+ const char* aURL)
+ : mStreamBuffer(nullptr)
+ , mNotifyURL(aURL ? PL_strdup(aURL) : nullptr)
+ , mInst(inst)
+ , mStreamBufferSize(0)
+ , mStreamBufferByteCount(0)
+ , mStreamType(NP_NORMAL)
+ , mStreamState(eStreamStopped)
+ , mStreamCleanedUp(false)
+ , mCallNotify(notifyData ? true : false)
+ , mIsSuspended(false)
+ , mIsPluginInitJSStream(mInst->mInPluginInitCall &&
+ aURL && strncmp(aURL, "javascript:",
+ sizeof("javascript:") - 1) == 0)
+ , mRedirectDenied(false)
+ , mResponseHeaderBuf(nullptr)
+ , mStreamStopMode(eNormalStop)
+ , mPendingStopBindingStatus(NS_OK)
+{
+ mNPStreamWrapper = new nsNPAPIStreamWrapper(nullptr, this);
+ mNPStreamWrapper->mNPStream.notifyData = notifyData;
+}
+
+nsNPAPIPluginStreamListener::~nsNPAPIPluginStreamListener()
+{
+ // remove this from the plugin instance's stream list
+ nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners();
+ streamListeners->RemoveElement(this);
+
+ // For those cases when NewStream is never called, we still may need
+ // to fire a notification callback. Return network error as fallback
+ // reason because for other cases, notify should have already been
+ // called for other reasons elsewhere.
+ CallURLNotify(NPRES_NETWORK_ERR);
+
+ // lets get rid of the buffer
+ if (mStreamBuffer) {
+ PR_Free(mStreamBuffer);
+ mStreamBuffer=nullptr;
+ }
+
+ if (mNotifyURL)
+ PL_strfree(mNotifyURL);
+
+ if (mResponseHeaderBuf)
+ PL_strfree(mResponseHeaderBuf);
+
+ if (mNPStreamWrapper) {
+ delete mNPStreamWrapper;
+ }
+}
+
+nsresult
+nsNPAPIPluginStreamListener::CleanUpStream(NPReason reason)
+{
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // Various bits of code in the rest of this method may result in the
+ // deletion of this object. Use a KungFuDeathGrip to keep ourselves
+ // alive during cleanup.
+ RefPtr<nsNPAPIPluginStreamListener> kungFuDeathGrip(this);
+
+ if (mStreamCleanedUp)
+ return NS_OK;
+
+ mStreamCleanedUp = true;
+
+ StopDataPump();
+
+ // Release any outstanding redirect callback.
+ if (mHTTPRedirectCallback) {
+ mHTTPRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
+ mHTTPRedirectCallback = nullptr;
+ }
+
+ // Seekable streams have an extra addref when they are created which must
+ // be matched here.
+ if (NP_SEEK == mStreamType && mStreamState == eStreamTypeSet)
+ NS_RELEASE_THIS();
+
+ if (mStreamListenerPeer) {
+ mStreamListenerPeer->CancelRequests(NS_BINDING_ABORTED);
+ mStreamListenerPeer = nullptr;
+ }
+
+ if (!mInst || !mInst->CanFireNotifications())
+ return rv;
+
+ PluginDestructionGuard guard(mInst);
+
+ nsNPAPIPlugin* plugin = mInst->GetPlugin();
+ if (!plugin || !plugin->GetLibrary())
+ return rv;
+
+ NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
+
+ NPP npp;
+ mInst->GetNPP(&npp);
+
+ if (mStreamState >= eNewStreamCalled && pluginFunctions->destroystream) {
+ NPPAutoPusher nppPusher(npp);
+
+ NPError error;
+ NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->destroystream)(npp, &mNPStreamWrapper->mNPStream, reason), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+
+ NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("NPP DestroyStream called: this=%p, npp=%p, reason=%d, return=%d, url=%s\n",
+ this, npp, reason, error, mNPStreamWrapper->mNPStream.url));
+
+ if (error == NPERR_NO_ERROR)
+ rv = NS_OK;
+ }
+
+ mStreamState = eStreamStopped;
+
+ // fire notification back to plugin, just like before
+ CallURLNotify(reason);
+
+ return rv;
+}
+
+void
+nsNPAPIPluginStreamListener::CallURLNotify(NPReason reason)
+{
+ if (!mCallNotify || !mInst || !mInst->CanFireNotifications())
+ return;
+
+ PluginDestructionGuard guard(mInst);
+
+ mCallNotify = false; // only do this ONCE and prevent recursion
+
+ nsNPAPIPlugin* plugin = mInst->GetPlugin();
+ if (!plugin || !plugin->GetLibrary())
+ return;
+
+ NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
+
+ if (pluginFunctions->urlnotify) {
+ NPP npp;
+ mInst->GetNPP(&npp);
+
+ NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlnotify)(npp, mNotifyURL, reason, mNPStreamWrapper->mNPStream.notifyData), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+
+ NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("NPP URLNotify called: this=%p, npp=%p, notify=%p, reason=%d, url=%s\n",
+ this, npp, mNPStreamWrapper->mNPStream.notifyData, reason, mNotifyURL));
+ }
+}
+
+nsresult
+nsNPAPIPluginStreamListener::OnStartBinding(nsPluginStreamListenerPeer* streamPeer)
+{
+ PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
+ if (!mInst || !mInst->CanFireNotifications() || mStreamCleanedUp)
+ return NS_ERROR_FAILURE;
+
+ PluginDestructionGuard guard(mInst);
+
+ nsNPAPIPlugin* plugin = mInst->GetPlugin();
+ if (!plugin || !plugin->GetLibrary())
+ return NS_ERROR_FAILURE;
+
+ NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
+
+ if (!pluginFunctions->newstream)
+ return NS_ERROR_FAILURE;
+
+ NPP npp;
+ mInst->GetNPP(&npp);
+
+ bool seekable;
+ char* contentType;
+ uint16_t streamType = NP_NORMAL;
+ NPError error;
+
+ streamPeer->GetURL(&mNPStreamWrapper->mNPStream.url);
+ streamPeer->GetLength((uint32_t*)&(mNPStreamWrapper->mNPStream.end));
+ streamPeer->GetLastModified((uint32_t*)&(mNPStreamWrapper->mNPStream.lastmodified));
+ streamPeer->IsSeekable(&seekable);
+ streamPeer->GetContentType(&contentType);
+
+ if (!mResponseHeaders.IsEmpty()) {
+ mResponseHeaderBuf = PL_strdup(mResponseHeaders.get());
+ mNPStreamWrapper->mNPStream.headers = mResponseHeaderBuf;
+ }
+
+ mStreamListenerPeer = streamPeer;
+
+ NPPAutoPusher nppPusher(npp);
+
+ NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->newstream)(npp, (char*)contentType, &mNPStreamWrapper->mNPStream, seekable, &streamType), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+
+ NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("NPP NewStream called: this=%p, npp=%p, mime=%s, seek=%d, type=%d, return=%d, url=%s\n",
+ this, npp, (char *)contentType, seekable, streamType, error, mNPStreamWrapper->mNPStream.url));
+
+ if (error != NPERR_NO_ERROR)
+ return NS_ERROR_FAILURE;
+
+ mStreamState = eNewStreamCalled;
+
+ if (!SetStreamType(streamType, false)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool
+nsNPAPIPluginStreamListener::SetStreamType(uint16_t aType, bool aNeedsResume)
+{
+ switch(aType)
+ {
+ case NP_NORMAL:
+ mStreamType = NP_NORMAL;
+ break;
+ case NP_ASFILEONLY:
+ mStreamType = NP_ASFILEONLY;
+ break;
+ case NP_ASFILE:
+ mStreamType = NP_ASFILE;
+ break;
+ case NP_SEEK:
+ mStreamType = NP_SEEK;
+ // Seekable streams should continue to exist even after OnStopRequest
+ // is fired, so we AddRef ourself an extra time and Release when the
+ // plugin calls NPN_DestroyStream (CleanUpStream). If the plugin never
+ // calls NPN_DestroyStream the stream will be destroyed before the plugin
+ // instance is destroyed.
+ NS_ADDREF_THIS();
+ break;
+ case nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN:
+ MOZ_ASSERT(!aNeedsResume);
+ mStreamType = nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN;
+ SuspendRequest();
+ mStreamStopMode = eDoDeferredStop;
+ // In this case we do not want to execute anything else in this function.
+ return true;
+ default:
+ return false;
+ }
+ mStreamState = eStreamTypeSet;
+ if (aNeedsResume) {
+ if (mStreamListenerPeer) {
+ mStreamListenerPeer->OnStreamTypeSet(mStreamType);
+ }
+ ResumeRequest();
+ }
+ return true;
+}
+
+void
+nsNPAPIPluginStreamListener::SuspendRequest()
+{
+ NS_ASSERTION(!mIsSuspended,
+ "Suspending a request that's already suspended!");
+
+ nsresult rv = StartDataPump();
+ if (NS_FAILED(rv))
+ return;
+
+ mIsSuspended = true;
+
+ if (mStreamListenerPeer) {
+ mStreamListenerPeer->SuspendRequests();
+ }
+}
+
+void
+nsNPAPIPluginStreamListener::ResumeRequest()
+{
+ if (mStreamListenerPeer) {
+ mStreamListenerPeer->ResumeRequests();
+ }
+ mIsSuspended = false;
+}
+
+nsresult
+nsNPAPIPluginStreamListener::StartDataPump()
+{
+ nsresult rv;
+ mDataPumpTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Start pumping data to the plugin every 100ms until it obeys and
+ // eats the data.
+ return mDataPumpTimer->InitWithCallback(this, 100,
+ nsITimer::TYPE_REPEATING_SLACK);
+}
+
+void
+nsNPAPIPluginStreamListener::StopDataPump()
+{
+ if (mDataPumpTimer) {
+ mDataPumpTimer->Cancel();
+ mDataPumpTimer = nullptr;
+ }
+}
+
+// Return true if a javascript: load that was started while the plugin
+// was being initialized is still in progress.
+bool
+nsNPAPIPluginStreamListener::PluginInitJSLoadInProgress()
+{
+ if (!mInst)
+ return false;
+
+ nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners();
+ for (unsigned int i = 0; i < streamListeners->Length(); i++) {
+ if (streamListeners->ElementAt(i)->mIsPluginInitJSStream) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+// This method is called when there's more data available off the
+// network, but it's also called from our data pump when we're feeding
+// the plugin data that we already got off the network, but the plugin
+// was unable to consume it at the point it arrived. In the case when
+// the plugin pump calls this method, the input argument will be null,
+// and the length will be the number of bytes available in our
+// internal buffer.
+nsresult
+nsNPAPIPluginStreamListener::OnDataAvailable(nsPluginStreamListenerPeer* streamPeer,
+ nsIInputStream* input,
+ uint32_t length)
+{
+ if (!length || !mInst || !mInst->CanFireNotifications())
+ return NS_ERROR_FAILURE;
+
+ PluginDestructionGuard guard(mInst);
+
+ // Just in case the caller switches plugin info on us.
+ mStreamListenerPeer = streamPeer;
+
+ nsNPAPIPlugin* plugin = mInst->GetPlugin();
+ if (!plugin || !plugin->GetLibrary())
+ return NS_ERROR_FAILURE;
+
+ NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
+
+ // check out if plugin implements NPP_Write call
+ if (!pluginFunctions->write)
+ return NS_ERROR_FAILURE; // it'll cancel necko transaction
+
+ if (!mStreamBuffer) {
+ // To optimize the mem usage & performance we have to allocate
+ // mStreamBuffer here in first ODA when length of data available
+ // in input stream is known. mStreamBuffer will be freed in DTOR.
+ // we also have to remember the size of that buff to make safe
+ // consecutive Read() calls form input stream into our buff.
+
+ uint32_t contentLength;
+ streamPeer->GetLength(&contentLength);
+
+ mStreamBufferSize = std::max(length, contentLength);
+
+ // Limit the size of the initial buffer to MAX_PLUGIN_NECKO_BUFFER
+ // (16k). This buffer will grow if needed, as in the case where
+ // we're getting data faster than the plugin can process it.
+ mStreamBufferSize = std::min(mStreamBufferSize,
+ uint32_t(MAX_PLUGIN_NECKO_BUFFER));
+
+ mStreamBuffer = (char*) PR_Malloc(mStreamBufferSize);
+ if (!mStreamBuffer)
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ // prepare NPP_ calls params
+ NPP npp;
+ mInst->GetNPP(&npp);
+
+ int32_t streamPosition;
+ streamPeer->GetStreamOffset(&streamPosition);
+ int32_t streamOffset = streamPosition;
+
+ if (input) {
+ streamOffset += length;
+
+ // Set new stream offset for the next ODA call regardless of how
+ // following NPP_Write call will behave we pretend to consume all
+ // data from the input stream. It's possible that current steam
+ // position will be overwritten from NPP_RangeRequest call made
+ // from NPP_Write, so we cannot call SetStreamOffset after
+ // NPP_Write.
+ //
+ // Note: there is a special case when data flow should be
+ // temporarily stopped if NPP_WriteReady returns 0 (bug #89270)
+ streamPeer->SetStreamOffset(streamOffset);
+
+ // set new end in case the content is compressed
+ // initial end is less than end of decompressed stream
+ // and some plugins (e.g. acrobat) can fail.
+ if ((int32_t)mNPStreamWrapper->mNPStream.end < streamOffset)
+ mNPStreamWrapper->mNPStream.end = streamOffset;
+ }
+
+ nsresult rv = NS_OK;
+ while (NS_SUCCEEDED(rv) && length > 0) {
+ if (input && length) {
+ if (mStreamBufferSize < mStreamBufferByteCount + length) {
+ // We're in the ::OnDataAvailable() call that we might get
+ // after suspending a request, or we suspended the request
+ // from within this ::OnDataAvailable() call while there's
+ // still data in the input, or we have resumed a previously
+ // suspended request and our buffer is already full, and we
+ // don't have enough space to store what we got off the network.
+ // Reallocate our internal buffer.
+ mStreamBufferSize = mStreamBufferByteCount + length;
+ char *buf = (char*)PR_Realloc(mStreamBuffer, mStreamBufferSize);
+ if (!buf)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ mStreamBuffer = buf;
+ }
+
+ uint32_t bytesToRead =
+ std::min(length, mStreamBufferSize - mStreamBufferByteCount);
+ MOZ_ASSERT(bytesToRead > 0);
+
+ uint32_t amountRead = 0;
+ rv = input->Read(mStreamBuffer + mStreamBufferByteCount, bytesToRead,
+ &amountRead);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (amountRead == 0) {
+ NS_NOTREACHED("input->Read() returns no data, it's almost impossible "
+ "to get here");
+
+ break;
+ }
+
+ mStreamBufferByteCount += amountRead;
+ length -= amountRead;
+ } else {
+ // No input, nothing to read. Set length to 0 so that we don't
+ // keep iterating through this outer loop any more.
+
+ length = 0;
+ }
+
+ // Temporary pointer to the beginning of the data we're writing as
+ // we loop and feed the plugin data.
+ char *ptrStreamBuffer = mStreamBuffer;
+
+ // it is possible plugin's NPP_Write() returns 0 byte consumed. We
+ // use zeroBytesWriteCount to count situation like this and break
+ // the loop
+ int32_t zeroBytesWriteCount = 0;
+
+ // mStreamBufferByteCount tells us how many bytes there are in the
+ // buffer. WriteReady returns to us how many bytes the plugin is
+ // ready to handle.
+ while (mStreamBufferByteCount > 0) {
+ int32_t numtowrite;
+ if (pluginFunctions->writeready) {
+ NPPAutoPusher nppPusher(npp);
+
+ NS_TRY_SAFE_CALL_RETURN(numtowrite, (*pluginFunctions->writeready)(npp, &mNPStreamWrapper->mNPStream), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+ NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
+ ("NPP WriteReady called: this=%p, npp=%p, "
+ "return(towrite)=%d, url=%s\n",
+ this, npp, numtowrite, mNPStreamWrapper->mNPStream.url));
+
+ if (mStreamState == eStreamStopped) {
+ // The plugin called NPN_DestroyStream() from within
+ // NPP_WriteReady(), kill the stream.
+
+ return NS_BINDING_ABORTED;
+ }
+
+ // if WriteReady returned 0, the plugin is not ready to handle
+ // the data, suspend the stream (if it isn't already
+ // suspended).
+ //
+ // Also suspend the stream if the stream we're loading is not
+ // a javascript: URL load that was initiated during plugin
+ // initialization and there currently is such a stream
+ // loading. This is done to work around a Windows Media Player
+ // plugin bug where it can't deal with being fed data for
+ // other streams while it's waiting for data from the
+ // javascript: URL loads it requests during
+ // initialization. See bug 386493 for more details.
+
+ if (numtowrite <= 0 ||
+ (!mIsPluginInitJSStream && PluginInitJSLoadInProgress())) {
+ if (!mIsSuspended) {
+ SuspendRequest();
+ }
+
+ // Break out of the inner loop, but keep going through the
+ // outer loop in case there's more data to read from the
+ // input stream.
+
+ break;
+ }
+
+ numtowrite = std::min(numtowrite, mStreamBufferByteCount);
+ } else {
+ // if WriteReady is not supported by the plugin, just write
+ // the whole buffer
+ numtowrite = mStreamBufferByteCount;
+ }
+
+ NPPAutoPusher nppPusher(npp);
+
+ int32_t writeCount = 0; // bytes consumed by plugin instance
+ NS_TRY_SAFE_CALL_RETURN(writeCount, (*pluginFunctions->write)(npp, &mNPStreamWrapper->mNPStream, streamPosition, numtowrite, ptrStreamBuffer), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+
+ NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
+ ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, "
+ "buf=%s, return(written)=%d, url=%s\n",
+ this, npp, streamPosition, numtowrite,
+ ptrStreamBuffer, writeCount, mNPStreamWrapper->mNPStream.url));
+
+ if (mStreamState == eStreamStopped) {
+ // The plugin called NPN_DestroyStream() from within
+ // NPP_Write(), kill the stream.
+ return NS_BINDING_ABORTED;
+ }
+
+ if (writeCount > 0) {
+ NS_ASSERTION(writeCount <= mStreamBufferByteCount,
+ "Plugin read past the end of the available data!");
+
+ writeCount = std::min(writeCount, mStreamBufferByteCount);
+ mStreamBufferByteCount -= writeCount;
+
+ streamPosition += writeCount;
+
+ zeroBytesWriteCount = 0;
+
+ if (mStreamBufferByteCount > 0) {
+ // This alignment code is most likely bogus, but we'll leave
+ // it in for now in case it matters for some plugins on some
+ // architectures. Who knows...
+ if (writeCount % sizeof(intptr_t)) {
+ // memmove will take care about alignment
+ memmove(mStreamBuffer, ptrStreamBuffer + writeCount,
+ mStreamBufferByteCount);
+ ptrStreamBuffer = mStreamBuffer;
+ } else {
+ // if aligned we can use ptrStreamBuffer += to eliminate
+ // memmove()
+ ptrStreamBuffer += writeCount;
+ }
+ }
+ } else if (writeCount == 0) {
+ // if NPP_Write() returns writeCount == 0 lets say 3 times in
+ // a row, suspend the request and continue feeding the plugin
+ // the data we got so far. Once that data is consumed, we'll
+ // resume the request.
+ if (mIsSuspended || ++zeroBytesWriteCount == 3) {
+ if (!mIsSuspended) {
+ SuspendRequest();
+ }
+
+ // Break out of the for loop, but keep going through the
+ // while loop in case there's more data to read from the
+ // input stream.
+
+ break;
+ }
+ } else {
+ // Something's really wrong, kill the stream.
+ rv = NS_ERROR_FAILURE;
+
+ break;
+ }
+ } // end of inner while loop
+
+ if (mStreamBufferByteCount && mStreamBuffer != ptrStreamBuffer) {
+ memmove(mStreamBuffer, ptrStreamBuffer, mStreamBufferByteCount);
+ }
+ }
+
+ if (streamPosition != streamOffset) {
+ // The plugin didn't consume all available data, or consumed some
+ // of our cached data while we're pumping cached data. Adjust the
+ // plugin info's stream offset to match reality, except if the
+ // plugin info's stream offset was set by a re-entering
+ // NPN_RequestRead() call.
+
+ int32_t postWriteStreamPosition;
+ streamPeer->GetStreamOffset(&postWriteStreamPosition);
+
+ if (postWriteStreamPosition == streamOffset) {
+ streamPeer->SetStreamOffset(streamPosition);
+ }
+ }
+
+ return rv;
+}
+
+nsresult
+nsNPAPIPluginStreamListener::OnFileAvailable(nsPluginStreamListenerPeer* streamPeer,
+ const char* fileName)
+{
+ if (!mInst || !mInst->CanFireNotifications())
+ return NS_ERROR_FAILURE;
+
+ PluginDestructionGuard guard(mInst);
+
+ nsNPAPIPlugin* plugin = mInst->GetPlugin();
+ if (!plugin || !plugin->GetLibrary())
+ return NS_ERROR_FAILURE;
+
+ NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
+
+ if (!pluginFunctions->asfile)
+ return NS_ERROR_FAILURE;
+
+ NPP npp;
+ mInst->GetNPP(&npp);
+
+ NS_TRY_SAFE_CALL_VOID((*pluginFunctions->asfile)(npp, &mNPStreamWrapper->mNPStream, fileName), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+
+ NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
+ ("NPP StreamAsFile called: this=%p, npp=%p, url=%s, file=%s\n",
+ this, npp, mNPStreamWrapper->mNPStream.url, fileName));
+
+ return NS_OK;
+}
+
+nsresult
+nsNPAPIPluginStreamListener::OnStopBinding(nsPluginStreamListenerPeer* streamPeer,
+ nsresult status)
+{
+ if (NS_FAILED(status)) {
+ // The stream was destroyed, or died for some reason. Make sure we
+ // cancel the underlying request.
+ if (mStreamListenerPeer) {
+ mStreamListenerPeer->CancelRequests(status);
+ }
+ }
+
+ if (!mInst || !mInst->CanFireNotifications()) {
+ StopDataPump();
+ return NS_ERROR_FAILURE;
+ }
+
+ // We need to detect that the stop is due to async stream init completion.
+ if (mStreamStopMode == eDoDeferredStop) {
+ // We shouldn't be delivering this until async init is done
+ mStreamStopMode = eStopPending;
+ mPendingStopBindingStatus = status;
+ if (!mDataPumpTimer) {
+ StartDataPump();
+ }
+ return NS_OK;
+ }
+
+ StopDataPump();
+
+ NPReason reason = NS_FAILED(status) ? NPRES_NETWORK_ERR : NPRES_DONE;
+ if (mRedirectDenied || status == NS_BINDING_ABORTED) {
+ reason = NPRES_USER_BREAK;
+ }
+
+ // The following code can result in the deletion of 'this'. Don't
+ // assume we are alive after this!
+ //
+ // Delay cleanup if the stream is of type NP_SEEK and status isn't
+ // NS_BINDING_ABORTED (meaning the plugin hasn't called NPN_DestroyStream).
+ // This is because even though we're done delivering data the plugin may
+ // want to seek. Eventually either the plugin will call NPN_DestroyStream
+ // or we'll perform cleanup when the instance goes away. See bug 91140.
+ if (mStreamType != NP_SEEK ||
+ (NP_SEEK == mStreamType && NS_BINDING_ABORTED == status)) {
+ return CleanUpStream(reason);
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsNPAPIPluginStreamListener::GetStreamType(int32_t *result)
+{
+ *result = mStreamType;
+ return NS_OK;
+}
+
+bool
+nsNPAPIPluginStreamListener::MaybeRunStopBinding()
+{
+ if (mIsSuspended || mStreamStopMode != eStopPending) {
+ return false;
+ }
+ OnStopBinding(mStreamListenerPeer, mPendingStopBindingStatus);
+ mStreamStopMode = eNormalStop;
+ return true;
+}
+
+NS_IMETHODIMP
+nsNPAPIPluginStreamListener::Notify(nsITimer *aTimer)
+{
+ NS_ASSERTION(aTimer == mDataPumpTimer, "Uh, wrong timer?");
+
+ int32_t oldStreamBufferByteCount = mStreamBufferByteCount;
+
+ nsresult rv = OnDataAvailable(mStreamListenerPeer, nullptr, mStreamBufferByteCount);
+
+ if (NS_FAILED(rv)) {
+ // We ran into an error, no need to keep firing this timer then.
+ StopDataPump();
+ MaybeRunStopBinding();
+ return NS_OK;
+ }
+
+ if (mStreamBufferByteCount != oldStreamBufferByteCount &&
+ ((mStreamState == eStreamTypeSet && mStreamBufferByteCount < 1024) ||
+ mStreamBufferByteCount == 0)) {
+ // The plugin read some data and we've got less than 1024 bytes in
+ // our buffer (or its empty and the stream is already
+ // done). Resume the request so that we get more data off the
+ // network.
+ ResumeRequest();
+ // Necko will pump data now that we've resumed the request.
+ StopDataPump();
+ }
+
+ MaybeRunStopBinding();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNPAPIPluginStreamListener::StatusLine(const char* line)
+{
+ mResponseHeaders.Append(line);
+ mResponseHeaders.Append('\n');
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNPAPIPluginStreamListener::NewResponseHeader(const char* headerName,
+ const char* headerValue)
+{
+ mResponseHeaders.Append(headerName);
+ mResponseHeaders.AppendLiteral(": ");
+ mResponseHeaders.Append(headerValue);
+ mResponseHeaders.Append('\n');
+ return NS_OK;
+}
+
+bool
+nsNPAPIPluginStreamListener::HandleRedirectNotification(nsIChannel *oldChannel, nsIChannel *newChannel,
+ nsIAsyncVerifyRedirectCallback* callback)
+{
+ nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(oldChannel);
+ nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
+ if (!oldHttpChannel || !newHttpChannel) {
+ return false;
+ }
+
+ if (!mInst || !mInst->CanFireNotifications()) {
+ return false;
+ }
+
+ nsNPAPIPlugin* plugin = mInst->GetPlugin();
+ if (!plugin || !plugin->GetLibrary()) {
+ return false;
+ }
+
+ NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
+ if (!pluginFunctions->urlredirectnotify) {
+ return false;
+ }
+
+ // A non-null closure is required for redirect handling support.
+ if (mNPStreamWrapper->mNPStream.notifyData) {
+ uint32_t status;
+ if (NS_SUCCEEDED(oldHttpChannel->GetResponseStatus(&status))) {
+ nsCOMPtr<nsIURI> uri;
+ if (NS_SUCCEEDED(newHttpChannel->GetURI(getter_AddRefs(uri))) && uri) {
+ nsAutoCString spec;
+ if (NS_SUCCEEDED(uri->GetAsciiSpec(spec))) {
+ // At this point the plugin will be responsible for making the callback
+ // so save the callback object.
+ mHTTPRedirectCallback = callback;
+
+ NPP npp;
+ mInst->GetNPP(&npp);
+#if defined(XP_WIN)
+ NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData), mInst,
+ NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
+#else
+ MAIN_THREAD_JNI_REF_GUARD;
+ (*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData);
+#endif
+ return true;
+ }
+ }
+ }
+ }
+
+ callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
+ return true;
+}
+
+void
+nsNPAPIPluginStreamListener::URLRedirectResponse(NPBool allow)
+{
+ if (mHTTPRedirectCallback) {
+ mHTTPRedirectCallback->OnRedirectVerifyCallback(allow ? NS_OK : NS_ERROR_FAILURE);
+ mRedirectDenied = allow ? false : true;
+ mHTTPRedirectCallback = nullptr;
+ }
+}
+
+void*
+nsNPAPIPluginStreamListener::GetNotifyData()
+{
+ if (mNPStreamWrapper) {
+ return mNPStreamWrapper->mNPStream.notifyData;
+ }
+ return nullptr;
+}