diff options
Diffstat (limited to 'dom/plugins/base/nsNPAPIPluginStreamListener.cpp')
-rw-r--r-- | dom/plugins/base/nsNPAPIPluginStreamListener.cpp | 959 |
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; +} |