Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsPluginStreamListenerPeer.h"
7 #include "nsIStreamConverterService.h"
8 #include "nsIHttpChannel.h"
9 #include "nsIHttpChannelInternal.h"
10 #include "nsIFileChannel.h"
11 #include "nsMimeTypes.h"
12 #include "nsISupportsPrimitives.h"
13 #include "nsNetCID.h"
14 #include "nsPluginLogging.h"
15 #include "nsIURI.h"
16 #include "nsIURL.h"
17 #include "nsPluginHost.h"
18 #include "nsIByteRangeRequest.h"
19 #include "nsIMultiPartChannel.h"
20 #include "nsIInputStreamTee.h"
21 #include "nsPrintfCString.h"
22 #include "nsIScriptGlobalObject.h"
23 #include "nsIDocument.h"
24 #include "nsIWebNavigation.h"
25 #include "nsContentUtils.h"
26 #include "nsNetUtil.h"
27 #include "nsPluginNativeWindow.h"
28 #include "GeckoProfiler.h"
29 #include "nsPluginInstanceOwner.h"
30 #include "nsDataHashtable.h"
32 #define MAGIC_REQUEST_CONTEXT 0x01020304
34 // nsPluginByteRangeStreamListener
36 class nsPluginByteRangeStreamListener
37 : public nsIStreamListener
38 , public nsIInterfaceRequestor
39 {
40 public:
41 nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr);
42 virtual ~nsPluginByteRangeStreamListener();
44 NS_DECL_ISUPPORTS
45 NS_DECL_NSIREQUESTOBSERVER
46 NS_DECL_NSISTREAMLISTENER
47 NS_DECL_NSIINTERFACEREQUESTOR
49 private:
50 nsCOMPtr<nsIStreamListener> mStreamConverter;
51 nsWeakPtr mWeakPtrPluginStreamListenerPeer;
52 bool mRemoveMagicNumber;
53 };
55 NS_IMPL_ISUPPORTS(nsPluginByteRangeStreamListener,
56 nsIRequestObserver,
57 nsIStreamListener,
58 nsIInterfaceRequestor)
60 nsPluginByteRangeStreamListener::nsPluginByteRangeStreamListener(nsIWeakReference* aWeakPtr)
61 {
62 mWeakPtrPluginStreamListenerPeer = aWeakPtr;
63 mRemoveMagicNumber = false;
64 }
66 nsPluginByteRangeStreamListener::~nsPluginByteRangeStreamListener()
67 {
68 mStreamConverter = 0;
69 mWeakPtrPluginStreamListenerPeer = 0;
70 }
72 /**
73 * Unwrap any byte-range requests so that we can check whether the base channel
74 * is being tracked properly.
75 */
76 static nsCOMPtr<nsIRequest>
77 GetBaseRequest(nsIRequest* r)
78 {
79 nsCOMPtr<nsIMultiPartChannel> mp = do_QueryInterface(r);
80 if (!mp)
81 return r;
83 nsCOMPtr<nsIChannel> base;
84 mp->GetBaseChannel(getter_AddRefs(base));
85 return already_AddRefed<nsIRequest>(base.forget());
86 }
88 NS_IMETHODIMP
89 nsPluginByteRangeStreamListener::OnStartRequest(nsIRequest *request, nsISupports *ctxt)
90 {
91 nsresult rv;
93 nsCOMPtr<nsIStreamListener> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer);
94 if (!finalStreamListener)
95 return NS_ERROR_FAILURE;
97 nsPluginStreamListenerPeer *pslp =
98 static_cast<nsPluginStreamListenerPeer*>(finalStreamListener.get());
100 NS_ASSERTION(pslp->mRequests.IndexOfObject(GetBaseRequest(request)) != -1,
101 "Untracked byte-range request?");
103 nsCOMPtr<nsIStreamConverterService> serv = do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv);
104 if (NS_SUCCEEDED(rv)) {
105 rv = serv->AsyncConvertData(MULTIPART_BYTERANGES,
106 "*/*",
107 finalStreamListener,
108 nullptr,
109 getter_AddRefs(mStreamConverter));
110 if (NS_SUCCEEDED(rv)) {
111 rv = mStreamConverter->OnStartRequest(request, ctxt);
112 if (NS_SUCCEEDED(rv))
113 return rv;
114 }
115 }
116 mStreamConverter = 0;
118 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(request));
119 if (!httpChannel) {
120 return NS_ERROR_FAILURE;
121 }
123 uint32_t responseCode = 0;
124 rv = httpChannel->GetResponseStatus(&responseCode);
125 if (NS_FAILED(rv)) {
126 return NS_ERROR_FAILURE;
127 }
129 if (responseCode != 200) {
130 uint32_t wantsAllNetworkStreams = 0;
131 rv = pslp->GetPluginInstance()->GetValueFromPlugin(NPPVpluginWantsAllNetworkStreams,
132 &wantsAllNetworkStreams);
133 // If the call returned an error code make sure we still use our default value.
134 if (NS_FAILED(rv)) {
135 wantsAllNetworkStreams = 0;
136 }
138 if (!wantsAllNetworkStreams){
139 return NS_ERROR_FAILURE;
140 }
141 }
143 // if server cannot continue with byte range (206 status) and sending us whole object (200 status)
144 // reset this seekable stream & try serve it to plugin instance as a file
145 mStreamConverter = finalStreamListener;
146 mRemoveMagicNumber = true;
148 rv = pslp->ServeStreamAsFile(request, ctxt);
149 return rv;
150 }
152 NS_IMETHODIMP
153 nsPluginByteRangeStreamListener::OnStopRequest(nsIRequest *request, nsISupports *ctxt,
154 nsresult status)
155 {
156 if (!mStreamConverter)
157 return NS_ERROR_FAILURE;
159 nsCOMPtr<nsIStreamListener> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer);
160 if (!finalStreamListener)
161 return NS_ERROR_FAILURE;
163 nsPluginStreamListenerPeer *pslp =
164 static_cast<nsPluginStreamListenerPeer*>(finalStreamListener.get());
165 bool found = pslp->mRequests.RemoveObject(request);
166 if (!found) {
167 NS_ERROR("OnStopRequest received for untracked byte-range request!");
168 }
170 if (mRemoveMagicNumber) {
171 // remove magic number from container
172 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(ctxt);
173 if (container) {
174 uint32_t magicNumber = 0;
175 container->GetData(&magicNumber);
176 if (magicNumber == MAGIC_REQUEST_CONTEXT) {
177 // to allow properly finish nsPluginStreamListenerPeer->OnStopRequest()
178 // set it to something that is not the magic number.
179 container->SetData(0);
180 }
181 } else {
182 NS_WARNING("Bad state of nsPluginByteRangeStreamListener");
183 }
184 }
186 return mStreamConverter->OnStopRequest(request, ctxt, status);
187 }
189 // CachedFileHolder
191 CachedFileHolder::CachedFileHolder(nsIFile* cacheFile)
192 : mFile(cacheFile)
193 {
194 NS_ASSERTION(mFile, "Empty CachedFileHolder");
195 }
197 CachedFileHolder::~CachedFileHolder()
198 {
199 mFile->Remove(false);
200 }
202 void
203 CachedFileHolder::AddRef()
204 {
205 ++mRefCnt;
206 NS_LOG_ADDREF(this, mRefCnt, "CachedFileHolder", sizeof(*this));
207 }
209 void
210 CachedFileHolder::Release()
211 {
212 --mRefCnt;
213 NS_LOG_RELEASE(this, mRefCnt, "CachedFileHolder");
214 if (0 == mRefCnt)
215 delete this;
216 }
219 NS_IMETHODIMP
220 nsPluginByteRangeStreamListener::OnDataAvailable(nsIRequest *request, nsISupports *ctxt,
221 nsIInputStream *inStr,
222 uint64_t sourceOffset,
223 uint32_t count)
224 {
225 if (!mStreamConverter)
226 return NS_ERROR_FAILURE;
228 nsCOMPtr<nsIStreamListener> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer);
229 if (!finalStreamListener)
230 return NS_ERROR_FAILURE;
232 return mStreamConverter->OnDataAvailable(request, ctxt, inStr, sourceOffset, count);
233 }
235 NS_IMETHODIMP
236 nsPluginByteRangeStreamListener::GetInterface(const nsIID& aIID, void** result)
237 {
238 // Forward interface requests to our parent
239 nsCOMPtr<nsIInterfaceRequestor> finalStreamListener = do_QueryReferent(mWeakPtrPluginStreamListenerPeer);
240 if (!finalStreamListener)
241 return NS_ERROR_FAILURE;
243 return finalStreamListener->GetInterface(aIID, result);
244 }
246 // nsPluginStreamListenerPeer
248 NS_IMPL_ISUPPORTS(nsPluginStreamListenerPeer,
249 nsIStreamListener,
250 nsIRequestObserver,
251 nsIHttpHeaderVisitor,
252 nsISupportsWeakReference,
253 nsIInterfaceRequestor,
254 nsIChannelEventSink)
256 nsPluginStreamListenerPeer::nsPluginStreamListenerPeer()
257 {
258 mStreamType = NP_NORMAL;
259 mStartBinding = false;
260 mAbort = false;
261 mRequestFailed = false;
263 mPendingRequests = 0;
264 mHaveFiredOnStartRequest = false;
265 mDataForwardToRequest = nullptr;
267 mSeekable = false;
268 mModified = 0;
269 mStreamOffset = 0;
270 mStreamComplete = 0;
271 }
273 nsPluginStreamListenerPeer::~nsPluginStreamListenerPeer()
274 {
275 #ifdef PLUGIN_LOGGING
276 PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL,
277 ("nsPluginStreamListenerPeer::dtor this=%p, url=%s\n",this, mURLSpec.get()));
278 #endif
280 if (mPStreamListener) {
281 mPStreamListener->SetStreamListenerPeer(nullptr);
282 }
284 // close FD of mFileCacheOutputStream if it's still open
285 // or we won't be able to remove the cache file
286 if (mFileCacheOutputStream)
287 mFileCacheOutputStream = nullptr;
289 delete mDataForwardToRequest;
291 if (mPluginInstance)
292 mPluginInstance->FileCachedStreamListeners()->RemoveElement(this);
293 }
295 // Called as a result of GetURL and PostURL, or by the host in the case of the
296 // initial plugin stream.
297 nsresult nsPluginStreamListenerPeer::Initialize(nsIURI *aURL,
298 nsNPAPIPluginInstance *aInstance,
299 nsNPAPIPluginStreamListener* aListener)
300 {
301 #ifdef PLUGIN_LOGGING
302 nsAutoCString urlSpec;
303 if (aURL != nullptr) aURL->GetSpec(urlSpec);
305 PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL,
306 ("nsPluginStreamListenerPeer::Initialize instance=%p, url=%s\n", aInstance, urlSpec.get()));
308 PR_LogFlush();
309 #endif
311 // Not gonna work out
312 if (!aInstance) {
313 return NS_ERROR_FAILURE;
314 }
316 mURL = aURL;
318 NS_ASSERTION(mPluginInstance == nullptr,
319 "nsPluginStreamListenerPeer::Initialize mPluginInstance != nullptr");
320 mPluginInstance = aInstance;
322 // If the plugin did not request this stream, e.g. the initial stream, we wont
323 // have a nsNPAPIPluginStreamListener yet - this will be handled by
324 // SetUpStreamListener
325 if (aListener) {
326 mPStreamListener = aListener;
327 mPStreamListener->SetStreamListenerPeer(this);
328 }
330 mPendingRequests = 1;
332 mDataForwardToRequest = new nsDataHashtable<nsUint32HashKey, uint32_t>();
334 return NS_OK;
335 }
337 // SetupPluginCacheFile is called if we have to save the stream to disk.
338 //
339 // These files will be deleted when the host is destroyed.
340 //
341 // TODO? What if we fill up the the dest dir?
342 nsresult
343 nsPluginStreamListenerPeer::SetupPluginCacheFile(nsIChannel* channel)
344 {
345 nsresult rv = NS_OK;
347 bool useExistingCacheFile = false;
348 nsRefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
350 // Look for an existing cache file for the URI.
351 nsTArray< nsRefPtr<nsNPAPIPluginInstance> > *instances = pluginHost->InstanceArray();
352 for (uint32_t i = 0; i < instances->Length(); i++) {
353 // most recent streams are at the end of list
354 nsTArray<nsPluginStreamListenerPeer*> *streamListeners = instances->ElementAt(i)->FileCachedStreamListeners();
355 for (int32_t i = streamListeners->Length() - 1; i >= 0; --i) {
356 nsPluginStreamListenerPeer *lp = streamListeners->ElementAt(i);
357 if (lp && lp->mLocalCachedFileHolder) {
358 useExistingCacheFile = lp->UseExistingPluginCacheFile(this);
359 if (useExistingCacheFile) {
360 mLocalCachedFileHolder = lp->mLocalCachedFileHolder;
361 break;
362 }
363 }
364 if (useExistingCacheFile)
365 break;
366 }
367 }
369 // Create a new cache file if one could not be found.
370 if (!useExistingCacheFile) {
371 nsCOMPtr<nsIFile> pluginTmp;
372 rv = nsPluginHost::GetPluginTempDir(getter_AddRefs(pluginTmp));
373 if (NS_FAILED(rv)) {
374 return rv;
375 }
377 // Get the filename from the channel
378 nsCOMPtr<nsIURI> uri;
379 rv = channel->GetURI(getter_AddRefs(uri));
380 if (NS_FAILED(rv)) return rv;
382 nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
383 if (!url)
384 return NS_ERROR_FAILURE;
386 nsAutoCString filename;
387 url->GetFileName(filename);
388 if (NS_FAILED(rv))
389 return rv;
391 // Create a file to save our stream into. Should we scramble the name?
392 filename.Insert(NS_LITERAL_CSTRING("plugin-"), 0);
393 rv = pluginTmp->AppendNative(filename);
394 if (NS_FAILED(rv))
395 return rv;
397 // Yes, make it unique.
398 rv = pluginTmp->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
399 if (NS_FAILED(rv))
400 return rv;
402 // create a file output stream to write to...
403 nsCOMPtr<nsIOutputStream> outstream;
404 rv = NS_NewLocalFileOutputStream(getter_AddRefs(mFileCacheOutputStream), pluginTmp, -1, 00600);
405 if (NS_FAILED(rv))
406 return rv;
408 // save the file.
409 mLocalCachedFileHolder = new CachedFileHolder(pluginTmp);
410 }
412 // add this listenerPeer to list of stream peers for this instance
413 mPluginInstance->FileCachedStreamListeners()->AppendElement(this);
415 return rv;
416 }
418 NS_IMETHODIMP
419 nsPluginStreamListenerPeer::OnStartRequest(nsIRequest *request,
420 nsISupports* aContext)
421 {
422 nsresult rv = NS_OK;
423 PROFILER_LABEL("nsPluginStreamListenerPeer", "OnStartRequest");
425 if (mRequests.IndexOfObject(GetBaseRequest(request)) == -1) {
426 NS_ASSERTION(mRequests.Count() == 0,
427 "Only our initial stream should be unknown!");
428 TrackRequest(request);
429 }
431 if (mHaveFiredOnStartRequest) {
432 return NS_OK;
433 }
435 mHaveFiredOnStartRequest = true;
437 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
438 NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
440 // deal with 404 (Not Found) HTTP response,
441 // just return, this causes the request to be ignored.
442 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
443 if (httpChannel) {
444 uint32_t responseCode = 0;
445 rv = httpChannel->GetResponseStatus(&responseCode);
446 if (NS_FAILED(rv)) {
447 // NPP_Notify() will be called from OnStopRequest
448 // in nsNPAPIPluginStreamListener::CleanUpStream
449 // return error will cancel this request
450 // ...and we also need to tell the plugin that
451 mRequestFailed = true;
452 return NS_ERROR_FAILURE;
453 }
455 if (responseCode > 206) { // not normal
456 uint32_t wantsAllNetworkStreams = 0;
458 // We don't always have an instance here already, but if we do, check
459 // to see if it wants all streams.
460 if (mPluginInstance) {
461 rv = mPluginInstance->GetValueFromPlugin(NPPVpluginWantsAllNetworkStreams,
462 &wantsAllNetworkStreams);
463 // If the call returned an error code make sure we still use our default value.
464 if (NS_FAILED(rv)) {
465 wantsAllNetworkStreams = 0;
466 }
467 }
469 if (!wantsAllNetworkStreams) {
470 mRequestFailed = true;
471 return NS_ERROR_FAILURE;
472 }
473 }
474 }
476 // Get the notification callbacks from the channel and save it as
477 // week ref we'll use it in nsPluginStreamInfo::RequestRead() when
478 // we'll create channel for byte range request.
479 nsCOMPtr<nsIInterfaceRequestor> callbacks;
480 channel->GetNotificationCallbacks(getter_AddRefs(callbacks));
481 if (callbacks)
482 mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks);
484 nsCOMPtr<nsILoadGroup> loadGroup;
485 channel->GetLoadGroup(getter_AddRefs(loadGroup));
486 if (loadGroup)
487 mWeakPtrChannelLoadGroup = do_GetWeakReference(loadGroup);
489 int64_t length;
490 rv = channel->GetContentLength(&length);
492 // it's possible for the server to not send a Content-Length.
493 // we should still work in this case.
494 if (NS_FAILED(rv) || length < 0 || length > UINT32_MAX) {
495 // check out if this is file channel
496 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(channel);
497 if (fileChannel) {
498 // file does not exist
499 mRequestFailed = true;
500 return NS_ERROR_FAILURE;
501 }
502 mLength = 0;
503 }
504 else {
505 mLength = uint32_t(length);
506 }
508 nsAutoCString aContentType; // XXX but we already got the type above!
509 rv = channel->GetContentType(aContentType);
510 if (NS_FAILED(rv))
511 return rv;
513 nsCOMPtr<nsIURI> aURL;
514 rv = channel->GetURI(getter_AddRefs(aURL));
515 if (NS_FAILED(rv))
516 return rv;
518 aURL->GetSpec(mURLSpec);
520 if (!aContentType.IsEmpty())
521 mContentType = aContentType;
523 #ifdef PLUGIN_LOGGING
524 PR_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NOISY,
525 ("nsPluginStreamListenerPeer::OnStartRequest this=%p request=%p mime=%s, url=%s\n",
526 this, request, aContentType.get(), mURLSpec.get()));
528 PR_LogFlush();
529 #endif
531 // Set up the stream listener...
532 rv = SetUpStreamListener(request, aURL);
533 if (NS_FAILED(rv)) return rv;
535 return rv;
536 }
538 NS_IMETHODIMP nsPluginStreamListenerPeer::OnProgress(nsIRequest *request,
539 nsISupports* aContext,
540 uint64_t aProgress,
541 uint64_t aProgressMax)
542 {
543 nsresult rv = NS_OK;
544 return rv;
545 }
547 NS_IMETHODIMP nsPluginStreamListenerPeer::OnStatus(nsIRequest *request,
548 nsISupports* aContext,
549 nsresult aStatus,
550 const char16_t* aStatusArg)
551 {
552 return NS_OK;
553 }
555 nsresult
556 nsPluginStreamListenerPeer::GetContentType(char** result)
557 {
558 *result = const_cast<char*>(mContentType.get());
559 return NS_OK;
560 }
563 nsresult
564 nsPluginStreamListenerPeer::IsSeekable(bool* result)
565 {
566 *result = mSeekable;
567 return NS_OK;
568 }
570 nsresult
571 nsPluginStreamListenerPeer::GetLength(uint32_t* result)
572 {
573 *result = mLength;
574 return NS_OK;
575 }
577 nsresult
578 nsPluginStreamListenerPeer::GetLastModified(uint32_t* result)
579 {
580 *result = mModified;
581 return NS_OK;
582 }
584 nsresult
585 nsPluginStreamListenerPeer::GetURL(const char** result)
586 {
587 *result = mURLSpec.get();
588 return NS_OK;
589 }
591 void
592 nsPluginStreamListenerPeer::MakeByteRangeString(NPByteRange* aRangeList, nsACString &rangeRequest,
593 int32_t *numRequests)
594 {
595 rangeRequest.Truncate();
596 *numRequests = 0;
597 //the string should look like this: bytes=500-700,601-999
598 if (!aRangeList)
599 return;
601 int32_t requestCnt = 0;
602 nsAutoCString string("bytes=");
604 for (NPByteRange * range = aRangeList; range != nullptr; range = range->next) {
605 // XXX zero length?
606 if (!range->length)
607 continue;
609 // XXX needs to be fixed for negative offsets
610 string.AppendInt(range->offset);
611 string.Append("-");
612 string.AppendInt(range->offset + range->length - 1);
613 if (range->next)
614 string += ",";
616 requestCnt++;
617 }
619 // get rid of possible trailing comma
620 string.Trim(",", false);
622 rangeRequest = string;
623 *numRequests = requestCnt;
624 return;
625 }
627 nsresult
628 nsPluginStreamListenerPeer::RequestRead(NPByteRange* rangeList)
629 {
630 nsAutoCString rangeString;
631 int32_t numRequests;
633 MakeByteRangeString(rangeList, rangeString, &numRequests);
635 if (numRequests == 0)
636 return NS_ERROR_FAILURE;
638 nsresult rv = NS_OK;
640 nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryReferent(mWeakPtrChannelCallbacks);
641 nsCOMPtr<nsILoadGroup> loadGroup = do_QueryReferent(mWeakPtrChannelLoadGroup);
642 nsCOMPtr<nsIChannel> channel;
643 rv = NS_NewChannel(getter_AddRefs(channel), mURL, nullptr, loadGroup, callbacks);
644 if (NS_FAILED(rv))
645 return rv;
647 nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel));
648 if (!httpChannel)
649 return NS_ERROR_FAILURE;
651 httpChannel->SetRequestHeader(NS_LITERAL_CSTRING("Range"), rangeString, false);
653 mAbort = true; // instruct old stream listener to cancel
654 // the request on the next ODA.
656 nsCOMPtr<nsIStreamListener> converter;
658 if (numRequests == 1) {
659 converter = this;
660 // set current stream offset equal to the first offset in the range list
661 // it will work for single byte range request
662 // for multy range we'll reset it in ODA
663 SetStreamOffset(rangeList->offset);
664 } else {
665 nsWeakPtr weakpeer =
666 do_GetWeakReference(static_cast<nsISupportsWeakReference*>(this));
667 nsPluginByteRangeStreamListener *brrListener =
668 new nsPluginByteRangeStreamListener(weakpeer);
669 if (brrListener)
670 converter = brrListener;
671 else
672 return NS_ERROR_OUT_OF_MEMORY;
673 }
675 mPendingRequests += numRequests;
677 nsCOMPtr<nsISupportsPRUint32> container = do_CreateInstance(NS_SUPPORTS_PRUINT32_CONTRACTID, &rv);
678 if (NS_FAILED(rv))
679 return rv;
680 rv = container->SetData(MAGIC_REQUEST_CONTEXT);
681 if (NS_FAILED(rv))
682 return rv;
684 rv = channel->AsyncOpen(converter, container);
685 if (NS_SUCCEEDED(rv))
686 TrackRequest(channel);
687 return rv;
688 }
690 nsresult
691 nsPluginStreamListenerPeer::GetStreamOffset(int32_t* result)
692 {
693 *result = mStreamOffset;
694 return NS_OK;
695 }
697 nsresult
698 nsPluginStreamListenerPeer::SetStreamOffset(int32_t value)
699 {
700 mStreamOffset = value;
701 return NS_OK;
702 }
704 nsresult nsPluginStreamListenerPeer::ServeStreamAsFile(nsIRequest *request,
705 nsISupports* aContext)
706 {
707 if (!mPluginInstance)
708 return NS_ERROR_FAILURE;
710 // mPluginInstance->Stop calls mPStreamListener->CleanUpStream(), so stream will be properly clean up
711 mPluginInstance->Stop();
712 mPluginInstance->Start();
713 nsRefPtr<nsPluginInstanceOwner> owner = mPluginInstance->GetOwner();
714 if (owner) {
715 NPWindow* window = nullptr;
716 owner->GetWindow(window);
717 #if (MOZ_WIDGET_GTK == 2) || defined(MOZ_WIDGET_QT)
718 // Should call GetPluginPort() here.
719 // This part is copied from nsPluginInstanceOwner::GetPluginPort().
720 nsCOMPtr<nsIWidget> widget;
721 ((nsPluginNativeWindow*)window)->GetPluginWidget(getter_AddRefs(widget));
722 if (widget) {
723 window->window = widget->GetNativeData(NS_NATIVE_PLUGIN_PORT);
724 }
725 #endif
726 owner->CallSetWindow();
727 }
729 mSeekable = false;
730 mPStreamListener->OnStartBinding(this);
731 mStreamOffset = 0;
733 // force the plugin to use stream as file
734 mStreamType = NP_ASFILE;
736 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
737 if (channel) {
738 SetupPluginCacheFile(channel);
739 }
741 // unset mPendingRequests
742 mPendingRequests = 0;
744 return NS_OK;
745 }
747 bool
748 nsPluginStreamListenerPeer::UseExistingPluginCacheFile(nsPluginStreamListenerPeer* psi)
749 {
750 NS_ENSURE_TRUE(psi, false);
752 if (psi->mLength == mLength &&
753 psi->mModified == mModified &&
754 mStreamComplete &&
755 mURLSpec.Equals(psi->mURLSpec))
756 {
757 return true;
758 }
759 return false;
760 }
762 NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable(nsIRequest *request,
763 nsISupports* aContext,
764 nsIInputStream *aIStream,
765 uint64_t sourceOffset,
766 uint32_t aLength)
767 {
768 if (mRequests.IndexOfObject(GetBaseRequest(request)) == -1) {
769 MOZ_ASSERT(false, "Received OnDataAvailable for untracked request.");
770 return NS_ERROR_UNEXPECTED;
771 }
773 if (mRequestFailed)
774 return NS_ERROR_FAILURE;
776 if (mAbort) {
777 uint32_t magicNumber = 0; // set it to something that is not the magic number.
778 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(aContext);
779 if (container)
780 container->GetData(&magicNumber);
782 if (magicNumber != MAGIC_REQUEST_CONTEXT) {
783 // this is not one of our range requests
784 mAbort = false;
785 return NS_BINDING_ABORTED;
786 }
787 }
789 nsresult rv = NS_OK;
791 if (!mPStreamListener)
792 return NS_ERROR_FAILURE;
794 const char * url = nullptr;
795 GetURL(&url);
797 PLUGIN_LOG(PLUGIN_LOG_NOISY,
798 ("nsPluginStreamListenerPeer::OnDataAvailable this=%p request=%p, offset=%llu, length=%u, url=%s\n",
799 this, request, sourceOffset, aLength, url ? url : "no url set"));
801 // if the plugin has requested an AsFileOnly stream, then don't
802 // call OnDataAvailable
803 if (mStreamType != NP_ASFILEONLY) {
804 // get the absolute offset of the request, if one exists.
805 nsCOMPtr<nsIByteRangeRequest> brr = do_QueryInterface(request);
806 if (brr) {
807 if (!mDataForwardToRequest)
808 return NS_ERROR_FAILURE;
810 int64_t absoluteOffset64 = 0;
811 brr->GetStartRange(&absoluteOffset64);
813 // XXX handle 64-bit for real
814 int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64);
816 // we need to track how much data we have forwarded to the
817 // plugin.
819 // FIXME: http://bugzilla.mozilla.org/show_bug.cgi?id=240130
820 //
821 // Why couldn't this be tracked on the plugin info, and not in a
822 // *hash table*?
823 int32_t amtForwardToPlugin = mDataForwardToRequest->Get(absoluteOffset);
824 mDataForwardToRequest->Put(absoluteOffset, (amtForwardToPlugin + aLength));
826 SetStreamOffset(absoluteOffset + amtForwardToPlugin);
827 }
829 nsCOMPtr<nsIInputStream> stream = aIStream;
831 // if we are caching the file ourselves to disk, we want to 'tee' off
832 // the data as the plugin read from the stream. We do this by the magic
833 // of an input stream tee.
835 if (mFileCacheOutputStream) {
836 rv = NS_NewInputStreamTee(getter_AddRefs(stream), aIStream, mFileCacheOutputStream);
837 if (NS_FAILED(rv))
838 return rv;
839 }
841 rv = mPStreamListener->OnDataAvailable(this,
842 stream,
843 aLength);
845 // if a plugin returns an error, the peer must kill the stream
846 // else the stream and PluginStreamListener leak
847 if (NS_FAILED(rv))
848 request->Cancel(rv);
849 }
850 else
851 {
852 // if we don't read from the stream, OnStopRequest will never be called
853 char* buffer = new char[aLength];
854 uint32_t amountRead, amountWrote = 0;
855 rv = aIStream->Read(buffer, aLength, &amountRead);
857 // if we are caching this to disk ourselves, lets write the bytes out.
858 if (mFileCacheOutputStream) {
859 while (amountWrote < amountRead && NS_SUCCEEDED(rv)) {
860 rv = mFileCacheOutputStream->Write(buffer, amountRead, &amountWrote);
861 }
862 }
863 delete [] buffer;
864 }
865 return rv;
866 }
868 NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest *request,
869 nsISupports* aContext,
870 nsresult aStatus)
871 {
872 nsresult rv = NS_OK;
874 nsCOMPtr<nsIMultiPartChannel> mp = do_QueryInterface(request);
875 if (!mp) {
876 bool found = mRequests.RemoveObject(request);
877 if (!found) {
878 NS_ERROR("Received OnStopRequest for untracked request.");
879 }
880 }
882 PLUGIN_LOG(PLUGIN_LOG_NOISY,
883 ("nsPluginStreamListenerPeer::OnStopRequest this=%p aStatus=%d request=%p\n",
884 this, aStatus, request));
886 // for ByteRangeRequest we're just updating the mDataForwardToRequest hash and return.
887 nsCOMPtr<nsIByteRangeRequest> brr = do_QueryInterface(request);
888 if (brr) {
889 int64_t absoluteOffset64 = 0;
890 brr->GetStartRange(&absoluteOffset64);
891 // XXX support 64-bit offsets
892 int32_t absoluteOffset = (int32_t)int64_t(absoluteOffset64);
894 // remove the request from our data forwarding count hash.
895 mDataForwardToRequest->Remove(absoluteOffset);
898 PLUGIN_LOG(PLUGIN_LOG_NOISY,
899 (" ::OnStopRequest for ByteRangeRequest Started=%d\n",
900 absoluteOffset));
901 } else {
902 // if this is not byte range request and
903 // if we are writting the stream to disk ourselves,
904 // close & tear it down here
905 mFileCacheOutputStream = nullptr;
906 }
908 // if we still have pending stuff to do, lets not close the plugin socket.
909 if (--mPendingRequests > 0)
910 return NS_OK;
912 // we keep our connections around...
913 nsCOMPtr<nsISupportsPRUint32> container = do_QueryInterface(aContext);
914 if (container) {
915 uint32_t magicNumber = 0; // set it to something that is not the magic number.
916 container->GetData(&magicNumber);
917 if (magicNumber == MAGIC_REQUEST_CONTEXT) {
918 // this is one of our range requests
919 return NS_OK;
920 }
921 }
923 if (!mPStreamListener)
924 return NS_ERROR_FAILURE;
926 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
927 if (!channel)
928 return NS_ERROR_FAILURE;
929 // Set the content type to ensure we don't pass null to the plugin
930 nsAutoCString aContentType;
931 rv = channel->GetContentType(aContentType);
932 if (NS_FAILED(rv) && !mRequestFailed)
933 return rv;
935 if (!aContentType.IsEmpty())
936 mContentType = aContentType;
938 // set error status if stream failed so we notify the plugin
939 if (mRequestFailed)
940 aStatus = NS_ERROR_FAILURE;
942 if (NS_FAILED(aStatus)) {
943 // on error status cleanup the stream
944 // and return w/o OnFileAvailable()
945 mPStreamListener->OnStopBinding(this, aStatus);
946 return NS_OK;
947 }
949 // call OnFileAvailable if plugin requests stream type StreamType_AsFile or StreamType_AsFileOnly
950 if (mStreamType >= NP_ASFILE) {
951 nsCOMPtr<nsIFile> localFile;
952 if (mLocalCachedFileHolder)
953 localFile = mLocalCachedFileHolder->file();
954 else {
955 // see if it is a file channel.
956 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(request);
957 if (fileChannel) {
958 fileChannel->GetFile(getter_AddRefs(localFile));
959 }
960 }
962 if (localFile) {
963 OnFileAvailable(localFile);
964 }
965 }
967 if (mStartBinding) {
968 // On start binding has been called
969 mPStreamListener->OnStopBinding(this, aStatus);
970 } else {
971 // OnStartBinding hasn't been called, so complete the action.
972 mPStreamListener->OnStartBinding(this);
973 mPStreamListener->OnStopBinding(this, aStatus);
974 }
976 if (NS_SUCCEEDED(aStatus)) {
977 mStreamComplete = true;
978 }
980 return NS_OK;
981 }
983 nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest *request,
984 nsIURI* aURL)
985 {
986 nsresult rv = NS_OK;
988 // If we don't yet have a stream listener, we need to get
989 // one from the plugin.
990 // NOTE: this should only happen when a stream was NOT created
991 // with GetURL or PostURL (i.e. it's the initial stream we
992 // send to the plugin as determined by the SRC or DATA attribute)
993 if (!mPStreamListener) {
994 if (!mPluginInstance) {
995 return NS_ERROR_FAILURE;
996 }
998 nsRefPtr<nsNPAPIPluginStreamListener> streamListener;
999 rv = mPluginInstance->NewStreamListener(nullptr, nullptr,
1000 getter_AddRefs(streamListener));
1001 if (NS_FAILED(rv) || !streamListener) {
1002 return NS_ERROR_FAILURE;
1003 }
1005 mPStreamListener = static_cast<nsNPAPIPluginStreamListener*>(streamListener.get());
1006 }
1008 mPStreamListener->SetStreamListenerPeer(this);
1010 bool useLocalCache = false;
1012 // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup
1013 nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
1014 nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(channel);
1016 /*
1017 * Assumption
1018 * By the time nsPluginStreamListenerPeer::OnDataAvailable() gets
1019 * called, all the headers have been read.
1020 */
1021 if (httpChannel) {
1022 // Reassemble the HTTP response status line and provide it to our
1023 // listener. Would be nice if we could get the raw status line,
1024 // but nsIHttpChannel doesn't currently provide that.
1025 // Status code: required; the status line isn't useful without it.
1026 uint32_t statusNum;
1027 if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&statusNum)) &&
1028 statusNum < 1000) {
1029 // HTTP version: provide if available. Defaults to empty string.
1030 nsCString ver;
1031 nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal =
1032 do_QueryInterface(channel);
1033 if (httpChannelInternal) {
1034 uint32_t major, minor;
1035 if (NS_SUCCEEDED(httpChannelInternal->GetResponseVersion(&major,
1036 &minor))) {
1037 ver = nsPrintfCString("/%lu.%lu", major, minor);
1038 }
1039 }
1041 // Status text: provide if available. Defaults to "OK".
1042 nsCString statusText;
1043 if (NS_FAILED(httpChannel->GetResponseStatusText(statusText))) {
1044 statusText = "OK";
1045 }
1047 // Assemble everything and pass to listener.
1048 nsPrintfCString status("HTTP%s %lu %s", ver.get(), statusNum,
1049 statusText.get());
1050 static_cast<nsIHTTPHeaderListener*>(mPStreamListener)->StatusLine(status.get());
1051 }
1053 // Also provide all HTTP response headers to our listener.
1054 httpChannel->VisitResponseHeaders(this);
1056 mSeekable = false;
1057 // first we look for a content-encoding header. If we find one, we tell the
1058 // plugin that stream is not seekable, because the plugin always sees
1059 // uncompressed data, so it can't make meaningful range requests on a
1060 // compressed entity. Also, we force the plugin to use
1061 // nsPluginStreamType_AsFile stream type and we have to save decompressed
1062 // file into local plugin cache, because necko cache contains original
1063 // compressed file.
1064 nsAutoCString contentEncoding;
1065 if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("Content-Encoding"),
1066 contentEncoding))) {
1067 useLocalCache = true;
1068 } else {
1069 // set seekability (seekable if the stream has a known length and if the
1070 // http server accepts byte ranges).
1071 uint32_t length;
1072 GetLength(&length);
1073 if (length) {
1074 nsAutoCString range;
1075 if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("accept-ranges"), range)) &&
1076 range.Equals(NS_LITERAL_CSTRING("bytes"), nsCaseInsensitiveCStringComparator())) {
1077 mSeekable = true;
1078 }
1079 }
1080 }
1082 // we require a content len
1083 // get Last-Modified header for plugin info
1084 nsAutoCString lastModified;
1085 if (NS_SUCCEEDED(httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("last-modified"), lastModified)) &&
1086 !lastModified.IsEmpty()) {
1087 PRTime time64;
1088 PR_ParseTimeString(lastModified.get(), true, &time64); //convert string time to integer time
1090 // Convert PRTime to unix-style time_t, i.e. seconds since the epoch
1091 double fpTime = double(time64);
1092 mModified = (uint32_t)(fpTime * 1e-6 + 0.5);
1093 }
1094 }
1096 rv = mPStreamListener->OnStartBinding(this);
1098 mStartBinding = true;
1100 if (NS_FAILED(rv))
1101 return rv;
1103 mPStreamListener->GetStreamType(&mStreamType);
1105 if (!useLocalCache && mStreamType >= NP_ASFILE) {
1106 // check it out if this is not a file channel.
1107 nsCOMPtr<nsIFileChannel> fileChannel = do_QueryInterface(request);
1108 if (!fileChannel) {
1109 useLocalCache = true;
1110 }
1111 }
1113 if (useLocalCache) {
1114 SetupPluginCacheFile(channel);
1115 }
1117 return NS_OK;
1118 }
1120 nsresult
1121 nsPluginStreamListenerPeer::OnFileAvailable(nsIFile* aFile)
1122 {
1123 nsresult rv;
1124 if (!mPStreamListener)
1125 return NS_ERROR_FAILURE;
1127 nsAutoCString path;
1128 rv = aFile->GetNativePath(path);
1129 if (NS_FAILED(rv)) return rv;
1131 if (path.IsEmpty()) {
1132 NS_WARNING("empty path");
1133 return NS_OK;
1134 }
1136 rv = mPStreamListener->OnFileAvailable(this, path.get());
1137 return rv;
1138 }
1140 NS_IMETHODIMP
1141 nsPluginStreamListenerPeer::VisitHeader(const nsACString &header, const nsACString &value)
1142 {
1143 return mPStreamListener->NewResponseHeader(PromiseFlatCString(header).get(),
1144 PromiseFlatCString(value).get());
1145 }
1147 nsresult
1148 nsPluginStreamListenerPeer::GetInterfaceGlobal(const nsIID& aIID, void** result)
1149 {
1150 if (!mPluginInstance) {
1151 return NS_ERROR_FAILURE;
1152 }
1154 nsRefPtr<nsPluginInstanceOwner> owner = mPluginInstance->GetOwner();
1155 if (owner) {
1156 nsCOMPtr<nsIDocument> doc;
1157 nsresult rv = owner->GetDocument(getter_AddRefs(doc));
1158 if (NS_SUCCEEDED(rv) && doc) {
1159 nsPIDOMWindow *window = doc->GetWindow();
1160 if (window) {
1161 nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(window);
1162 nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(webNav);
1163 return ir->GetInterface(aIID, result);
1164 }
1165 }
1166 }
1168 return NS_ERROR_FAILURE;
1169 }
1171 NS_IMETHODIMP
1172 nsPluginStreamListenerPeer::GetInterface(const nsIID& aIID, void** result)
1173 {
1174 // Provide nsIChannelEventSink ourselves, otherwise let our document's
1175 // script global object owner provide the interface.
1176 if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
1177 return QueryInterface(aIID, result);
1178 }
1180 return GetInterfaceGlobal(aIID, result);
1181 }
1183 /**
1184 * Proxy class which forwards async redirect notifications back to the necko
1185 * callback, keeping nsPluginStreamListenerPeer::mRequests in sync with
1186 * which channel is active.
1187 */
1188 class ChannelRedirectProxyCallback : public nsIAsyncVerifyRedirectCallback
1189 {
1190 public:
1191 ChannelRedirectProxyCallback(nsPluginStreamListenerPeer* listener,
1192 nsIAsyncVerifyRedirectCallback* parent,
1193 nsIChannel* oldChannel,
1194 nsIChannel* newChannel)
1195 : mWeakListener(do_GetWeakReference(static_cast<nsIStreamListener*>(listener)))
1196 , mParent(parent)
1197 , mOldChannel(oldChannel)
1198 , mNewChannel(newChannel)
1199 {
1200 }
1202 ChannelRedirectProxyCallback() {}
1203 virtual ~ChannelRedirectProxyCallback() {}
1205 NS_DECL_ISUPPORTS
1207 NS_IMETHODIMP OnRedirectVerifyCallback(nsresult result)
1208 {
1209 if (NS_SUCCEEDED(result)) {
1210 nsCOMPtr<nsIStreamListener> listener = do_QueryReferent(mWeakListener);
1211 if (listener)
1212 static_cast<nsPluginStreamListenerPeer*>(listener.get())->ReplaceRequest(mOldChannel, mNewChannel);
1213 }
1214 return mParent->OnRedirectVerifyCallback(result);
1215 }
1217 private:
1218 nsWeakPtr mWeakListener;
1219 nsCOMPtr<nsIAsyncVerifyRedirectCallback> mParent;
1220 nsCOMPtr<nsIChannel> mOldChannel;
1221 nsCOMPtr<nsIChannel> mNewChannel;
1222 };
1224 NS_IMPL_ISUPPORTS(ChannelRedirectProxyCallback, nsIAsyncVerifyRedirectCallback)
1227 NS_IMETHODIMP
1228 nsPluginStreamListenerPeer::AsyncOnChannelRedirect(nsIChannel *oldChannel, nsIChannel *newChannel,
1229 uint32_t flags, nsIAsyncVerifyRedirectCallback* callback)
1230 {
1231 // Disallow redirects if we don't have a stream listener.
1232 if (!mPStreamListener) {
1233 return NS_ERROR_FAILURE;
1234 }
1236 nsCOMPtr<nsIAsyncVerifyRedirectCallback> proxyCallback =
1237 new ChannelRedirectProxyCallback(this, callback, oldChannel, newChannel);
1239 // Give NPAPI a chance to control redirects.
1240 bool notificationHandled = mPStreamListener->HandleRedirectNotification(oldChannel, newChannel, proxyCallback);
1241 if (notificationHandled) {
1242 return NS_OK;
1243 }
1245 // Don't allow cross-origin 307 POST redirects.
1246 nsCOMPtr<nsIHttpChannel> oldHttpChannel(do_QueryInterface(oldChannel));
1247 if (oldHttpChannel) {
1248 uint32_t responseStatus;
1249 nsresult rv = oldHttpChannel->GetResponseStatus(&responseStatus);
1250 if (NS_FAILED(rv)) {
1251 return rv;
1252 }
1253 if (responseStatus == 307) {
1254 nsAutoCString method;
1255 rv = oldHttpChannel->GetRequestMethod(method);
1256 if (NS_FAILED(rv)) {
1257 return rv;
1258 }
1259 if (method.EqualsLiteral("POST")) {
1260 rv = nsContentUtils::CheckSameOrigin(oldChannel, newChannel);
1261 if (NS_FAILED(rv)) {
1262 return rv;
1263 }
1264 }
1265 }
1266 }
1268 // Fall back to channel event sink for window.
1269 nsCOMPtr<nsIChannelEventSink> channelEventSink;
1270 nsresult rv = GetInterfaceGlobal(NS_GET_IID(nsIChannelEventSink), getter_AddRefs(channelEventSink));
1271 if (NS_FAILED(rv)) {
1272 return rv;
1273 }
1275 return channelEventSink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, proxyCallback);
1276 }