/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set sw=2 ts=8 et tw=80 : */ /* 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 "RemoteOpenFileChild.h" #include "mozilla/unused.h" #include "mozilla/ipc/FileDescriptor.h" #include "mozilla/ipc/FileDescriptorUtils.h" #include "mozilla/ipc/URIUtils.h" #include "mozilla/net/NeckoChild.h" #include "nsThreadUtils.h" #include "nsJARProtocolHandler.h" #include "nsIRemoteOpenFileListener.h" #include "nsProxyRelease.h" #include "SerializedLoadContext.h" // needed to alloc/free NSPR file descriptors #include "private/pprio.h" using namespace mozilla::ipc; namespace mozilla { namespace net { //----------------------------------------------------------------------------- // Helper class to dispatch events async on windows/OSX //----------------------------------------------------------------------------- class CallsListenerInNewEvent : public nsRunnable { public: CallsListenerInNewEvent(nsIRemoteOpenFileListener *aListener, nsresult aRv) : mListener(aListener), mRV(aRv) { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); } void Dispatch() { MOZ_ASSERT(NS_IsMainThread()); nsresult rv = NS_DispatchToCurrentThread(this); NS_ENSURE_SUCCESS_VOID(rv); } private: NS_IMETHOD Run() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mListener); mListener->OnRemoteFileOpenComplete(mRV); return NS_OK; } nsCOMPtr mListener; nsresult mRV; }; //----------------------------------------------------------------------------- // RemoteOpenFileChild //----------------------------------------------------------------------------- NS_IMPL_ISUPPORTS(RemoteOpenFileChild, nsIFile, nsIHashable, nsICachedFileDescriptorListener) RemoteOpenFileChild::RemoteOpenFileChild(const RemoteOpenFileChild& other) : mTabChild(other.mTabChild) , mNSPRFileDesc(other.mNSPRFileDesc) , mAsyncOpenCalled(other.mAsyncOpenCalled) , mNSPROpenCalled(other.mNSPROpenCalled) { // Note: don't clone mListener or we'll have a refcount leak. other.mURI->Clone(getter_AddRefs(mURI)); if (other.mAppURI) { other.mAppURI->Clone(getter_AddRefs(mAppURI)); } other.mFile->Clone(getter_AddRefs(mFile)); } RemoteOpenFileChild::~RemoteOpenFileChild() { if (NS_IsMainThread()) { if (mListener) { NotifyListener(NS_ERROR_UNEXPECTED); } } else { nsCOMPtr mainThread = do_GetMainThread(); if (mainThread) { MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProxyRelease(mainThread, mURI, true))); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProxyRelease(mainThread, mAppURI, true))); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_ProxyRelease(mainThread, mListener, true))); TabChild* tabChild; mTabChild.forget(&tabChild); if (tabChild) { nsCOMPtr runnable = NS_NewNonOwningRunnableMethod(tabChild, &TabChild::Release); MOZ_ASSERT(runnable); MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mainThread->Dispatch(runnable, NS_DISPATCH_NORMAL))); } } else { using mozilla::unused; NS_WARNING("RemoteOpenFileChild released after thread shutdown, leaking " "its members!"); unused << mURI.forget(); unused << mAppURI.forget(); unused << mListener.forget(); unused << mTabChild.forget(); } } if (mNSPRFileDesc) { // If we handed out fd we shouldn't have pointer to it any more. MOZ_ASSERT(!mNSPROpenCalled); // PR_Close both closes the file and deallocates the PRFileDesc PR_Close(mNSPRFileDesc); } } nsresult RemoteOpenFileChild::Init(nsIURI* aRemoteOpenUri, nsIURI* aAppUri) { if (!aRemoteOpenUri) { return NS_ERROR_INVALID_ARG; } if (aAppUri) { aAppUri->Clone(getter_AddRefs(mAppURI)); } nsAutoCString scheme; nsresult rv = aRemoteOpenUri->GetScheme(scheme); NS_ENSURE_SUCCESS(rv, rv); if (!scheme.EqualsLiteral("remoteopenfile")) { return NS_ERROR_INVALID_ARG; } // scheme of URI is not file:// so this is not a nsIFileURL. Convert to one. nsCOMPtr clonedURI; rv = aRemoteOpenUri->Clone(getter_AddRefs(clonedURI)); NS_ENSURE_SUCCESS(rv, rv); clonedURI->SetScheme(NS_LITERAL_CSTRING("file")); nsAutoCString spec; clonedURI->GetSpec(spec); rv = NS_NewURI(getter_AddRefs(mURI), spec); NS_ENSURE_SUCCESS(rv, rv); // Get nsIFile nsCOMPtr fileURL = do_QueryInterface(mURI); if (!fileURL) { return NS_ERROR_UNEXPECTED; } rv = fileURL->GetFile(getter_AddRefs(mFile)); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult RemoteOpenFileChild::AsyncRemoteFileOpen(int32_t aFlags, nsIRemoteOpenFileListener* aListener, nsITabChild* aTabChild, nsILoadContext *aLoadContext) { if (!mFile) { return NS_ERROR_NOT_INITIALIZED; } if (!aListener) { return NS_ERROR_INVALID_ARG; } if (mAsyncOpenCalled) { return NS_ERROR_ALREADY_OPENED; } if (aFlags != PR_RDONLY) { return NS_ERROR_NOT_AVAILABLE; } mTabChild = static_cast(aTabChild); if (MissingRequiredTabChild(mTabChild, "remoteopenfile")) { return NS_ERROR_ILLEGAL_VALUE; } #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) // Windows/OSX desktop builds skip remoting, and just open file in child // process when asked for NSPR handle nsRefPtr runnable = new CallsListenerInNewEvent(aListener, NS_OK); runnable->Dispatch(); mAsyncOpenCalled = true; return NS_OK; #else nsString path; if (NS_FAILED(mFile->GetPath(path))) { MOZ_CRASH("Couldn't get path from file!"); } if (mTabChild) { if (mTabChild->GetCachedFileDescriptor(path, this)) { // The file descriptor was found in the cache and OnCachedFileDescriptor() // will be called with it. return NS_OK; } } URIParams uri; SerializeURI(mURI, uri); OptionalURIParams appUri; SerializeURI(mAppURI, appUri); IPC::SerializedLoadContext loadContext(aLoadContext); gNeckoChild->SendPRemoteOpenFileConstructor(this, loadContext, uri, appUri); // The chrome process now has a logical ref to us until it calls Send__delete. AddIPDLReference(); mListener = aListener; mAsyncOpenCalled = true; return NS_OK; #endif } void RemoteOpenFileChild::OnCachedFileDescriptor(const nsAString& aPath, const FileDescriptor& aFD) { #ifdef DEBUG if (!aPath.IsEmpty()) { MOZ_ASSERT(mFile); nsString path; MOZ_ASSERT(NS_SUCCEEDED(mFile->GetPath(path))); MOZ_ASSERT(path == aPath, "Paths don't match!"); } #endif HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvDelete */ false); } void RemoteOpenFileChild::HandleFileDescriptorAndNotifyListener( const FileDescriptor& aFD, bool aFromRecvDelete) { #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) MOZ_CRASH("OS X and Windows shouldn't be doing IPDL here"); #else if (!mListener) { // We already notified our listener (either in response to a cached file // descriptor callback or through the normal messaging mechanism). Close the // file descriptor if it is valid. if (aFD.IsValid()) { nsRefPtr runnable = new CloseFileRunnable(aFD); runnable->Dispatch(); } return; } MOZ_ASSERT(!mNSPRFileDesc); nsRefPtr tabChild; mTabChild.swap(tabChild); // If RemoteOpenFile reply (Recv__delete__) for app's application.zip comes // back sooner than the parent-pushed fd (TabChild::RecvCacheFileDescriptor()) // have TabChild cancel running callbacks, since we'll call them in // NotifyListener. if (tabChild && aFromRecvDelete) { nsString path; if (NS_FAILED(mFile->GetPath(path))) { MOZ_CRASH("Couldn't get path from file!"); } tabChild->CancelCachedFileDescriptorCallback(path, this); } if (aFD.IsValid()) { mNSPRFileDesc = PR_ImportFile(aFD.PlatformHandle()); if (!mNSPRFileDesc) { NS_WARNING("Failed to import file handle!"); } } NotifyListener(mNSPRFileDesc ? NS_OK : NS_ERROR_FILE_NOT_FOUND); #endif } void RemoteOpenFileChild::NotifyListener(nsresult aResult) { MOZ_ASSERT(mListener); mListener->OnRemoteFileOpenComplete(aResult); mListener = nullptr; // release ref to listener nsRefPtr handler(gJarHandler); NS_WARN_IF_FALSE(handler, "nsJARProtocolHandler is already gone!"); if (handler) { handler->RemoteOpenFileComplete(this, aResult); } } //----------------------------------------------------------------------------- // RemoteOpenFileChild::PRemoteOpenFileChild //----------------------------------------------------------------------------- bool RemoteOpenFileChild::Recv__delete__(const FileDescriptor& aFD) { #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) NS_NOTREACHED("OS X and Windows shouldn't be doing IPDL here"); #else HandleFileDescriptorAndNotifyListener(aFD, /* aFromRecvDelete */ true); #endif return true; } //----------------------------------------------------------------------------- // RemoteOpenFileChild::nsIFile functions that we override logic for //----------------------------------------------------------------------------- NS_IMETHODIMP RemoteOpenFileChild::Clone(nsIFile **file) { *file = new RemoteOpenFileChild(*this); NS_ADDREF(*file); // if we transferred ownership of file to clone, forget our pointer. if (mNSPRFileDesc) { mNSPRFileDesc = nullptr; } return NS_OK; } /* The main event: get file descriptor from parent process */ NS_IMETHODIMP RemoteOpenFileChild::OpenNSPRFileDesc(int32_t aFlags, int32_t aMode, PRFileDesc **aRetval) { #if defined(XP_WIN) || defined(MOZ_WIDGET_COCOA) // Windows and OSX builds: just open nsIFile locally. return mFile->OpenNSPRFileDesc(aFlags, aMode, aRetval); #else if (aFlags != PR_RDONLY) { return NS_ERROR_NOT_AVAILABLE; } // Unlike regular nsIFile we can't (easily) support multiple open()s. if (mNSPROpenCalled) { return NS_ERROR_ALREADY_OPENED; } if (!mNSPRFileDesc) { // client skipped AsyncRemoteFileOpen() or didn't wait for result, or this // object has been cloned return NS_ERROR_NOT_AVAILABLE; } // hand off ownership (i.e responsibility to PR_Close() file handle) to caller *aRetval = mNSPRFileDesc; mNSPRFileDesc = nullptr; mNSPROpenCalled = true; return NS_OK; #endif } //----------------------------------------------------------------------------- // RemoteOpenFileChild::nsIFile functions that we delegate to underlying nsIFile //----------------------------------------------------------------------------- nsresult RemoteOpenFileChild::GetLeafName(nsAString &aLeafName) { return mFile->GetLeafName(aLeafName); } NS_IMETHODIMP RemoteOpenFileChild::GetNativeLeafName(nsACString &aLeafName) { return mFile->GetNativeLeafName(aLeafName); } nsresult RemoteOpenFileChild::GetTarget(nsAString &_retval) { return mFile->GetTarget(_retval); } NS_IMETHODIMP RemoteOpenFileChild::GetNativeTarget(nsACString &_retval) { return mFile->GetNativeTarget(_retval); } nsresult RemoteOpenFileChild::GetPath(nsAString &_retval) { return mFile->GetPath(_retval); } NS_IMETHODIMP RemoteOpenFileChild::GetNativePath(nsACString &_retval) { return mFile->GetNativePath(_retval); } NS_IMETHODIMP RemoteOpenFileChild::Equals(nsIFile *inFile, bool *_retval) { return mFile->Equals(inFile, _retval); } NS_IMETHODIMP RemoteOpenFileChild::Contains(nsIFile *inFile, bool *_retval) { return mFile->Contains(inFile, _retval); } NS_IMETHODIMP RemoteOpenFileChild::GetParent(nsIFile **aParent) { return mFile->GetParent(aParent); } NS_IMETHODIMP RemoteOpenFileChild::GetFollowLinks(bool *aFollowLinks) { return mFile->GetFollowLinks(aFollowLinks); } //----------------------------------------------------------------------------- // RemoteOpenFileChild::nsIFile functions that are not currently supported //----------------------------------------------------------------------------- nsresult RemoteOpenFileChild::Append(const nsAString &node) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::AppendNative(const nsACString &fragment) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Normalize() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Create(uint32_t type, uint32_t permissions) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult RemoteOpenFileChild::SetLeafName(const nsAString &aLeafName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetNativeLeafName(const nsACString &aLeafName) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult RemoteOpenFileChild::InitWithPath(const nsAString &filePath) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::InitWithNativePath(const nsACString &filePath) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::InitWithFile(nsIFile *aFile) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetFollowLinks(bool aFollowLinks) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult RemoteOpenFileChild::AppendRelativePath(const nsAString &node) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::AppendRelativeNativePath(const nsACString &fragment) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetPersistentDescriptor(nsACString &aPersistentDescriptor) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetPersistentDescriptor(const nsACString &aPersistentDescriptor) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetRelativeDescriptor(nsIFile *fromFile, nsACString& _retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetRelativeDescriptor(nsIFile *fromFile, const nsACString& relativeDesc) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult RemoteOpenFileChild::CopyTo(nsIFile *newParentDir, const nsAString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::CopyToNative(nsIFile *newParent, const nsACString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult RemoteOpenFileChild::CopyToFollowingLinks(nsIFile *newParentDir, const nsAString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::CopyToFollowingLinksNative(nsIFile *newParent, const nsACString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } nsresult RemoteOpenFileChild::MoveTo(nsIFile *newParentDir, const nsAString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::MoveToNative(nsIFile *newParent, const nsACString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::RenameTo(nsIFile *newParentDir, const nsAString &newName) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Remove(bool recursive) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetPermissions(uint32_t *aPermissions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetPermissions(uint32_t aPermissions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetPermissionsOfLink(uint32_t *aPermissionsOfLink) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetPermissionsOfLink(uint32_t aPermissions) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetLastModifiedTime(PRTime *aLastModTime) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetLastModifiedTime(PRTime aLastModTime) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetLastModifiedTimeOfLink(PRTime *aLastModTimeOfLink) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetLastModifiedTimeOfLink(PRTime aLastModTimeOfLink) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetFileSize(int64_t *aFileSize) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::SetFileSize(int64_t aFileSize) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetFileSizeOfLink(int64_t *aFileSize) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Exists(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsWritable(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsReadable(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsExecutable(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsHidden(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsDirectory(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsFile(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsSymlink(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::IsSpecial(bool *_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::CreateUnique(uint32_t type, uint32_t attributes) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetDirectoryEntries(nsISimpleEnumerator **entries) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::OpenANSIFileDesc(const char *mode, FILE **_retval) { // TODO: can implement using fdopen()? return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Load(PRLibrary **_retval) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::GetDiskSpaceAvailable(int64_t *aDiskSpaceAvailable) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Reveal() { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP RemoteOpenFileChild::Launch() { return NS_ERROR_NOT_IMPLEMENTED; } //----------------------------------------------------------------------------- // RemoteOpenFileChild::nsIHashable functions that we delegate to underlying nsIFile //----------------------------------------------------------------------------- NS_IMETHODIMP RemoteOpenFileChild::Equals(nsIHashable* aOther, bool *aResult) { nsCOMPtr hashable = do_QueryInterface(mFile); MOZ_ASSERT(hashable); if (hashable) { return hashable->Equals(aOther, aResult); } return NS_ERROR_UNEXPECTED; } NS_IMETHODIMP RemoteOpenFileChild::GetHashCode(uint32_t *aResult) { nsCOMPtr hashable = do_QueryInterface(mFile); MOZ_ASSERT(hashable); if (hashable) { return hashable->GetHashCode(aResult); } return NS_ERROR_UNEXPECTED; } } // namespace net } // namespace mozilla