/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsPluginStreamListenerPeer.h" #include "nsIContentPolicy.h" #include "nsContentPolicyUtils.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIFileChannel.h" #include "nsMimeTypes.h" #include "nsNetCID.h" #include "nsPluginInstanceOwner.h" #include "nsPluginLogging.h" #include "nsIURI.h" #include "nsPluginHost.h" #include "nsIMultiPartChannel.h" #include "nsPrintfCString.h" #include "nsIScriptGlobalObject.h" #include "mozilla/dom/Document.h" #include "nsIWebNavigation.h" #include "nsContentUtils.h" #include "nsNetUtil.h" #include "nsPluginNativeWindow.h" #include "GeckoProfiler.h" #include "nsPluginInstanceOwner.h" #include "nsDataHashtable.h" #include "mozilla/NullPrincipal.h" // nsPluginStreamListenerPeer NS_IMPL_ISUPPORTS(nsPluginStreamListenerPeer, nsIStreamListener, nsIRequestObserver, nsIHttpHeaderVisitor, nsISupportsWeakReference, nsIInterfaceRequestor, nsIChannelEventSink) nsPluginStreamListenerPeer::nsPluginStreamListenerPeer() : mLength(0) { mStreamType = NP_NORMAL; mStartBinding = false; mRequestFailed = false; mPendingRequests = 0; mHaveFiredOnStartRequest = false; mUseLocalCache = false; mModified = 0; mStreamOffset = 0; mStreamComplete = 0; } nsPluginStreamListenerPeer::~nsPluginStreamListenerPeer() { #ifdef PLUGIN_LOGGING MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, ("nsPluginStreamListenerPeer::dtor this=%p, url=%s\n", this, mURLSpec.get())); #endif if (mPStreamListener) { mPStreamListener->SetStreamListenerPeer(nullptr); } } // Called as a result of GetURL and PostURL, or by the host in the case of the // initial plugin stream. nsresult nsPluginStreamListenerPeer::Initialize( nsIURI* aURL, nsNPAPIPluginInstance* aInstance, nsNPAPIPluginStreamListener* aListener) { #ifdef PLUGIN_LOGGING MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NORMAL, ("nsPluginStreamListenerPeer::Initialize instance=%p, url=%s\n", aInstance, aURL ? aURL->GetSpecOrDefault().get() : "")); PR_LogFlush(); #endif // Not gonna work out if (!aInstance) { return NS_ERROR_FAILURE; } mURL = aURL; NS_ASSERTION( mPluginInstance == nullptr, "nsPluginStreamListenerPeer::Initialize mPluginInstance != nullptr"); mPluginInstance = aInstance; // If the plugin did not request this stream, e.g. the initial stream, we wont // have a nsNPAPIPluginStreamListener yet - this will be handled by // SetUpStreamListener if (aListener) { mPStreamListener = aListener; mPStreamListener->SetStreamListenerPeer(this); } mPendingRequests = 1; return NS_OK; } NS_IMETHODIMP nsPluginStreamListenerPeer::OnStartRequest(nsIRequest* request) { nsresult rv = NS_OK; AUTO_PROFILER_LABEL("nsPluginStreamListenerPeer::OnStartRequest", OTHER); if (mRequests.IndexOfObject(request) == -1) { NS_ASSERTION(mRequests.Count() == 0, "Only our initial stream should be unknown!"); TrackRequest(request); } if (mHaveFiredOnStartRequest) { return NS_OK; } mHaveFiredOnStartRequest = true; nsCOMPtr channel = do_QueryInterface(request); NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); // deal with 404 (Not Found) HTTP response, // just return, this causes the request to be ignored. nsCOMPtr httpChannel(do_QueryInterface(channel)); if (httpChannel) { uint32_t responseCode = 0; rv = httpChannel->GetResponseStatus(&responseCode); if (NS_FAILED(rv)) { // NPP_Notify() will be called from OnStopRequest // in nsNPAPIPluginStreamListener::CleanUpStream // return error will cancel this request // ...and we also need to tell the plugin that mRequestFailed = true; return NS_ERROR_FAILURE; } if (responseCode > 206) { // not normal uint32_t wantsAllNetworkStreams = 0; // We don't always have an instance here already, but if we do, check // to see if it wants all streams. if (mPluginInstance) { rv = mPluginInstance->GetValueFromPlugin( NPPVpluginWantsAllNetworkStreams, &wantsAllNetworkStreams); // If the call returned an error code make sure we still use our default // value. if (NS_FAILED(rv)) { wantsAllNetworkStreams = 0; } } if (!wantsAllNetworkStreams) { mRequestFailed = true; return NS_ERROR_FAILURE; } } } nsAutoCString contentType; rv = channel->GetContentType(contentType); if (NS_FAILED(rv)) return rv; // Check ShouldProcess with content policy nsCOMPtr loadInfo = channel->LoadInfo(); int16_t shouldLoad = nsIContentPolicy::ACCEPT; rv = NS_CheckContentProcessPolicy(mURL, loadInfo, contentType, &shouldLoad); if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { mRequestFailed = true; return NS_ERROR_CONTENT_BLOCKED; } // Get the notification callbacks from the channel and save it as // week ref we'll use it in nsPluginStreamInfo::RequestRead() when // we'll create channel for byte range request. nsCOMPtr callbacks; channel->GetNotificationCallbacks(getter_AddRefs(callbacks)); if (callbacks) mWeakPtrChannelCallbacks = do_GetWeakReference(callbacks); nsCOMPtr loadGroup; channel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) mWeakPtrChannelLoadGroup = do_GetWeakReference(loadGroup); int64_t length; rv = channel->GetContentLength(&length); // it's possible for the server to not send a Content-Length. // we should still work in this case. if (NS_FAILED(rv) || length < 0 || length > UINT32_MAX) { // check out if this is file channel nsCOMPtr fileChannel = do_QueryInterface(channel); if (fileChannel) { // file does not exist mRequestFailed = true; return NS_ERROR_FAILURE; } mLength = 0; } else { mLength = uint32_t(length); } nsCOMPtr aURL; rv = channel->GetURI(getter_AddRefs(aURL)); if (NS_FAILED(rv)) return rv; aURL->GetSpec(mURLSpec); if (!contentType.IsEmpty()) mContentType = contentType; #ifdef PLUGIN_LOGGING MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_NOISY, ("nsPluginStreamListenerPeer::OnStartRequest this=%p request=%p " "mime=%s, url=%s\n", this, request, contentType.get(), mURLSpec.get())); PR_LogFlush(); #endif // Set up the stream listener... rv = SetUpStreamListener(request, aURL); if (NS_FAILED(rv)) { return rv; } return rv; } NS_IMETHODIMP nsPluginStreamListenerPeer::OnProgress(nsIRequest* request, int64_t aProgress, int64_t aProgressMax) { nsresult rv = NS_OK; return rv; } NS_IMETHODIMP nsPluginStreamListenerPeer::OnStatus(nsIRequest* request, nsresult aStatus, const char16_t* aStatusArg) { return NS_OK; } nsresult nsPluginStreamListenerPeer::GetContentType(char** result) { *result = const_cast(mContentType.get()); return NS_OK; } nsresult nsPluginStreamListenerPeer::GetLength(uint32_t* result) { *result = mLength; return NS_OK; } nsresult nsPluginStreamListenerPeer::GetLastModified(uint32_t* result) { *result = mModified; return NS_OK; } nsresult nsPluginStreamListenerPeer::GetURL(const char** result) { *result = mURLSpec.get(); return NS_OK; } nsresult nsPluginStreamListenerPeer::GetStreamOffset(int32_t* result) { *result = mStreamOffset; return NS_OK; } nsresult nsPluginStreamListenerPeer::SetStreamOffset(int32_t value) { mStreamOffset = value; return NS_OK; } NS_IMETHODIMP nsPluginStreamListenerPeer::OnDataAvailable( nsIRequest* request, nsIInputStream* aIStream, uint64_t sourceOffset, uint32_t aLength) { if (mRequests.IndexOfObject(request) == -1) { MOZ_ASSERT(false, "Received OnDataAvailable for untracked request."); return NS_ERROR_UNEXPECTED; } if (mRequestFailed) return NS_ERROR_FAILURE; nsresult rv = NS_OK; if (!mPStreamListener) return NS_ERROR_FAILURE; const char* url = nullptr; GetURL(&url); PLUGIN_LOG(PLUGIN_LOG_NOISY, ("nsPluginStreamListenerPeer::OnDataAvailable this=%p request=%p, " "offset=%" PRIu64 ", length=%u, url=%s\n", this, request, sourceOffset, aLength, url ? url : "no url set")); nsCOMPtr stream = aIStream; rv = mPStreamListener->OnDataAvailable(this, stream, aLength); // if a plugin returns an error, the peer must kill the stream // else the stream and PluginStreamListener leak if (NS_FAILED(rv)) { request->Cancel(rv); } return rv; } NS_IMETHODIMP nsPluginStreamListenerPeer::OnStopRequest(nsIRequest* request, nsresult aStatus) { nsresult rv = NS_OK; nsCOMPtr mp = do_QueryInterface(request); if (!mp) { bool found = mRequests.RemoveObject(request); if (!found) { NS_ERROR("Received OnStopRequest for untracked request."); } } PLUGIN_LOG( PLUGIN_LOG_NOISY, ("nsPluginStreamListenerPeer::OnStopRequest this=%p aStatus=%" PRIu32 " request=%p\n", this, static_cast(aStatus), request)); // if we still have pending stuff to do, lets not close the plugin socket. if (--mPendingRequests > 0) return NS_OK; if (!mPStreamListener) return NS_ERROR_FAILURE; nsCOMPtr channel = do_QueryInterface(request); if (!channel) return NS_ERROR_FAILURE; // Set the content type to ensure we don't pass null to the plugin nsAutoCString aContentType; rv = channel->GetContentType(aContentType); if (NS_FAILED(rv) && !mRequestFailed) return rv; if (!aContentType.IsEmpty()) mContentType = aContentType; // set error status if stream failed so we notify the plugin if (mRequestFailed) aStatus = NS_ERROR_FAILURE; if (NS_FAILED(aStatus)) { // on error status cleanup the stream // and return w/o OnFileAvailable() mPStreamListener->OnStopBinding(this, aStatus); return NS_OK; } if (mStartBinding) { // On start binding has been called mPStreamListener->OnStopBinding(this, aStatus); } else { // OnStartBinding hasn't been called, so complete the action. mPStreamListener->OnStartBinding(this); mPStreamListener->OnStopBinding(this, aStatus); } if (NS_SUCCEEDED(aStatus)) { mStreamComplete = true; } return NS_OK; } nsresult nsPluginStreamListenerPeer::SetUpStreamListener(nsIRequest* request, nsIURI* aURL) { nsresult rv = NS_OK; // If we don't yet have a stream listener, we need to get // one from the plugin. // NOTE: this should only happen when a stream was NOT created // with GetURL or PostURL (i.e. it's the initial stream we // send to the plugin as determined by the SRC or DATA attribute) if (!mPStreamListener) { if (!mPluginInstance) { return NS_ERROR_FAILURE; } RefPtr streamListener; rv = mPluginInstance->NewStreamListener(nullptr, nullptr, getter_AddRefs(streamListener)); if (NS_FAILED(rv) || !streamListener) { return NS_ERROR_FAILURE; } mPStreamListener = static_cast(streamListener.get()); } mPStreamListener->SetStreamListenerPeer(this); // get httpChannel to retrieve some info we need for nsIPluginStreamInfo setup nsCOMPtr channel = do_QueryInterface(request); nsCOMPtr httpChannel = do_QueryInterface(channel); /* * Assumption * By the time nsPluginStreamListenerPeer::OnDataAvailable() gets * called, all the headers have been read. */ if (httpChannel) { // Reassemble the HTTP response status line and provide it to our // listener. Would be nice if we could get the raw status line, // but nsIHttpChannel doesn't currently provide that. // Status code: required; the status line isn't useful without it. uint32_t statusNum; if (NS_SUCCEEDED(httpChannel->GetResponseStatus(&statusNum)) && statusNum < 1000) { // HTTP version: provide if available. Defaults to empty string. nsCString ver; nsCOMPtr httpChannelInternal = do_QueryInterface(channel); if (httpChannelInternal) { uint32_t major, minor; if (NS_SUCCEEDED( httpChannelInternal->GetResponseVersion(&major, &minor))) { ver = nsPrintfCString("/%" PRIu32 ".%" PRIu32, major, minor); } } // Status text: provide if available. Defaults to "OK". nsCString statusText; if (NS_FAILED(httpChannel->GetResponseStatusText(statusText))) { statusText = "OK"; } // Assemble everything and pass to listener. nsPrintfCString status("HTTP%s %" PRIu32 " %s", ver.get(), statusNum, statusText.get()); static_cast(mPStreamListener) ->StatusLine(status.get()); } // Also provide all HTTP response headers to our listener. rv = httpChannel->VisitResponseHeaders(this); MOZ_ASSERT(NS_SUCCEEDED(rv)); // we require a content len // get Last-Modified header for plugin info nsAutoCString lastModified; if (NS_SUCCEEDED( httpChannel->GetResponseHeader("last-modified"_ns, lastModified)) && !lastModified.IsEmpty()) { PRTime time64; PR_ParseTimeString(lastModified.get(), true, &time64); // convert string time to integer time // Convert PRTime to unix-style time_t, i.e. seconds since the epoch double fpTime = double(time64); mModified = (uint32_t)(fpTime * 1e-6 + 0.5); } } MOZ_ASSERT(!mRequest); mRequest = request; rv = mPStreamListener->OnStartBinding(this); mStartBinding = true; if (NS_FAILED(rv)) return rv; return NS_OK; } NS_IMETHODIMP nsPluginStreamListenerPeer::VisitHeader(const nsACString& header, const nsACString& value) { return mPStreamListener->NewResponseHeader(PromiseFlatCString(header).get(), PromiseFlatCString(value).get()); } nsresult nsPluginStreamListenerPeer::GetInterfaceGlobal(const nsIID& aIID, void** result) { if (!mPluginInstance) { return NS_ERROR_FAILURE; } RefPtr owner = mPluginInstance->GetOwner(); if (!owner) { return NS_ERROR_FAILURE; } nsCOMPtr doc; nsresult rv = owner->GetDocument(getter_AddRefs(doc)); if (NS_FAILED(rv) || !doc) { return NS_ERROR_FAILURE; } nsPIDOMWindowOuter* window = doc->GetWindow(); if (!window) { return NS_ERROR_FAILURE; } nsCOMPtr webNav = do_GetInterface(window); nsCOMPtr ir = do_QueryInterface(webNav); if (!ir) { return NS_ERROR_FAILURE; } return ir->GetInterface(aIID, result); } NS_IMETHODIMP nsPluginStreamListenerPeer::GetInterface(const nsIID& aIID, void** result) { // Provide nsIChannelEventSink ourselves, otherwise let our document's // script global object owner provide the interface. if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) { return QueryInterface(aIID, result); } return GetInterfaceGlobal(aIID, result); } /** * Proxy class which forwards async redirect notifications back to the necko * callback, keeping nsPluginStreamListenerPeer::mRequests in sync with * which channel is active. */ class ChannelRedirectProxyCallback : public nsIAsyncVerifyRedirectCallback { public: ChannelRedirectProxyCallback(nsPluginStreamListenerPeer* listener, nsIAsyncVerifyRedirectCallback* parent, nsIChannel* oldChannel, nsIChannel* newChannel) : mWeakListener( do_GetWeakReference(static_cast(listener))), mParent(parent), mOldChannel(oldChannel), mNewChannel(newChannel) {} ChannelRedirectProxyCallback() = default; NS_DECL_ISUPPORTS NS_IMETHOD OnRedirectVerifyCallback(nsresult aResult) override { if (NS_SUCCEEDED(aResult)) { nsCOMPtr listener = do_QueryReferent(mWeakListener); if (listener) static_cast(listener.get()) ->ReplaceRequest(mOldChannel, mNewChannel); } return mParent->OnRedirectVerifyCallback(aResult); } private: virtual ~ChannelRedirectProxyCallback() = default; nsWeakPtr mWeakListener; nsCOMPtr mParent; nsCOMPtr mOldChannel; nsCOMPtr mNewChannel; }; NS_IMPL_ISUPPORTS(ChannelRedirectProxyCallback, nsIAsyncVerifyRedirectCallback) NS_IMETHODIMP nsPluginStreamListenerPeer::AsyncOnChannelRedirect( nsIChannel* oldChannel, nsIChannel* newChannel, uint32_t flags, nsIAsyncVerifyRedirectCallback* callback) { // Disallow redirects if we don't have a stream listener. if (!mPStreamListener) { return NS_ERROR_FAILURE; } // Don't allow cross-origin 307/308 POST redirects. nsCOMPtr oldHttpChannel(do_QueryInterface(oldChannel)); if (oldHttpChannel) { uint32_t responseStatus; nsresult rv = oldHttpChannel->GetResponseStatus(&responseStatus); if (NS_FAILED(rv)) { return rv; } if (responseStatus == 307 || responseStatus == 308) { nsAutoCString method; rv = oldHttpChannel->GetRequestMethod(method); if (NS_FAILED(rv)) { return rv; } if (method.EqualsLiteral("POST")) { rv = nsContentUtils::CheckSameOrigin(oldChannel, newChannel); if (NS_FAILED(rv)) { return rv; } } } } nsCOMPtr proxyCallback = new ChannelRedirectProxyCallback(this, callback, oldChannel, newChannel); // Give NPAPI a chance to control redirects. bool notificationHandled = mPStreamListener->HandleRedirectNotification( oldChannel, newChannel, proxyCallback); if (notificationHandled) { return NS_OK; } // Fall back to channel event sink for window. nsCOMPtr channelEventSink; nsresult rv = GetInterfaceGlobal(NS_GET_IID(nsIChannelEventSink), getter_AddRefs(channelEventSink)); if (NS_FAILED(rv)) { return rv; } return channelEventSink->AsyncOnChannelRedirect(oldChannel, newChannel, flags, proxyCallback); }