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