dom/plugins/base/nsNPAPIPluginStreamListener.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial