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