Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsNPAPIPluginStreamListener.h"
7 #include "plstr.h"
8 #include "prmem.h"
9 #include "nsDirectoryServiceDefs.h"
10 #include "nsDirectoryServiceUtils.h"
11 #include "nsIFile.h"
12 #include "nsNetUtil.h"
13 #include "nsPluginHost.h"
14 #include "nsNPAPIPlugin.h"
15 #include "nsPluginLogging.h"
16 #include "nsPluginStreamListenerPeer.h"
18 #include <stdint.h>
19 #include <algorithm>
21 nsNPAPIStreamWrapper::nsNPAPIStreamWrapper(nsIOutputStream *outputStream,
22 nsNPAPIPluginStreamListener *streamListener)
23 {
24 mOutputStream = outputStream;
25 mStreamListener = streamListener;
27 memset(&mNPStream, 0, sizeof(mNPStream));
28 mNPStream.ndata = static_cast<void*>(this);
29 }
31 nsNPAPIStreamWrapper::~nsNPAPIStreamWrapper()
32 {
33 if (mOutputStream) {
34 mOutputStream->Close();
35 }
36 }
38 NS_IMPL_ISUPPORTS(nsPluginStreamToFile, nsIOutputStream)
40 nsPluginStreamToFile::nsPluginStreamToFile(const char* target,
41 nsIPluginInstanceOwner* owner)
42 : mTarget(PL_strdup(target)),
43 mOwner(owner)
44 {
45 nsresult rv;
46 nsCOMPtr<nsIFile> pluginTmp;
47 rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(pluginTmp));
48 if (NS_FAILED(rv)) return;
50 mTempFile = do_QueryInterface(pluginTmp, &rv);
51 if (NS_FAILED(rv)) return;
53 // need to create a file with a unique name - use target as the basis
54 rv = mTempFile->AppendNative(nsDependentCString(target));
55 if (NS_FAILED(rv)) return;
57 // Yes, make it unique.
58 rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
59 if (NS_FAILED(rv)) return;
61 // create the file
62 rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), mTempFile, -1, 00600);
63 if (NS_FAILED(rv))
64 return;
66 // construct the URL we'll use later in calls to GetURL()
67 NS_GetURLSpecFromFile(mTempFile, mFileURL);
69 #ifdef DEBUG
70 printf("File URL = %s\n", mFileURL.get());
71 #endif
72 }
74 nsPluginStreamToFile::~nsPluginStreamToFile()
75 {
76 // should we be deleting mTempFile here?
77 if (nullptr != mTarget)
78 PL_strfree(mTarget);
79 }
81 NS_IMETHODIMP
82 nsPluginStreamToFile::Flush()
83 {
84 return NS_OK;
85 }
87 NS_IMETHODIMP
88 nsPluginStreamToFile::Write(const char* aBuf, uint32_t aCount,
89 uint32_t *aWriteCount)
90 {
91 mOutputStream->Write(aBuf, aCount, aWriteCount);
92 mOutputStream->Flush();
93 mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0);
95 return NS_OK;
96 }
98 NS_IMETHODIMP
99 nsPluginStreamToFile::WriteFrom(nsIInputStream *inStr, uint32_t count,
100 uint32_t *_retval)
101 {
102 NS_NOTREACHED("WriteFrom");
103 return NS_ERROR_NOT_IMPLEMENTED;
104 }
106 NS_IMETHODIMP
107 nsPluginStreamToFile::WriteSegments(nsReadSegmentFun reader, void * closure,
108 uint32_t count, uint32_t *_retval)
109 {
110 NS_NOTREACHED("WriteSegments");
111 return NS_ERROR_NOT_IMPLEMENTED;
112 }
114 NS_IMETHODIMP
115 nsPluginStreamToFile::IsNonBlocking(bool *aNonBlocking)
116 {
117 *aNonBlocking = false;
118 return NS_OK;
119 }
121 NS_IMETHODIMP
122 nsPluginStreamToFile::Close(void)
123 {
124 mOutputStream->Close();
125 mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0);
126 return NS_OK;
127 }
129 // nsNPAPIPluginStreamListener Methods
131 NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener,
132 nsITimerCallback, nsIHTTPHeaderListener)
134 nsNPAPIPluginStreamListener::nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst,
135 void* notifyData,
136 const char* aURL)
137 : mStreamBuffer(nullptr),
138 mNotifyURL(aURL ? PL_strdup(aURL) : nullptr),
139 mInst(inst),
140 mStreamBufferSize(0),
141 mStreamBufferByteCount(0),
142 mStreamType(NP_NORMAL),
143 mStreamStarted(false),
144 mStreamCleanedUp(false),
145 mCallNotify(notifyData ? true : false),
146 mIsSuspended(false),
147 mIsPluginInitJSStream(mInst->mInPluginInitCall &&
148 aURL && strncmp(aURL, "javascript:",
149 sizeof("javascript:") - 1) == 0),
150 mRedirectDenied(false),
151 mResponseHeaderBuf(nullptr)
152 {
153 mNPStreamWrapper = new nsNPAPIStreamWrapper(nullptr, this);
154 mNPStreamWrapper->mNPStream.notifyData = notifyData;
155 }
157 nsNPAPIPluginStreamListener::~nsNPAPIPluginStreamListener()
158 {
159 // remove this from the plugin instance's stream list
160 nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners();
161 streamListeners->RemoveElement(this);
163 // For those cases when NewStream is never called, we still may need
164 // to fire a notification callback. Return network error as fallback
165 // reason because for other cases, notify should have already been
166 // called for other reasons elsewhere.
167 CallURLNotify(NPRES_NETWORK_ERR);
169 // lets get rid of the buffer
170 if (mStreamBuffer) {
171 PR_Free(mStreamBuffer);
172 mStreamBuffer=nullptr;
173 }
175 if (mNotifyURL)
176 PL_strfree(mNotifyURL);
178 if (mResponseHeaderBuf)
179 PL_strfree(mResponseHeaderBuf);
181 if (mNPStreamWrapper) {
182 delete mNPStreamWrapper;
183 }
184 }
186 nsresult
187 nsNPAPIPluginStreamListener::CleanUpStream(NPReason reason)
188 {
189 nsresult rv = NS_ERROR_FAILURE;
191 // Various bits of code in the rest of this method may result in the
192 // deletion of this object. Use a KungFuDeathGrip to keep ourselves
193 // alive during cleanup.
194 nsRefPtr<nsNPAPIPluginStreamListener> kungFuDeathGrip(this);
196 if (mStreamCleanedUp)
197 return NS_OK;
199 mStreamCleanedUp = true;
201 StopDataPump();
203 // Release any outstanding redirect callback.
204 if (mHTTPRedirectCallback) {
205 mHTTPRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
206 mHTTPRedirectCallback = nullptr;
207 }
209 // Seekable streams have an extra addref when they are created which must
210 // be matched here.
211 if (NP_SEEK == mStreamType && mStreamStarted)
212 NS_RELEASE_THIS();
214 if (mStreamListenerPeer) {
215 mStreamListenerPeer->CancelRequests(NS_BINDING_ABORTED);
216 mStreamListenerPeer = nullptr;
217 }
219 if (!mInst || !mInst->CanFireNotifications())
220 return rv;
222 PluginDestructionGuard guard(mInst);
224 nsNPAPIPlugin* plugin = mInst->GetPlugin();
225 if (!plugin || !plugin->GetLibrary())
226 return rv;
228 NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
230 NPP npp;
231 mInst->GetNPP(&npp);
233 if (mStreamStarted && pluginFunctions->destroystream) {
234 NPPAutoPusher nppPusher(npp);
236 NPError error;
237 NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->destroystream)(npp, &mNPStreamWrapper->mNPStream, reason), mInst,
238 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
240 NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
241 ("NPP DestroyStream called: this=%p, npp=%p, reason=%d, return=%d, url=%s\n",
242 this, npp, reason, error, mNPStreamWrapper->mNPStream.url));
244 if (error == NPERR_NO_ERROR)
245 rv = NS_OK;
246 }
248 mStreamStarted = false;
250 // fire notification back to plugin, just like before
251 CallURLNotify(reason);
253 return rv;
254 }
256 void
257 nsNPAPIPluginStreamListener::CallURLNotify(NPReason reason)
258 {
259 if (!mCallNotify || !mInst || !mInst->CanFireNotifications())
260 return;
262 PluginDestructionGuard guard(mInst);
264 mCallNotify = false; // only do this ONCE and prevent recursion
266 nsNPAPIPlugin* plugin = mInst->GetPlugin();
267 if (!plugin || !plugin->GetLibrary())
268 return;
270 NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
272 if (pluginFunctions->urlnotify) {
273 NPP npp;
274 mInst->GetNPP(&npp);
276 NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlnotify)(npp, mNotifyURL, reason, mNPStreamWrapper->mNPStream.notifyData), mInst,
277 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
279 NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
280 ("NPP URLNotify called: this=%p, npp=%p, notify=%p, reason=%d, url=%s\n",
281 this, npp, mNPStreamWrapper->mNPStream.notifyData, reason, mNotifyURL));
282 }
283 }
285 nsresult
286 nsNPAPIPluginStreamListener::OnStartBinding(nsPluginStreamListenerPeer* streamPeer)
287 {
288 if (!mInst || !mInst->CanFireNotifications() || mStreamCleanedUp)
289 return NS_ERROR_FAILURE;
291 PluginDestructionGuard guard(mInst);
293 nsNPAPIPlugin* plugin = mInst->GetPlugin();
294 if (!plugin || !plugin->GetLibrary())
295 return NS_ERROR_FAILURE;
297 NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
299 if (!pluginFunctions->newstream)
300 return NS_ERROR_FAILURE;
302 NPP npp;
303 mInst->GetNPP(&npp);
305 bool seekable;
306 char* contentType;
307 uint16_t streamType = NP_NORMAL;
308 NPError error;
310 streamPeer->GetURL(&mNPStreamWrapper->mNPStream.url);
311 streamPeer->GetLength((uint32_t*)&(mNPStreamWrapper->mNPStream.end));
312 streamPeer->GetLastModified((uint32_t*)&(mNPStreamWrapper->mNPStream.lastmodified));
313 streamPeer->IsSeekable(&seekable);
314 streamPeer->GetContentType(&contentType);
316 if (!mResponseHeaders.IsEmpty()) {
317 mResponseHeaderBuf = PL_strdup(mResponseHeaders.get());
318 mNPStreamWrapper->mNPStream.headers = mResponseHeaderBuf;
319 }
321 mStreamListenerPeer = streamPeer;
323 NPPAutoPusher nppPusher(npp);
325 NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->newstream)(npp, (char*)contentType, &mNPStreamWrapper->mNPStream, seekable, &streamType), mInst,
326 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
328 NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
329 ("NPP NewStream called: this=%p, npp=%p, mime=%s, seek=%d, type=%d, return=%d, url=%s\n",
330 this, npp, (char *)contentType, seekable, streamType, error, mNPStreamWrapper->mNPStream.url));
332 if (error != NPERR_NO_ERROR)
333 return NS_ERROR_FAILURE;
335 switch(streamType)
336 {
337 case NP_NORMAL:
338 mStreamType = NP_NORMAL;
339 break;
340 case NP_ASFILEONLY:
341 mStreamType = NP_ASFILEONLY;
342 break;
343 case NP_ASFILE:
344 mStreamType = NP_ASFILE;
345 break;
346 case NP_SEEK:
347 mStreamType = NP_SEEK;
348 // Seekable streams should continue to exist even after OnStopRequest
349 // is fired, so we AddRef ourself an extra time and Release when the
350 // plugin calls NPN_DestroyStream (CleanUpStream). If the plugin never
351 // calls NPN_DestroyStream the stream will be destroyed before the plugin
352 // instance is destroyed.
353 NS_ADDREF_THIS();
354 break;
355 default:
356 return NS_ERROR_FAILURE;
357 }
359 mStreamStarted = true;
360 return NS_OK;
361 }
363 void
364 nsNPAPIPluginStreamListener::SuspendRequest()
365 {
366 NS_ASSERTION(!mIsSuspended,
367 "Suspending a request that's already suspended!");
369 nsresult rv = StartDataPump();
370 if (NS_FAILED(rv))
371 return;
373 mIsSuspended = true;
375 if (mStreamListenerPeer) {
376 mStreamListenerPeer->SuspendRequests();
377 }
378 }
380 void
381 nsNPAPIPluginStreamListener::ResumeRequest()
382 {
383 mStreamListenerPeer->ResumeRequests();
384 mIsSuspended = false;
385 }
387 nsresult
388 nsNPAPIPluginStreamListener::StartDataPump()
389 {
390 nsresult rv;
391 mDataPumpTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
392 NS_ENSURE_SUCCESS(rv, rv);
394 // Start pumping data to the plugin every 100ms until it obeys and
395 // eats the data.
396 return mDataPumpTimer->InitWithCallback(this, 100,
397 nsITimer::TYPE_REPEATING_SLACK);
398 }
400 void
401 nsNPAPIPluginStreamListener::StopDataPump()
402 {
403 if (mDataPumpTimer) {
404 mDataPumpTimer->Cancel();
405 mDataPumpTimer = nullptr;
406 }
407 }
409 // Return true if a javascript: load that was started while the plugin
410 // was being initialized is still in progress.
411 bool
412 nsNPAPIPluginStreamListener::PluginInitJSLoadInProgress()
413 {
414 if (!mInst)
415 return false;
417 nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners();
418 for (unsigned int i = 0; i < streamListeners->Length(); i++) {
419 if (streamListeners->ElementAt(i)->mIsPluginInitJSStream) {
420 return true;
421 }
422 }
424 return false;
425 }
427 // This method is called when there's more data available off the
428 // network, but it's also called from our data pump when we're feeding
429 // the plugin data that we already got off the network, but the plugin
430 // was unable to consume it at the point it arrived. In the case when
431 // the plugin pump calls this method, the input argument will be null,
432 // and the length will be the number of bytes available in our
433 // internal buffer.
434 nsresult
435 nsNPAPIPluginStreamListener::OnDataAvailable(nsPluginStreamListenerPeer* streamPeer,
436 nsIInputStream* input,
437 uint32_t length)
438 {
439 if (!length || !mInst || !mInst->CanFireNotifications())
440 return NS_ERROR_FAILURE;
442 PluginDestructionGuard guard(mInst);
444 // Just in case the caller switches plugin info on us.
445 mStreamListenerPeer = streamPeer;
447 nsNPAPIPlugin* plugin = mInst->GetPlugin();
448 if (!plugin || !plugin->GetLibrary())
449 return NS_ERROR_FAILURE;
451 NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
453 // check out if plugin implements NPP_Write call
454 if (!pluginFunctions->write)
455 return NS_ERROR_FAILURE; // it'll cancel necko transaction
457 if (!mStreamBuffer) {
458 // To optimize the mem usage & performance we have to allocate
459 // mStreamBuffer here in first ODA when length of data available
460 // in input stream is known. mStreamBuffer will be freed in DTOR.
461 // we also have to remember the size of that buff to make safe
462 // consecutive Read() calls form input stream into our buff.
464 uint32_t contentLength;
465 streamPeer->GetLength(&contentLength);
467 mStreamBufferSize = std::max(length, contentLength);
469 // Limit the size of the initial buffer to MAX_PLUGIN_NECKO_BUFFER
470 // (16k). This buffer will grow if needed, as in the case where
471 // we're getting data faster than the plugin can process it.
472 mStreamBufferSize = std::min(mStreamBufferSize,
473 uint32_t(MAX_PLUGIN_NECKO_BUFFER));
475 mStreamBuffer = (char*) PR_Malloc(mStreamBufferSize);
476 if (!mStreamBuffer)
477 return NS_ERROR_OUT_OF_MEMORY;
478 }
480 // prepare NPP_ calls params
481 NPP npp;
482 mInst->GetNPP(&npp);
484 int32_t streamPosition;
485 streamPeer->GetStreamOffset(&streamPosition);
486 int32_t streamOffset = streamPosition;
488 if (input) {
489 streamOffset += length;
491 // Set new stream offset for the next ODA call regardless of how
492 // following NPP_Write call will behave we pretend to consume all
493 // data from the input stream. It's possible that current steam
494 // position will be overwritten from NPP_RangeRequest call made
495 // from NPP_Write, so we cannot call SetStreamOffset after
496 // NPP_Write.
497 //
498 // Note: there is a special case when data flow should be
499 // temporarily stopped if NPP_WriteReady returns 0 (bug #89270)
500 streamPeer->SetStreamOffset(streamOffset);
502 // set new end in case the content is compressed
503 // initial end is less than end of decompressed stream
504 // and some plugins (e.g. acrobat) can fail.
505 if ((int32_t)mNPStreamWrapper->mNPStream.end < streamOffset)
506 mNPStreamWrapper->mNPStream.end = streamOffset;
507 }
509 nsresult rv = NS_OK;
510 while (NS_SUCCEEDED(rv) && length > 0) {
511 if (input && length) {
512 if (mStreamBufferSize < mStreamBufferByteCount + length && mIsSuspended) {
513 // We're in the ::OnDataAvailable() call that we might get
514 // after suspending a request, or we suspended the request
515 // from within this ::OnDataAvailable() call while there's
516 // still data in the input, and we don't have enough space to
517 // store what we got off the network. Reallocate our internal
518 // buffer.
519 mStreamBufferSize = mStreamBufferByteCount + length;
520 char *buf = (char*)PR_Realloc(mStreamBuffer, mStreamBufferSize);
521 if (!buf)
522 return NS_ERROR_OUT_OF_MEMORY;
524 mStreamBuffer = buf;
525 }
527 uint32_t bytesToRead =
528 std::min(length, mStreamBufferSize - mStreamBufferByteCount);
530 uint32_t amountRead = 0;
531 rv = input->Read(mStreamBuffer + mStreamBufferByteCount, bytesToRead,
532 &amountRead);
533 NS_ENSURE_SUCCESS(rv, rv);
535 if (amountRead == 0) {
536 NS_NOTREACHED("input->Read() returns no data, it's almost impossible "
537 "to get here");
539 break;
540 }
542 mStreamBufferByteCount += amountRead;
543 length -= amountRead;
544 } else {
545 // No input, nothing to read. Set length to 0 so that we don't
546 // keep iterating through this outer loop any more.
548 length = 0;
549 }
551 // Temporary pointer to the beginning of the data we're writing as
552 // we loop and feed the plugin data.
553 char *ptrStreamBuffer = mStreamBuffer;
555 // it is possible plugin's NPP_Write() returns 0 byte consumed. We
556 // use zeroBytesWriteCount to count situation like this and break
557 // the loop
558 int32_t zeroBytesWriteCount = 0;
560 // mStreamBufferByteCount tells us how many bytes there are in the
561 // buffer. WriteReady returns to us how many bytes the plugin is
562 // ready to handle.
563 while (mStreamBufferByteCount > 0) {
564 int32_t numtowrite;
565 if (pluginFunctions->writeready) {
566 NPPAutoPusher nppPusher(npp);
568 NS_TRY_SAFE_CALL_RETURN(numtowrite, (*pluginFunctions->writeready)(npp, &mNPStreamWrapper->mNPStream), mInst,
569 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
570 NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
571 ("NPP WriteReady called: this=%p, npp=%p, "
572 "return(towrite)=%d, url=%s\n",
573 this, npp, numtowrite, mNPStreamWrapper->mNPStream.url));
575 if (!mStreamStarted) {
576 // The plugin called NPN_DestroyStream() from within
577 // NPP_WriteReady(), kill the stream.
579 return NS_BINDING_ABORTED;
580 }
582 // if WriteReady returned 0, the plugin is not ready to handle
583 // the data, suspend the stream (if it isn't already
584 // suspended).
585 //
586 // Also suspend the stream if the stream we're loading is not
587 // a javascript: URL load that was initiated during plugin
588 // initialization and there currently is such a stream
589 // loading. This is done to work around a Windows Media Player
590 // plugin bug where it can't deal with being fed data for
591 // other streams while it's waiting for data from the
592 // javascript: URL loads it requests during
593 // initialization. See bug 386493 for more details.
595 if (numtowrite <= 0 ||
596 (!mIsPluginInitJSStream && PluginInitJSLoadInProgress())) {
597 if (!mIsSuspended) {
598 SuspendRequest();
599 }
601 // Break out of the inner loop, but keep going through the
602 // outer loop in case there's more data to read from the
603 // input stream.
605 break;
606 }
608 numtowrite = std::min(numtowrite, mStreamBufferByteCount);
609 } else {
610 // if WriteReady is not supported by the plugin, just write
611 // the whole buffer
612 numtowrite = mStreamBufferByteCount;
613 }
615 NPPAutoPusher nppPusher(npp);
617 int32_t writeCount = 0; // bytes consumed by plugin instance
618 NS_TRY_SAFE_CALL_RETURN(writeCount, (*pluginFunctions->write)(npp, &mNPStreamWrapper->mNPStream, streamPosition, numtowrite, ptrStreamBuffer), mInst,
619 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
621 NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
622 ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, "
623 "buf=%s, return(written)=%d, url=%s\n",
624 this, npp, streamPosition, numtowrite,
625 ptrStreamBuffer, writeCount, mNPStreamWrapper->mNPStream.url));
627 if (!mStreamStarted) {
628 // The plugin called NPN_DestroyStream() from within
629 // NPP_Write(), kill the stream.
630 return NS_BINDING_ABORTED;
631 }
633 if (writeCount > 0) {
634 NS_ASSERTION(writeCount <= mStreamBufferByteCount,
635 "Plugin read past the end of the available data!");
637 writeCount = std::min(writeCount, mStreamBufferByteCount);
638 mStreamBufferByteCount -= writeCount;
640 streamPosition += writeCount;
642 zeroBytesWriteCount = 0;
644 if (mStreamBufferByteCount > 0) {
645 // This alignment code is most likely bogus, but we'll leave
646 // it in for now in case it matters for some plugins on some
647 // architectures. Who knows...
648 if (writeCount % sizeof(intptr_t)) {
649 // memmove will take care about alignment
650 memmove(mStreamBuffer, ptrStreamBuffer + writeCount,
651 mStreamBufferByteCount);
652 ptrStreamBuffer = mStreamBuffer;
653 } else {
654 // if aligned we can use ptrStreamBuffer += to eliminate
655 // memmove()
656 ptrStreamBuffer += writeCount;
657 }
658 }
659 } else if (writeCount == 0) {
660 // if NPP_Write() returns writeCount == 0 lets say 3 times in
661 // a row, suspend the request and continue feeding the plugin
662 // the data we got so far. Once that data is consumed, we'll
663 // resume the request.
664 if (mIsSuspended || ++zeroBytesWriteCount == 3) {
665 if (!mIsSuspended) {
666 SuspendRequest();
667 }
669 // Break out of the for loop, but keep going through the
670 // while loop in case there's more data to read from the
671 // input stream.
673 break;
674 }
675 } else {
676 // Something's really wrong, kill the stream.
677 rv = NS_ERROR_FAILURE;
679 break;
680 }
681 } // end of inner while loop
683 if (mStreamBufferByteCount && mStreamBuffer != ptrStreamBuffer) {
684 memmove(mStreamBuffer, ptrStreamBuffer, mStreamBufferByteCount);
685 }
686 }
688 if (streamPosition != streamOffset) {
689 // The plugin didn't consume all available data, or consumed some
690 // of our cached data while we're pumping cached data. Adjust the
691 // plugin info's stream offset to match reality, except if the
692 // plugin info's stream offset was set by a re-entering
693 // NPN_RequestRead() call.
695 int32_t postWriteStreamPosition;
696 streamPeer->GetStreamOffset(&postWriteStreamPosition);
698 if (postWriteStreamPosition == streamOffset) {
699 streamPeer->SetStreamOffset(streamPosition);
700 }
701 }
703 return rv;
704 }
706 nsresult
707 nsNPAPIPluginStreamListener::OnFileAvailable(nsPluginStreamListenerPeer* streamPeer,
708 const char* fileName)
709 {
710 if (!mInst || !mInst->CanFireNotifications())
711 return NS_ERROR_FAILURE;
713 PluginDestructionGuard guard(mInst);
715 nsNPAPIPlugin* plugin = mInst->GetPlugin();
716 if (!plugin || !plugin->GetLibrary())
717 return NS_ERROR_FAILURE;
719 NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
721 if (!pluginFunctions->asfile)
722 return NS_ERROR_FAILURE;
724 NPP npp;
725 mInst->GetNPP(&npp);
727 NS_TRY_SAFE_CALL_VOID((*pluginFunctions->asfile)(npp, &mNPStreamWrapper->mNPStream, fileName), mInst,
728 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
730 NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
731 ("NPP StreamAsFile called: this=%p, npp=%p, url=%s, file=%s\n",
732 this, npp, mNPStreamWrapper->mNPStream.url, fileName));
734 return NS_OK;
735 }
737 nsresult
738 nsNPAPIPluginStreamListener::OnStopBinding(nsPluginStreamListenerPeer* streamPeer,
739 nsresult status)
740 {
741 StopDataPump();
743 if (NS_FAILED(status)) {
744 // The stream was destroyed, or died for some reason. Make sure we
745 // cancel the underlying request.
746 if (mStreamListenerPeer) {
747 mStreamListenerPeer->CancelRequests(status);
748 }
749 }
751 if (!mInst || !mInst->CanFireNotifications())
752 return NS_ERROR_FAILURE;
754 NPReason reason = NS_FAILED(status) ? NPRES_NETWORK_ERR : NPRES_DONE;
755 if (mRedirectDenied || status == NS_BINDING_ABORTED) {
756 reason = NPRES_USER_BREAK;
757 }
759 // The following code can result in the deletion of 'this'. Don't
760 // assume we are alive after this!
761 //
762 // Delay cleanup if the stream is of type NP_SEEK and status isn't
763 // NS_BINDING_ABORTED (meaning the plugin hasn't called NPN_DestroyStream).
764 // This is because even though we're done delivering data the plugin may
765 // want to seek. Eventually either the plugin will call NPN_DestroyStream
766 // or we'll perform cleanup when the instance goes away. See bug 91140.
767 if (mStreamType != NP_SEEK ||
768 (NP_SEEK == mStreamType && NS_BINDING_ABORTED == status)) {
769 return CleanUpStream(reason);
770 }
772 return NS_OK;
773 }
775 nsresult
776 nsNPAPIPluginStreamListener::GetStreamType(int32_t *result)
777 {
778 *result = mStreamType;
779 return NS_OK;
780 }
782 NS_IMETHODIMP
783 nsNPAPIPluginStreamListener::Notify(nsITimer *aTimer)
784 {
785 NS_ASSERTION(aTimer == mDataPumpTimer, "Uh, wrong timer?");
787 int32_t oldStreamBufferByteCount = mStreamBufferByteCount;
789 nsresult rv = OnDataAvailable(mStreamListenerPeer, nullptr, mStreamBufferByteCount);
791 if (NS_FAILED(rv)) {
792 // We ran into an error, no need to keep firing this timer then.
793 aTimer->Cancel();
794 return NS_OK;
795 }
797 if (mStreamBufferByteCount != oldStreamBufferByteCount &&
798 ((mStreamStarted && mStreamBufferByteCount < 1024) ||
799 mStreamBufferByteCount == 0)) {
800 // The plugin read some data and we've got less than 1024 bytes in
801 // our buffer (or its empty and the stream is already
802 // done). Resume the request so that we get more data off the
803 // network.
804 ResumeRequest();
805 // Necko will pump data now that we've resumed the request.
806 StopDataPump();
807 }
809 return NS_OK;
810 }
812 NS_IMETHODIMP
813 nsNPAPIPluginStreamListener::StatusLine(const char* line)
814 {
815 mResponseHeaders.Append(line);
816 mResponseHeaders.Append('\n');
817 return NS_OK;
818 }
820 NS_IMETHODIMP
821 nsNPAPIPluginStreamListener::NewResponseHeader(const char* headerName,
822 const char* headerValue)
823 {
824 mResponseHeaders.Append(headerName);
825 mResponseHeaders.Append(": ");
826 mResponseHeaders.Append(headerValue);
827 mResponseHeaders.Append('\n');
828 return NS_OK;
829 }
831 bool
832 nsNPAPIPluginStreamListener::HandleRedirectNotification(nsIChannel *oldChannel, nsIChannel *newChannel,
833 nsIAsyncVerifyRedirectCallback* callback)
834 {
835 nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(oldChannel);
836 nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
837 if (!oldHttpChannel || !newHttpChannel) {
838 return false;
839 }
841 if (!mInst || !mInst->CanFireNotifications()) {
842 return false;
843 }
845 nsNPAPIPlugin* plugin = mInst->GetPlugin();
846 if (!plugin || !plugin->GetLibrary()) {
847 return false;
848 }
850 NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
851 if (!pluginFunctions->urlredirectnotify) {
852 return false;
853 }
855 // A non-null closure is required for redirect handling support.
856 if (mNPStreamWrapper->mNPStream.notifyData) {
857 uint32_t status;
858 if (NS_SUCCEEDED(oldHttpChannel->GetResponseStatus(&status))) {
859 nsCOMPtr<nsIURI> uri;
860 if (NS_SUCCEEDED(newHttpChannel->GetURI(getter_AddRefs(uri))) && uri) {
861 nsAutoCString spec;
862 if (NS_SUCCEEDED(uri->GetAsciiSpec(spec))) {
863 // At this point the plugin will be responsible for making the callback
864 // so save the callback object.
865 mHTTPRedirectCallback = callback;
867 NPP npp;
868 mInst->GetNPP(&npp);
869 #if defined(XP_WIN)
870 NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData), mInst,
871 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
872 #else
873 MAIN_THREAD_JNI_REF_GUARD;
874 (*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData);
875 #endif
876 return true;
877 }
878 }
879 }
880 }
882 callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
883 return true;
884 }
886 void
887 nsNPAPIPluginStreamListener::URLRedirectResponse(NPBool allow)
888 {
889 if (mHTTPRedirectCallback) {
890 mHTTPRedirectCallback->OnRedirectVerifyCallback(allow ? NS_OK : NS_ERROR_FAILURE);
891 mRedirectDenied = allow ? false : true;
892 mHTTPRedirectCallback = nullptr;
893 }
894 }
896 void*
897 nsNPAPIPluginStreamListener::GetNotifyData()
898 {
899 if (mNPStreamWrapper) {
900 return mNPStreamWrapper->mNPStream.notifyData;
901 }
902 return nullptr;
903 }