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/. */
5 
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"
17 
18 #include <stdint.h>
19 #include <algorithm>
20 
nsNPAPIStreamWrapper(nsIOutputStream * outputStream,nsNPAPIPluginStreamListener * streamListener)21 nsNPAPIStreamWrapper::nsNPAPIStreamWrapper(nsIOutputStream *outputStream,
22                                            nsNPAPIPluginStreamListener *streamListener)
23 {
24   mOutputStream = outputStream;
25   mStreamListener = streamListener;
26 
27   memset(&mNPStream, 0, sizeof(mNPStream));
28   mNPStream.ndata = static_cast<void*>(this);
29 }
30 
~nsNPAPIStreamWrapper()31 nsNPAPIStreamWrapper::~nsNPAPIStreamWrapper()
32 {
33   if (mOutputStream) {
34     mOutputStream->Close();
35   }
36 }
37 
NS_IMPL_ISUPPORTS(nsPluginStreamToFile,nsIOutputStream)38 NS_IMPL_ISUPPORTS(nsPluginStreamToFile, nsIOutputStream)
39 
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;
49 
50   mTempFile = do_QueryInterface(pluginTmp, &rv);
51   if (NS_FAILED(rv)) return;
52 
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;
56 
57   // Yes, make it unique.
58   rv = mTempFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0700);
59   if (NS_FAILED(rv)) return;
60 
61   // create the file
62   rv = NS_NewLocalFileOutputStream(getter_AddRefs(mOutputStream), mTempFile, -1, 00600);
63   if (NS_FAILED(rv))
64     return;
65 
66   // construct the URL we'll use later in calls to GetURL()
67   NS_GetURLSpecFromFile(mTempFile, mFileURL);
68 
69 #ifdef DEBUG
70   printf("File URL = %s\n", mFileURL.get());
71 #endif
72 }
73 
~nsPluginStreamToFile()74 nsPluginStreamToFile::~nsPluginStreamToFile()
75 {
76   // should we be deleting mTempFile here?
77   if (nullptr != mTarget)
78     PL_strfree(mTarget);
79 }
80 
81 NS_IMETHODIMP
Flush()82 nsPluginStreamToFile::Flush()
83 {
84   return NS_OK;
85 }
86 
87 NS_IMETHODIMP
Write(const char * aBuf,uint32_t aCount,uint32_t * aWriteCount)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, false);
94   return NS_OK;
95 }
96 
97 NS_IMETHODIMP
WriteFrom(nsIInputStream * inStr,uint32_t count,uint32_t * _retval)98 nsPluginStreamToFile::WriteFrom(nsIInputStream *inStr, uint32_t count,
99                                 uint32_t *_retval)
100 {
101   NS_NOTREACHED("WriteFrom");
102   return NS_ERROR_NOT_IMPLEMENTED;
103 }
104 
105 NS_IMETHODIMP
WriteSegments(nsReadSegmentFun reader,void * closure,uint32_t count,uint32_t * _retval)106 nsPluginStreamToFile::WriteSegments(nsReadSegmentFun reader, void * closure,
107                                     uint32_t count, uint32_t *_retval)
108 {
109   NS_NOTREACHED("WriteSegments");
110   return NS_ERROR_NOT_IMPLEMENTED;
111 }
112 
113 NS_IMETHODIMP
IsNonBlocking(bool * aNonBlocking)114 nsPluginStreamToFile::IsNonBlocking(bool *aNonBlocking)
115 {
116   *aNonBlocking = false;
117   return NS_OK;
118 }
119 
120 NS_IMETHODIMP
Close(void)121 nsPluginStreamToFile::Close(void)
122 {
123   mOutputStream->Close();
124   mOwner->GetURL(mFileURL.get(), mTarget, nullptr, nullptr, 0, false);
125   return NS_OK;
126 }
127 
128 // nsNPAPIPluginStreamListener Methods
129 
NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener,nsITimerCallback,nsIHTTPHeaderListener)130 NS_IMPL_ISUPPORTS(nsNPAPIPluginStreamListener,
131                   nsITimerCallback, nsIHTTPHeaderListener)
132 
133 nsNPAPIPluginStreamListener::nsNPAPIPluginStreamListener(nsNPAPIPluginInstance* inst,
134                                                          void* notifyData,
135                                                          const char* aURL)
136   : mStreamBuffer(nullptr)
137   , mNotifyURL(aURL ? PL_strdup(aURL) : nullptr)
138   , mInst(inst)
139   , mStreamBufferSize(0)
140   , mStreamBufferByteCount(0)
141   , mStreamType(NP_NORMAL)
142   , mStreamState(eStreamStopped)
143   , mStreamCleanedUp(false)
144   , mCallNotify(notifyData ? true : false)
145   , mIsSuspended(false)
146   , mIsPluginInitJSStream(mInst->mInPluginInitCall &&
147                           aURL && strncmp(aURL, "javascript:",
148                                           sizeof("javascript:") - 1) == 0)
149   , mRedirectDenied(false)
150   , mResponseHeaderBuf(nullptr)
151   , mStreamStopMode(eNormalStop)
152   , mPendingStopBindingStatus(NS_OK)
153 {
154   mNPStreamWrapper = new nsNPAPIStreamWrapper(nullptr, this);
155   mNPStreamWrapper->mNPStream.notifyData = notifyData;
156 }
157 
~nsNPAPIPluginStreamListener()158 nsNPAPIPluginStreamListener::~nsNPAPIPluginStreamListener()
159 {
160   // remove this from the plugin instance's stream list
161   nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners();
162   streamListeners->RemoveElement(this);
163 
164   // For those cases when NewStream is never called, we still may need
165   // to fire a notification callback. Return network error as fallback
166   // reason because for other cases, notify should have already been
167   // called for other reasons elsewhere.
168   CallURLNotify(NPRES_NETWORK_ERR);
169 
170   // lets get rid of the buffer
171   if (mStreamBuffer) {
172     PR_Free(mStreamBuffer);
173     mStreamBuffer=nullptr;
174   }
175 
176   if (mNotifyURL)
177     PL_strfree(mNotifyURL);
178 
179   if (mResponseHeaderBuf)
180     PL_strfree(mResponseHeaderBuf);
181 
182   if (mNPStreamWrapper) {
183     delete mNPStreamWrapper;
184   }
185 }
186 
187 nsresult
CleanUpStream(NPReason reason)188 nsNPAPIPluginStreamListener::CleanUpStream(NPReason reason)
189 {
190   nsresult rv = NS_ERROR_FAILURE;
191 
192   // Various bits of code in the rest of this method may result in the
193   // deletion of this object. Use a KungFuDeathGrip to keep ourselves
194   // alive during cleanup.
195   RefPtr<nsNPAPIPluginStreamListener> kungFuDeathGrip(this);
196 
197   if (mStreamCleanedUp)
198     return NS_OK;
199 
200   mStreamCleanedUp = true;
201 
202   StopDataPump();
203 
204   // Release any outstanding redirect callback.
205   if (mHTTPRedirectCallback) {
206     mHTTPRedirectCallback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
207     mHTTPRedirectCallback = nullptr;
208   }
209 
210   // Seekable streams have an extra addref when they are created which must
211   // be matched here.
212   if (NP_SEEK == mStreamType && mStreamState == eStreamTypeSet)
213     NS_RELEASE_THIS();
214 
215   if (mStreamListenerPeer) {
216     mStreamListenerPeer->CancelRequests(NS_BINDING_ABORTED);
217     mStreamListenerPeer = nullptr;
218   }
219 
220   if (!mInst || !mInst->CanFireNotifications())
221     return rv;
222 
223   PluginDestructionGuard guard(mInst);
224 
225   nsNPAPIPlugin* plugin = mInst->GetPlugin();
226   if (!plugin || !plugin->GetLibrary())
227     return rv;
228 
229   NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
230 
231   NPP npp;
232   mInst->GetNPP(&npp);
233 
234   if (mStreamState >= eNewStreamCalled && pluginFunctions->destroystream) {
235     NPPAutoPusher nppPusher(npp);
236 
237     NPError error;
238     NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->destroystream)(npp, &mNPStreamWrapper->mNPStream, reason), mInst,
239                             NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
240 
241     NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
242                    ("NPP DestroyStream called: this=%p, npp=%p, reason=%d, return=%d, url=%s\n",
243                     this, npp, reason, error, mNPStreamWrapper->mNPStream.url));
244 
245     if (error == NPERR_NO_ERROR)
246       rv = NS_OK;
247   }
248 
249   mStreamState = eStreamStopped;
250 
251   // fire notification back to plugin, just like before
252   CallURLNotify(reason);
253 
254   return rv;
255 }
256 
257 void
CallURLNotify(NPReason reason)258 nsNPAPIPluginStreamListener::CallURLNotify(NPReason reason)
259 {
260   if (!mCallNotify || !mInst || !mInst->CanFireNotifications())
261     return;
262 
263   PluginDestructionGuard guard(mInst);
264 
265   mCallNotify = false; // only do this ONCE and prevent recursion
266 
267   nsNPAPIPlugin* plugin = mInst->GetPlugin();
268   if (!plugin || !plugin->GetLibrary())
269     return;
270 
271   NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
272 
273   if (pluginFunctions->urlnotify) {
274     NPP npp;
275     mInst->GetNPP(&npp);
276 
277     NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlnotify)(npp, mNotifyURL, reason, mNPStreamWrapper->mNPStream.notifyData), mInst,
278                           NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
279 
280     NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
281                    ("NPP URLNotify called: this=%p, npp=%p, notify=%p, reason=%d, url=%s\n",
282                     this, npp, mNPStreamWrapper->mNPStream.notifyData, reason, mNotifyURL));
283   }
284 }
285 
286 nsresult
OnStartBinding(nsPluginStreamListenerPeer * streamPeer)287 nsNPAPIPluginStreamListener::OnStartBinding(nsPluginStreamListenerPeer* streamPeer)
288 {
289   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
290   if (!mInst || !mInst->CanFireNotifications() || mStreamCleanedUp)
291     return NS_ERROR_FAILURE;
292 
293   PluginDestructionGuard guard(mInst);
294 
295   nsNPAPIPlugin* plugin = mInst->GetPlugin();
296   if (!plugin || !plugin->GetLibrary())
297     return NS_ERROR_FAILURE;
298 
299   NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
300 
301   if (!pluginFunctions->newstream)
302     return NS_ERROR_FAILURE;
303 
304   NPP npp;
305   mInst->GetNPP(&npp);
306 
307   bool seekable;
308   char* contentType;
309   uint16_t streamType = NP_NORMAL;
310   NPError error;
311 
312   streamPeer->GetURL(&mNPStreamWrapper->mNPStream.url);
313   streamPeer->GetLength((uint32_t*)&(mNPStreamWrapper->mNPStream.end));
314   streamPeer->GetLastModified((uint32_t*)&(mNPStreamWrapper->mNPStream.lastmodified));
315   streamPeer->IsSeekable(&seekable);
316   streamPeer->GetContentType(&contentType);
317 
318   if (!mResponseHeaders.IsEmpty()) {
319     mResponseHeaderBuf = PL_strdup(mResponseHeaders.get());
320     mNPStreamWrapper->mNPStream.headers = mResponseHeaderBuf;
321   }
322 
323   mStreamListenerPeer = streamPeer;
324 
325   NPPAutoPusher nppPusher(npp);
326 
327   NS_TRY_SAFE_CALL_RETURN(error, (*pluginFunctions->newstream)(npp, (char*)contentType, &mNPStreamWrapper->mNPStream, seekable, &streamType), mInst,
328                           NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
329 
330   NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
331                  ("NPP NewStream called: this=%p, npp=%p, mime=%s, seek=%d, type=%d, return=%d, url=%s\n",
332                   this, npp, (char *)contentType, seekable, streamType, error, mNPStreamWrapper->mNPStream.url));
333 
334   if (error != NPERR_NO_ERROR)
335     return NS_ERROR_FAILURE;
336 
337   mStreamState = eNewStreamCalled;
338 
339   if (!SetStreamType(streamType, false)) {
340     return NS_ERROR_FAILURE;
341   }
342 
343   return NS_OK;
344 }
345 
346 bool
SetStreamType(uint16_t aType,bool aNeedsResume)347 nsNPAPIPluginStreamListener::SetStreamType(uint16_t aType, bool aNeedsResume)
348 {
349   switch(aType)
350   {
351     case NP_NORMAL:
352       mStreamType = NP_NORMAL;
353       break;
354     case NP_ASFILEONLY:
355       mStreamType = NP_ASFILEONLY;
356       break;
357     case NP_ASFILE:
358       mStreamType = NP_ASFILE;
359       break;
360     case NP_SEEK:
361       mStreamType = NP_SEEK;
362       // Seekable streams should continue to exist even after OnStopRequest
363       // is fired, so we AddRef ourself an extra time and Release when the
364       // plugin calls NPN_DestroyStream (CleanUpStream). If the plugin never
365       // calls NPN_DestroyStream the stream will be destroyed before the plugin
366       // instance is destroyed.
367       NS_ADDREF_THIS();
368       break;
369     case nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN:
370       MOZ_ASSERT(!aNeedsResume);
371       mStreamType = nsPluginStreamListenerPeer::STREAM_TYPE_UNKNOWN;
372       SuspendRequest();
373       mStreamStopMode = eDoDeferredStop;
374       // In this case we do not want to execute anything else in this function.
375       return true;
376     default:
377       return false;
378   }
379   mStreamState = eStreamTypeSet;
380   if (aNeedsResume) {
381     if (mStreamListenerPeer) {
382       mStreamListenerPeer->OnStreamTypeSet(mStreamType);
383     }
384     ResumeRequest();
385   }
386   return true;
387 }
388 
389 void
SuspendRequest()390 nsNPAPIPluginStreamListener::SuspendRequest()
391 {
392   NS_ASSERTION(!mIsSuspended,
393                "Suspending a request that's already suspended!");
394 
395   nsresult rv = StartDataPump();
396   if (NS_FAILED(rv))
397     return;
398 
399   mIsSuspended = true;
400 
401   if (mStreamListenerPeer) {
402     mStreamListenerPeer->SuspendRequests();
403   }
404 }
405 
406 void
ResumeRequest()407 nsNPAPIPluginStreamListener::ResumeRequest()
408 {
409   if (mStreamListenerPeer) {
410     mStreamListenerPeer->ResumeRequests();
411   }
412   mIsSuspended = false;
413 }
414 
415 nsresult
StartDataPump()416 nsNPAPIPluginStreamListener::StartDataPump()
417 {
418   nsresult rv;
419   mDataPumpTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
420   NS_ENSURE_SUCCESS(rv, rv);
421 
422   // Start pumping data to the plugin every 100ms until it obeys and
423   // eats the data.
424   return mDataPumpTimer->InitWithCallback(this, 100,
425                                           nsITimer::TYPE_REPEATING_SLACK);
426 }
427 
428 void
StopDataPump()429 nsNPAPIPluginStreamListener::StopDataPump()
430 {
431   if (mDataPumpTimer) {
432     mDataPumpTimer->Cancel();
433     mDataPumpTimer = nullptr;
434   }
435 }
436 
437 // Return true if a javascript: load that was started while the plugin
438 // was being initialized is still in progress.
439 bool
PluginInitJSLoadInProgress()440 nsNPAPIPluginStreamListener::PluginInitJSLoadInProgress()
441 {
442   if (!mInst)
443     return false;
444 
445   nsTArray<nsNPAPIPluginStreamListener*> *streamListeners = mInst->StreamListeners();
446   for (unsigned int i = 0; i < streamListeners->Length(); i++) {
447     if (streamListeners->ElementAt(i)->mIsPluginInitJSStream) {
448       return true;
449     }
450   }
451 
452   return false;
453 }
454 
455 // This method is called when there's more data available off the
456 // network, but it's also called from our data pump when we're feeding
457 // the plugin data that we already got off the network, but the plugin
458 // was unable to consume it at the point it arrived. In the case when
459 // the plugin pump calls this method, the input argument will be null,
460 // and the length will be the number of bytes available in our
461 // internal buffer.
462 nsresult
OnDataAvailable(nsPluginStreamListenerPeer * streamPeer,nsIInputStream * input,uint32_t length)463 nsNPAPIPluginStreamListener::OnDataAvailable(nsPluginStreamListenerPeer* streamPeer,
464                                              nsIInputStream* input,
465                                              uint32_t length)
466 {
467   if (!length || !mInst || !mInst->CanFireNotifications())
468     return NS_ERROR_FAILURE;
469 
470   PluginDestructionGuard guard(mInst);
471 
472   // Just in case the caller switches plugin info on us.
473   mStreamListenerPeer = streamPeer;
474 
475   nsNPAPIPlugin* plugin = mInst->GetPlugin();
476   if (!plugin || !plugin->GetLibrary())
477     return NS_ERROR_FAILURE;
478 
479   NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
480 
481   // check out if plugin implements NPP_Write call
482   if (!pluginFunctions->write)
483     return NS_ERROR_FAILURE; // it'll cancel necko transaction
484 
485   if (!mStreamBuffer) {
486     // To optimize the mem usage & performance we have to allocate
487     // mStreamBuffer here in first ODA when length of data available
488     // in input stream is known.  mStreamBuffer will be freed in DTOR.
489     // we also have to remember the size of that buff to make safe
490     // consecutive Read() calls form input stream into our buff.
491 
492     uint32_t contentLength;
493     streamPeer->GetLength(&contentLength);
494 
495     mStreamBufferSize = std::max(length, contentLength);
496 
497     // Limit the size of the initial buffer to MAX_PLUGIN_NECKO_BUFFER
498     // (16k). This buffer will grow if needed, as in the case where
499     // we're getting data faster than the plugin can process it.
500     mStreamBufferSize = std::min(mStreamBufferSize,
501                                uint32_t(MAX_PLUGIN_NECKO_BUFFER));
502 
503     mStreamBuffer = (char*) PR_Malloc(mStreamBufferSize);
504     if (!mStreamBuffer)
505       return NS_ERROR_OUT_OF_MEMORY;
506   }
507 
508   // prepare NPP_ calls params
509   NPP npp;
510   mInst->GetNPP(&npp);
511 
512   int32_t streamPosition;
513   streamPeer->GetStreamOffset(&streamPosition);
514   int32_t streamOffset = streamPosition;
515 
516   if (input) {
517     streamOffset += length;
518 
519     // Set new stream offset for the next ODA call regardless of how
520     // following NPP_Write call will behave we pretend to consume all
521     // data from the input stream.  It's possible that current steam
522     // position will be overwritten from NPP_RangeRequest call made
523     // from NPP_Write, so we cannot call SetStreamOffset after
524     // NPP_Write.
525     //
526     // Note: there is a special case when data flow should be
527     // temporarily stopped if NPP_WriteReady returns 0 (bug #89270)
528     streamPeer->SetStreamOffset(streamOffset);
529 
530     // set new end in case the content is compressed
531     // initial end is less than end of decompressed stream
532     // and some plugins (e.g. acrobat) can fail.
533     if ((int32_t)mNPStreamWrapper->mNPStream.end < streamOffset)
534       mNPStreamWrapper->mNPStream.end = streamOffset;
535   }
536 
537   nsresult rv = NS_OK;
538   while (NS_SUCCEEDED(rv) && length > 0) {
539     if (input && length) {
540       if (mStreamBufferSize < mStreamBufferByteCount + length) {
541         // We're in the ::OnDataAvailable() call that we might get
542         // after suspending a request, or we suspended the request
543         // from within this ::OnDataAvailable() call while there's
544         // still data in the input, or we have resumed a previously
545         // suspended request and our buffer is already full, and we
546         // don't have enough space to store what we got off the network.
547         // Reallocate our internal buffer.
548         mStreamBufferSize = mStreamBufferByteCount + length;
549         char *buf = (char*)PR_Realloc(mStreamBuffer, mStreamBufferSize);
550         if (!buf)
551           return NS_ERROR_OUT_OF_MEMORY;
552 
553         mStreamBuffer = buf;
554       }
555 
556       uint32_t bytesToRead =
557       std::min(length, mStreamBufferSize - mStreamBufferByteCount);
558       MOZ_ASSERT(bytesToRead > 0);
559 
560       uint32_t amountRead = 0;
561       rv = input->Read(mStreamBuffer + mStreamBufferByteCount, bytesToRead,
562                        &amountRead);
563       NS_ENSURE_SUCCESS(rv, rv);
564 
565       if (amountRead == 0) {
566         NS_NOTREACHED("input->Read() returns no data, it's almost impossible "
567                       "to get here");
568 
569         break;
570       }
571 
572       mStreamBufferByteCount += amountRead;
573       length -= amountRead;
574     } else {
575       // No input, nothing to read. Set length to 0 so that we don't
576       // keep iterating through this outer loop any more.
577 
578       length = 0;
579     }
580 
581     // Temporary pointer to the beginning of the data we're writing as
582     // we loop and feed the plugin data.
583     char *ptrStreamBuffer = mStreamBuffer;
584 
585     // it is possible plugin's NPP_Write() returns 0 byte consumed. We
586     // use zeroBytesWriteCount to count situation like this and break
587     // the loop
588     int32_t zeroBytesWriteCount = 0;
589 
590     // mStreamBufferByteCount tells us how many bytes there are in the
591     // buffer. WriteReady returns to us how many bytes the plugin is
592     // ready to handle.
593     while (mStreamBufferByteCount > 0) {
594       int32_t numtowrite;
595       if (pluginFunctions->writeready) {
596         NPPAutoPusher nppPusher(npp);
597 
598         NS_TRY_SAFE_CALL_RETURN(numtowrite, (*pluginFunctions->writeready)(npp, &mNPStreamWrapper->mNPStream), mInst,
599                                 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
600         NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
601                        ("NPP WriteReady called: this=%p, npp=%p, "
602                         "return(towrite)=%d, url=%s\n",
603                         this, npp, numtowrite, mNPStreamWrapper->mNPStream.url));
604 
605         if (mStreamState == eStreamStopped) {
606           // The plugin called NPN_DestroyStream() from within
607           // NPP_WriteReady(), kill the stream.
608 
609           return NS_BINDING_ABORTED;
610         }
611 
612         // if WriteReady returned 0, the plugin is not ready to handle
613         // the data, suspend the stream (if it isn't already
614         // suspended).
615         //
616         // Also suspend the stream if the stream we're loading is not
617         // a javascript: URL load that was initiated during plugin
618         // initialization and there currently is such a stream
619         // loading. This is done to work around a Windows Media Player
620         // plugin bug where it can't deal with being fed data for
621         // other streams while it's waiting for data from the
622         // javascript: URL loads it requests during
623         // initialization. See bug 386493 for more details.
624 
625         if (numtowrite <= 0 ||
626             (!mIsPluginInitJSStream && PluginInitJSLoadInProgress())) {
627           if (!mIsSuspended) {
628             SuspendRequest();
629           }
630 
631           // Break out of the inner loop, but keep going through the
632           // outer loop in case there's more data to read from the
633           // input stream.
634 
635           break;
636         }
637 
638         numtowrite = std::min(numtowrite, mStreamBufferByteCount);
639       } else {
640         // if WriteReady is not supported by the plugin, just write
641         // the whole buffer
642         numtowrite = mStreamBufferByteCount;
643       }
644 
645       NPPAutoPusher nppPusher(npp);
646 
647       int32_t writeCount = 0; // bytes consumed by plugin instance
648       NS_TRY_SAFE_CALL_RETURN(writeCount, (*pluginFunctions->write)(npp, &mNPStreamWrapper->mNPStream, streamPosition, numtowrite, ptrStreamBuffer), mInst,
649                               NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
650 
651       NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
652                      ("NPP Write called: this=%p, npp=%p, pos=%d, len=%d, "
653                       "buf=%s, return(written)=%d,  url=%s\n",
654                       this, npp, streamPosition, numtowrite,
655                       ptrStreamBuffer, writeCount, mNPStreamWrapper->mNPStream.url));
656 
657       if (mStreamState == eStreamStopped) {
658         // The plugin called NPN_DestroyStream() from within
659         // NPP_Write(), kill the stream.
660         return NS_BINDING_ABORTED;
661       }
662 
663       if (writeCount > 0) {
664         NS_ASSERTION(writeCount <= mStreamBufferByteCount,
665                      "Plugin read past the end of the available data!");
666 
667         writeCount = std::min(writeCount, mStreamBufferByteCount);
668         mStreamBufferByteCount -= writeCount;
669 
670         streamPosition += writeCount;
671 
672         zeroBytesWriteCount = 0;
673 
674         if (mStreamBufferByteCount > 0) {
675           // This alignment code is most likely bogus, but we'll leave
676           // it in for now in case it matters for some plugins on some
677           // architectures. Who knows...
678           if (writeCount % sizeof(intptr_t)) {
679             // memmove will take care  about alignment
680             memmove(mStreamBuffer, ptrStreamBuffer + writeCount,
681                     mStreamBufferByteCount);
682             ptrStreamBuffer = mStreamBuffer;
683           } else {
684             // if aligned we can use ptrStreamBuffer += to eliminate
685             // memmove()
686             ptrStreamBuffer += writeCount;
687           }
688         }
689       } else if (writeCount == 0) {
690         // if NPP_Write() returns writeCount == 0 lets say 3 times in
691         // a row, suspend the request and continue feeding the plugin
692         // the data we got so far. Once that data is consumed, we'll
693         // resume the request.
694         if (mIsSuspended || ++zeroBytesWriteCount == 3) {
695           if (!mIsSuspended) {
696             SuspendRequest();
697           }
698 
699           // Break out of the for loop, but keep going through the
700           // while loop in case there's more data to read from the
701           // input stream.
702 
703           break;
704         }
705       } else {
706         // Something's really wrong, kill the stream.
707         rv = NS_ERROR_FAILURE;
708 
709         break;
710       }
711     } // end of inner while loop
712 
713     if (mStreamBufferByteCount && mStreamBuffer != ptrStreamBuffer) {
714       memmove(mStreamBuffer, ptrStreamBuffer, mStreamBufferByteCount);
715     }
716   }
717 
718   if (streamPosition != streamOffset) {
719     // The plugin didn't consume all available data, or consumed some
720     // of our cached data while we're pumping cached data. Adjust the
721     // plugin info's stream offset to match reality, except if the
722     // plugin info's stream offset was set by a re-entering
723     // NPN_RequestRead() call.
724 
725     int32_t postWriteStreamPosition;
726     streamPeer->GetStreamOffset(&postWriteStreamPosition);
727 
728     if (postWriteStreamPosition == streamOffset) {
729       streamPeer->SetStreamOffset(streamPosition);
730     }
731   }
732 
733   return rv;
734 }
735 
736 nsresult
OnFileAvailable(nsPluginStreamListenerPeer * streamPeer,const char * fileName)737 nsNPAPIPluginStreamListener::OnFileAvailable(nsPluginStreamListenerPeer* streamPeer,
738                                              const char* fileName)
739 {
740   if (!mInst || !mInst->CanFireNotifications())
741     return NS_ERROR_FAILURE;
742 
743   PluginDestructionGuard guard(mInst);
744 
745   nsNPAPIPlugin* plugin = mInst->GetPlugin();
746   if (!plugin || !plugin->GetLibrary())
747     return NS_ERROR_FAILURE;
748 
749   NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
750 
751   if (!pluginFunctions->asfile)
752     return NS_ERROR_FAILURE;
753 
754   NPP npp;
755   mInst->GetNPP(&npp);
756 
757   NS_TRY_SAFE_CALL_VOID((*pluginFunctions->asfile)(npp, &mNPStreamWrapper->mNPStream, fileName), mInst,
758                         NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
759 
760   NPP_PLUGIN_LOG(PLUGIN_LOG_NORMAL,
761                  ("NPP StreamAsFile called: this=%p, npp=%p, url=%s, file=%s\n",
762                   this, npp, mNPStreamWrapper->mNPStream.url, fileName));
763 
764   return NS_OK;
765 }
766 
767 nsresult
OnStopBinding(nsPluginStreamListenerPeer * streamPeer,nsresult status)768 nsNPAPIPluginStreamListener::OnStopBinding(nsPluginStreamListenerPeer* streamPeer,
769                                            nsresult status)
770 {
771   if (NS_FAILED(status)) {
772     // The stream was destroyed, or died for some reason. Make sure we
773     // cancel the underlying request.
774     if (mStreamListenerPeer) {
775       mStreamListenerPeer->CancelRequests(status);
776     }
777   }
778 
779   if (!mInst || !mInst->CanFireNotifications()) {
780     StopDataPump();
781     return NS_ERROR_FAILURE;
782   }
783 
784   // We need to detect that the stop is due to async stream init completion.
785   if (mStreamStopMode == eDoDeferredStop) {
786     // We shouldn't be delivering this until async init is done
787     mStreamStopMode = eStopPending;
788     mPendingStopBindingStatus = status;
789     if (!mDataPumpTimer) {
790       StartDataPump();
791     }
792     return NS_OK;
793   }
794 
795   StopDataPump();
796 
797   NPReason reason = NS_FAILED(status) ? NPRES_NETWORK_ERR : NPRES_DONE;
798   if (mRedirectDenied || status == NS_BINDING_ABORTED) {
799     reason = NPRES_USER_BREAK;
800   }
801 
802   // The following code can result in the deletion of 'this'. Don't
803   // assume we are alive after this!
804   //
805   // Delay cleanup if the stream is of type NP_SEEK and status isn't
806   // NS_BINDING_ABORTED (meaning the plugin hasn't called NPN_DestroyStream).
807   // This is because even though we're done delivering data the plugin may
808   // want to seek. Eventually either the plugin will call NPN_DestroyStream
809   // or we'll perform cleanup when the instance goes away. See bug 91140.
810   if (mStreamType != NP_SEEK ||
811       (NP_SEEK == mStreamType && NS_BINDING_ABORTED == status)) {
812     return CleanUpStream(reason);
813   }
814 
815   return NS_OK;
816 }
817 
818 nsresult
GetStreamType(int32_t * result)819 nsNPAPIPluginStreamListener::GetStreamType(int32_t *result)
820 {
821   *result = mStreamType;
822   return NS_OK;
823 }
824 
825 bool
MaybeRunStopBinding()826 nsNPAPIPluginStreamListener::MaybeRunStopBinding()
827 {
828   if (mIsSuspended || mStreamStopMode != eStopPending) {
829     return false;
830   }
831   OnStopBinding(mStreamListenerPeer, mPendingStopBindingStatus);
832   mStreamStopMode = eNormalStop;
833   return true;
834 }
835 
836 NS_IMETHODIMP
Notify(nsITimer * aTimer)837 nsNPAPIPluginStreamListener::Notify(nsITimer *aTimer)
838 {
839   NS_ASSERTION(aTimer == mDataPumpTimer, "Uh, wrong timer?");
840 
841   int32_t oldStreamBufferByteCount = mStreamBufferByteCount;
842 
843   nsresult rv = OnDataAvailable(mStreamListenerPeer, nullptr, mStreamBufferByteCount);
844 
845   if (NS_FAILED(rv)) {
846     // We ran into an error, no need to keep firing this timer then.
847     StopDataPump();
848     MaybeRunStopBinding();
849     return NS_OK;
850   }
851 
852   if (mStreamBufferByteCount != oldStreamBufferByteCount &&
853       ((mStreamState == eStreamTypeSet && mStreamBufferByteCount < 1024) ||
854        mStreamBufferByteCount == 0)) {
855         // The plugin read some data and we've got less than 1024 bytes in
856         // our buffer (or its empty and the stream is already
857         // done). Resume the request so that we get more data off the
858         // network.
859         ResumeRequest();
860         // Necko will pump data now that we've resumed the request.
861         StopDataPump();
862       }
863 
864   MaybeRunStopBinding();
865   return NS_OK;
866 }
867 
868 NS_IMETHODIMP
StatusLine(const char * line)869 nsNPAPIPluginStreamListener::StatusLine(const char* line)
870 {
871   mResponseHeaders.Append(line);
872   mResponseHeaders.Append('\n');
873   return NS_OK;
874 }
875 
876 NS_IMETHODIMP
NewResponseHeader(const char * headerName,const char * headerValue)877 nsNPAPIPluginStreamListener::NewResponseHeader(const char* headerName,
878                                                const char* headerValue)
879 {
880   mResponseHeaders.Append(headerName);
881   mResponseHeaders.AppendLiteral(": ");
882   mResponseHeaders.Append(headerValue);
883   mResponseHeaders.Append('\n');
884   return NS_OK;
885 }
886 
887 bool
HandleRedirectNotification(nsIChannel * oldChannel,nsIChannel * newChannel,nsIAsyncVerifyRedirectCallback * callback)888 nsNPAPIPluginStreamListener::HandleRedirectNotification(nsIChannel *oldChannel, nsIChannel *newChannel,
889                                                         nsIAsyncVerifyRedirectCallback* callback)
890 {
891   nsCOMPtr<nsIHttpChannel> oldHttpChannel = do_QueryInterface(oldChannel);
892   nsCOMPtr<nsIHttpChannel> newHttpChannel = do_QueryInterface(newChannel);
893   if (!oldHttpChannel || !newHttpChannel) {
894     return false;
895   }
896 
897   if (!mInst || !mInst->CanFireNotifications()) {
898     return false;
899   }
900 
901   nsNPAPIPlugin* plugin = mInst->GetPlugin();
902   if (!plugin || !plugin->GetLibrary()) {
903     return false;
904   }
905 
906   NPPluginFuncs* pluginFunctions = plugin->PluginFuncs();
907   if (!pluginFunctions->urlredirectnotify) {
908     return false;
909   }
910 
911   // A non-null closure is required for redirect handling support.
912   if (mNPStreamWrapper->mNPStream.notifyData) {
913     uint32_t status;
914     if (NS_SUCCEEDED(oldHttpChannel->GetResponseStatus(&status))) {
915       nsCOMPtr<nsIURI> uri;
916       if (NS_SUCCEEDED(newHttpChannel->GetURI(getter_AddRefs(uri))) && uri) {
917         nsAutoCString spec;
918         if (NS_SUCCEEDED(uri->GetAsciiSpec(spec))) {
919           // At this point the plugin will be responsible for making the callback
920           // so save the callback object.
921           mHTTPRedirectCallback = callback;
922 
923           NPP npp;
924           mInst->GetNPP(&npp);
925 #if defined(XP_WIN)
926           NS_TRY_SAFE_CALL_VOID((*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData), mInst,
927                                 NS_PLUGIN_CALL_UNSAFE_TO_REENTER_GECKO);
928 #else
929           MAIN_THREAD_JNI_REF_GUARD;
930           (*pluginFunctions->urlredirectnotify)(npp, spec.get(), static_cast<int32_t>(status), mNPStreamWrapper->mNPStream.notifyData);
931 #endif
932           return true;
933         }
934       }
935     }
936   }
937 
938   callback->OnRedirectVerifyCallback(NS_ERROR_FAILURE);
939   return true;
940 }
941 
942 void
URLRedirectResponse(NPBool allow)943 nsNPAPIPluginStreamListener::URLRedirectResponse(NPBool allow)
944 {
945   if (mHTTPRedirectCallback) {
946     mHTTPRedirectCallback->OnRedirectVerifyCallback(allow ? NS_OK : NS_ERROR_FAILURE);
947     mRedirectDenied = allow ? false : true;
948     mHTTPRedirectCallback = nullptr;
949   }
950 }
951 
952 void*
GetNotifyData()953 nsNPAPIPluginStreamListener::GetNotifyData()
954 {
955   if (mNPStreamWrapper) {
956     return mNPStreamWrapper->mNPStream.notifyData;
957   }
958   return nullptr;
959 }
960