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