/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 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 "ExtensionProtocolHandler.h" #include "mozilla/ClearOnShutdown.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/ExtensionPolicyService.h" #include "mozilla/FileUtils.h" #include "mozilla/ipc/IPCStreamUtils.h" #include "mozilla/ipc/URIParams.h" #include "mozilla/ipc/URIUtils.h" #include "mozilla/net/NeckoChild.h" #include "mozilla/RefPtr.h" #include "mozilla/ResultExtensions.h" #include "FileDescriptor.h" #include "FileDescriptorFile.h" #include "LoadInfo.h" #include "nsContentUtils.h" #include "nsServiceManagerUtils.h" #include "nsDirectoryServiceDefs.h" #include "nsIFile.h" #include "nsIFileChannel.h" #include "nsIFileStreams.h" #include "nsIFileURL.h" #include "nsIJARChannel.h" #include "nsIMIMEService.h" #include "nsIURL.h" #include "nsIChannel.h" #include "nsIInputStreamPump.h" #include "nsIJARURI.h" #include "nsIStreamListener.h" #include "nsIThread.h" #include "nsIInputStream.h" #include "nsIOutputStream.h" #include "nsIStreamConverterService.h" #include "nsNetUtil.h" #include "prio.h" #include "SimpleChannel.h" #if defined(XP_WIN) #include "nsILocalFileWin.h" #include "WinUtils.h" #endif #define EXTENSION_SCHEME "moz-extension" using mozilla::ipc::FileDescriptor; using OptionalIPCStream = mozilla::ipc::OptionalIPCStream; namespace mozilla { namespace net { using extensions::URLInfo; LazyLogModule gExtProtocolLog("ExtProtocol"); #undef LOG #define LOG(...) MOZ_LOG(gExtProtocolLog, LogLevel::Debug, (__VA_ARGS__)) StaticRefPtr ExtensionProtocolHandler::sSingleton; /** * Helper class used with SimpleChannel to asynchronously obtain an input * stream or file descriptor from the parent for a remote moz-extension load * from the child. */ class ExtensionStreamGetter : public RefCounted { public: // To use when getting a remote input stream for a resource // in an unpacked extension. ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo) : mURI(aURI) , mLoadInfo(aLoadInfo) , mIsJarChannel(false) { MOZ_ASSERT(aURI); MOZ_ASSERT(aLoadInfo); SetupEventTarget(); } // To use when getting an FD for a packed extension JAR file // in order to load a resource. ExtensionStreamGetter(nsIURI* aURI, nsILoadInfo* aLoadInfo, already_AddRefed&& aJarChannel, nsIFile* aJarFile) : mURI(aURI) , mLoadInfo(aLoadInfo) , mJarChannel(Move(aJarChannel)) , mJarFile(aJarFile) , mIsJarChannel(true) { MOZ_ASSERT(aURI); MOZ_ASSERT(aLoadInfo); MOZ_ASSERT(mJarChannel); MOZ_ASSERT(aJarFile); SetupEventTarget(); } ~ExtensionStreamGetter() {} void SetupEventTarget() { mMainThreadEventTarget = nsContentUtils::GetEventTargetByLoadInfo(mLoadInfo, TaskCategory::Other); if (!mMainThreadEventTarget) { mMainThreadEventTarget = GetMainThreadSerialEventTarget(); } } // Get an input stream or file descriptor from the parent asynchronously. Result GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel); // Handle an input stream being returned from the parent void OnStream(nsIInputStream* aStream); // Handle file descriptor being returned from the parent void OnFD(const FileDescriptor& aFD); MOZ_DECLARE_REFCOUNTED_TYPENAME(ExtensionStreamGetter) private: nsCOMPtr mURI; nsCOMPtr mLoadInfo; nsCOMPtr mJarChannel; nsCOMPtr mJarFile; nsCOMPtr mListener; nsCOMPtr mChannel; nsCOMPtr mMainThreadEventTarget; bool mIsJarChannel; }; class ExtensionJARFileOpener : public nsISupports { public: ExtensionJARFileOpener(nsIFile* aFile, NeckoParent::GetExtensionFDResolver& aResolve) : mFile(aFile), mResolve(aResolve) { MOZ_ASSERT(aFile); MOZ_ASSERT(aResolve); } NS_IMETHOD OpenFile() { MOZ_ASSERT(!NS_IsMainThread()); AutoFDClose prFileDesc; #if defined(XP_WIN) nsresult rv; nsCOMPtr winFile = do_QueryInterface(mFile, &rv); MOZ_ASSERT(winFile); if (NS_SUCCEEDED(rv)) { rv = winFile->OpenNSPRFileDescShareDelete(PR_RDONLY, 0, &prFileDesc.rwget()); } #else nsresult rv = mFile->OpenNSPRFileDesc(PR_RDONLY, 0, &prFileDesc.rwget()); #endif /* XP_WIN */ if (NS_SUCCEEDED(rv)) { mFD = FileDescriptor(FileDescriptor::PlatformHandleType( PR_FileDesc2NativeHandle(prFileDesc))); } nsCOMPtr event = mozilla::NewRunnableMethod("ExtensionJarFileFDResolver", this, &ExtensionJARFileOpener::SendBackFD); rv = NS_DispatchToMainThread(event, nsIEventTarget::DISPATCH_NORMAL); NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread"); return NS_OK; } NS_IMETHOD SendBackFD() { MOZ_ASSERT(NS_IsMainThread()); mResolve(mFD); return NS_OK; } NS_DECL_THREADSAFE_ISUPPORTS private: virtual ~ExtensionJARFileOpener() {} nsCOMPtr mFile; NeckoParent::GetExtensionFDResolver mResolve; FileDescriptor mFD; }; NS_IMPL_ISUPPORTS(ExtensionJARFileOpener, nsISupports) // The amount of time, in milliseconds, that the file opener thread will remain // allocated after it is used. This value chosen because to match other uses // of LazyIdleThread. #define DEFAULT_THREAD_TIMEOUT_MS 30000 // Request an FD or input stream from the parent. Result ExtensionStreamGetter::GetAsync(nsIStreamListener* aListener, nsIChannel* aChannel) { MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(mMainThreadEventTarget); mListener = aListener; mChannel = aChannel; // Serialize the URI to send to parent mozilla::ipc::URIParams uri; SerializeURI(mURI, uri); RefPtr self = this; if (mIsJarChannel) { // Request an FD for this moz-extension URI gNeckoChild->SendGetExtensionFD(uri)->Then( mMainThreadEventTarget, __func__, [self] (const FileDescriptor& fd) { self->OnFD(fd); }, [self] (const mozilla::ipc::PromiseRejectReason) { self->OnFD(FileDescriptor()); } ); return Ok(); } // Request an input stream for this moz-extension URI gNeckoChild->SendGetExtensionStream(uri)->Then( mMainThreadEventTarget, __func__, [self] (const OptionalIPCStream& stream) { nsCOMPtr inputStream; if (stream.type() == OptionalIPCStream::OptionalIPCStream::TIPCStream) { inputStream = ipc::DeserializeIPCStream(stream); } self->OnStream(inputStream); }, [self] (const mozilla::ipc::PromiseRejectReason) { self->OnStream(nullptr); } ); return Ok(); } static void CancelRequest(nsIStreamListener* aListener, nsIChannel* aChannel, nsresult aResult) { MOZ_ASSERT(aListener); MOZ_ASSERT(aChannel); aListener->OnStartRequest(aChannel, nullptr); aListener->OnStopRequest(aChannel, nullptr, aResult); aChannel->Cancel(NS_BINDING_ABORTED); } // Handle an input stream sent from the parent. void ExtensionStreamGetter::OnStream(nsIInputStream* aStream) { MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(mListener); MOZ_ASSERT(mMainThreadEventTarget); // We must keep an owning reference to the listener // until we pass it on to AsyncRead. nsCOMPtr listener = mListener.forget(); MOZ_ASSERT(mChannel); if (!aStream) { // The parent didn't send us back a stream. CancelRequest(listener, mChannel, NS_ERROR_FILE_ACCESS_DENIED); return; } nsCOMPtr pump; nsresult rv = NS_NewInputStreamPump(getter_AddRefs(pump), aStream, 0, 0, false, mMainThreadEventTarget); if (NS_FAILED(rv)) { CancelRequest(listener, mChannel, rv); return; } rv = pump->AsyncRead(listener, nullptr); if (NS_FAILED(rv)) { CancelRequest(listener, mChannel, rv); } } // Handle an FD sent from the parent. void ExtensionStreamGetter::OnFD(const FileDescriptor& aFD) { MOZ_ASSERT(IsNeckoChild()); MOZ_ASSERT(mListener); MOZ_ASSERT(mChannel); if (!aFD.IsValid()) { OnStream(nullptr); return; } // We must keep an owning reference to the listener // until we pass it on to AsyncOpen2. nsCOMPtr listener = mListener.forget(); RefPtr fdFile = new FileDescriptorFile(aFD, mJarFile); mJarChannel->SetJarFile(fdFile); nsresult rv = mJarChannel->AsyncOpen2(listener); if (NS_FAILED(rv)) { CancelRequest(listener, mChannel, rv); } } NS_IMPL_QUERY_INTERFACE(ExtensionProtocolHandler, nsISubstitutingProtocolHandler, nsIProtocolHandler, nsIProtocolHandlerWithDynamicFlags, nsISupportsWeakReference) NS_IMPL_ADDREF_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler) NS_IMPL_RELEASE_INHERITED(ExtensionProtocolHandler, SubstitutingProtocolHandler) already_AddRefed ExtensionProtocolHandler::GetSingleton() { if (!sSingleton) { sSingleton = new ExtensionProtocolHandler(); ClearOnShutdown(&sSingleton); } return do_AddRef(sSingleton); } ExtensionProtocolHandler::ExtensionProtocolHandler() : SubstitutingProtocolHandler(EXTENSION_SCHEME) #if !defined(XP_WIN) #if defined(XP_MACOSX) , mAlreadyCheckedDevRepo(false) #endif /* XP_MACOSX */ , mAlreadyCheckedAppDir(false) #endif /* ! XP_WIN */ { // Note, extensions.webextensions.protocol.remote=false is for // debugging purposes only. With process-level sandboxing, child // processes (specifically content and extension processes), will // not be able to load most moz-extension URI's when the pref is // set to false. mUseRemoteFileChannels = IsNeckoChild() && Preferences::GetBool("extensions.webextensions.protocol.remote"); } static inline ExtensionPolicyService& EPS() { return ExtensionPolicyService::GetSingleton(); } nsresult ExtensionProtocolHandler::GetFlagsForURI(nsIURI* aURI, uint32_t* aFlags) { // In general a moz-extension URI is only loadable by chrome, but a whitelisted // subset are web-accessible (and cross-origin fetchable). Check that whitelist. bool loadableByAnyone = false; URLInfo url(aURI); if (auto* policy = EPS().GetByURL(url)) { loadableByAnyone = policy->IsPathWebAccessible(url.FilePath()); } *aFlags = URI_STD | URI_IS_LOCAL_RESOURCE | (loadableByAnyone ? (URI_LOADABLE_BY_ANYONE | URI_FETCHABLE_BY_ANYONE) : URI_DANGEROUS_TO_LOAD); return NS_OK; } bool ExtensionProtocolHandler::ResolveSpecialCases(const nsACString& aHost, const nsACString& aPath, const nsACString& aPathname, nsACString& aResult) { // Create special moz-extension:-pages such as moz-extension://foo/_blank.html // for all registered extensions. We can't just do this as a substitution // because substitutions can only match on host. if (!SubstitutingProtocolHandler::HasSubstitution(aHost)) { return false; } if (aPathname.EqualsLiteral("/_generated_background_page.html")) { Unused << EPS().GetGeneratedBackgroundPageUrl(aHost, aResult); return !aResult.IsEmpty(); } return false; } // For file or JAR URI's, substitute in a remote channel. Result ExtensionProtocolHandler::SubstituteRemoteChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** aRetVal) { MOZ_ASSERT(IsNeckoChild()); MOZ_TRY(aURI ? NS_OK : NS_ERROR_INVALID_ARG); MOZ_TRY(aLoadInfo ? NS_OK : NS_ERROR_INVALID_ARG); nsAutoCString unResolvedSpec; MOZ_TRY(aURI->GetSpec(unResolvedSpec)); nsAutoCString resolvedSpec; MOZ_TRY(ResolveURI(aURI, resolvedSpec)); // Use the target URI scheme to determine if this is a packed or unpacked // extension URI. For unpacked extensions, we'll request an input stream // from the parent. For a packed extension, we'll request a file descriptor // for the JAR file. nsAutoCString scheme; MOZ_TRY(net_ExtractURLScheme(resolvedSpec, scheme)); if (scheme.EqualsLiteral("file")) { // Unpacked extension SubstituteRemoteFileChannel(aURI, aLoadInfo, resolvedSpec, aRetVal); return Ok(); } if (scheme.EqualsLiteral("jar")) { // Packed extension return SubstituteRemoteJarChannel(aURI, aLoadInfo, resolvedSpec, aRetVal); } // Only unpacked resource files and JAR files are remoted. // No other moz-extension loads should be reading from the filesystem. return Ok(); } nsresult ExtensionProtocolHandler::SubstituteChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, nsIChannel** result) { nsresult rv; nsCOMPtr url = do_QueryInterface(aURI, &rv); NS_ENSURE_SUCCESS(rv, rv); if (mUseRemoteFileChannels) { MOZ_TRY(SubstituteRemoteChannel(aURI, aLoadInfo, result)); } nsAutoCString ext; rv = url->GetFileExtension(ext); NS_ENSURE_SUCCESS(rv, rv); if (!ext.LowerCaseEqualsLiteral("css")) { return NS_OK; } // Filter CSS files to replace locale message tokens with localized strings. bool haveLoadInfo = aLoadInfo; nsCOMPtr channel = NS_NewSimpleChannel( aURI, aLoadInfo, *result, [haveLoadInfo] (nsIStreamListener* listener, nsIChannel* channel, nsIChannel* origChannel) -> RequestOrReason { nsresult rv; nsCOMPtr convService = do_GetService(NS_STREAMCONVERTERSERVICE_CONTRACTID, &rv); MOZ_TRY(rv); nsCOMPtr uri; MOZ_TRY(channel->GetURI(getter_AddRefs(uri))); const char* kFromType = "application/vnd.mozilla.webext.unlocalized"; const char* kToType = "text/css"; nsCOMPtr converter; MOZ_TRY(convService->AsyncConvertData(kFromType, kToType, listener, uri, getter_AddRefs(converter))); if (haveLoadInfo) { MOZ_TRY(origChannel->AsyncOpen2(converter)); } else { MOZ_TRY(origChannel->AsyncOpen(converter, nullptr)); } return RequestOrReason(origChannel); }); NS_ENSURE_TRUE(channel, NS_ERROR_OUT_OF_MEMORY); if (aLoadInfo) { nsCOMPtr loadInfo = static_cast(aLoadInfo)->CloneForNewRequest(); (*result)->SetLoadInfo(loadInfo); } channel.swap(*result); return NS_OK; } Result ExtensionProtocolHandler::AllowExternalResource(nsIFile* aExtensionDir, nsIFile* aRequestedFile, bool* aResult) { MOZ_ASSERT(!IsNeckoChild()); MOZ_ASSERT(aResult); *aResult = false; #if defined(XP_WIN) // On Windows, dev builds don't use symlinks so we never need to // allow a resource from outside of the extension dir. return Ok(); #else if (!mozilla::IsDevelopmentBuild()) { return Ok(); } // On Mac and Linux unpackaged dev builds, system extensions use // symlinks to point to resources in the repo dir which we have to // allow loading. Before we allow an unpacked extension to load a // resource outside of the extension dir, we make sure the extension // dir is within the app directory. MOZ_TRY(AppDirContains(aExtensionDir, aResult)); if (!*aResult) { return Ok(); } #if defined(XP_MACOSX) // Additionally, on Mac dev builds, we make sure that the requested // resource is within the repo dir. We don't perform this check on Linux // because we don't have a reliable path to the repo dir on Linux. MOZ_TRY(DevRepoContains(aRequestedFile, aResult)); #endif /* XP_MACOSX */ return Ok(); #endif /* defined(XP_WIN) */ } #if defined(XP_MACOSX) // The |aRequestedFile| argument must already be Normalize()'d Result ExtensionProtocolHandler::DevRepoContains(nsIFile* aRequestedFile, bool* aResult) { MOZ_ASSERT(mozilla::IsDevelopmentBuild()); MOZ_ASSERT(!IsNeckoChild()); MOZ_ASSERT(aResult); *aResult = false; // On the first invocation, set mDevRepo if (!mAlreadyCheckedDevRepo) { mAlreadyCheckedDevRepo = true; MOZ_TRY(mozilla::GetRepoDir(getter_AddRefs(mDevRepo))); if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) { nsAutoCString repoPath; Unused << mDevRepo->GetNativePath(repoPath); LOG("Repo path: %s", repoPath.get()); } } if (mDevRepo) { MOZ_TRY(mDevRepo->Contains(aRequestedFile, aResult)); } return Ok(); } #endif /* XP_MACOSX */ #if !defined(XP_WIN) Result ExtensionProtocolHandler::AppDirContains(nsIFile* aExtensionDir, bool* aResult) { MOZ_ASSERT(mozilla::IsDevelopmentBuild()); MOZ_ASSERT(!IsNeckoChild()); MOZ_ASSERT(aResult); *aResult = false; // On the first invocation, set mAppDir if (!mAlreadyCheckedAppDir) { mAlreadyCheckedAppDir = true; MOZ_TRY(NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(mAppDir))); if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) { nsAutoCString appDirPath; Unused << mAppDir->GetNativePath(appDirPath); LOG("AppDir path: %s", appDirPath.get()); } } if (mAppDir) { MOZ_TRY(mAppDir->Contains(aExtensionDir, aResult)); } return Ok(); } #endif /* !defined(XP_WIN) */ static void LogExternalResourceError(nsIFile* aExtensionDir, nsIFile* aRequestedFile) { MOZ_ASSERT(aExtensionDir); MOZ_ASSERT(aRequestedFile); nsAutoCString extensionDirPath, requestedFilePath; Unused << aExtensionDir->GetNativePath(extensionDirPath); Unused << aRequestedFile->GetNativePath(requestedFilePath); LOG("Rejecting external unpacked extension resource [%s] from " "extension directory [%s]", requestedFilePath.get(), extensionDirPath.get()); } Result, nsresult> ExtensionProtocolHandler::NewStream(nsIURI* aChildURI, bool* aTerminateSender) { MOZ_ASSERT(!IsNeckoChild()); MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG); MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG); *aTerminateSender = true; nsresult rv; // We should never receive a URI that isn't for a moz-extension because // these requests ordinarily come from the child's ExtensionProtocolHandler. // Ensure this request is for a moz-extension URI. A rogue child process // could send us any URI. bool isExtScheme = false; if (NS_FAILED(aChildURI->SchemeIs(EXTENSION_SCHEME, &isExtScheme)) || !isExtScheme) { return Err(NS_ERROR_UNKNOWN_PROTOCOL); } // For errors after this point, we want to propagate the error to // the child, but we don't force the child to be terminated because // the error is likely to be due to a bug in the extension. *aTerminateSender = false; /* * Make sure there is a substitution installed for the host found * in the child's request URI and make sure the host resolves to * a directory. */ nsAutoCString host; MOZ_TRY(aChildURI->GetAsciiHost(host)); // Lookup the directory this host string resolves to nsCOMPtr baseURI; MOZ_TRY(GetSubstitution(host, getter_AddRefs(baseURI))); // The result should be a file URL for the extension base dir nsCOMPtr fileURL = do_QueryInterface(baseURI, &rv); MOZ_TRY(rv); nsCOMPtr extensionDir; MOZ_TRY(fileURL->GetFile(getter_AddRefs(extensionDir))); bool isDirectory = false; MOZ_TRY(extensionDir->IsDirectory(&isDirectory)); if (!isDirectory) { // The host should map to a directory for unpacked extensions return Err(NS_ERROR_FILE_NOT_DIRECTORY); } // Make sure the child URI resolves to a file URI then get a file // channel for the request. The resultant channel should be a // file channel because we only request remote streams for unpacked // extension resource loads where the URI resolves to a file. nsAutoCString resolvedSpec; MOZ_TRY(ResolveURI(aChildURI, resolvedSpec)); nsAutoCString resolvedScheme; MOZ_TRY(net_ExtractURLScheme(resolvedSpec, resolvedScheme)); if (!resolvedScheme.EqualsLiteral("file")) { return Err(NS_ERROR_UNEXPECTED); } nsCOMPtr ioService = do_GetIOService(&rv); MOZ_TRY(rv); nsCOMPtr resolvedURI; MOZ_TRY(ioService->NewURI(resolvedSpec, nullptr, nullptr, getter_AddRefs(resolvedURI))); // We use the system principal to get a file channel for the request, // but only after we've checked (above) that the child URI is of // moz-extension scheme and that the URI host maps to a directory. nsCOMPtr channel; MOZ_TRY(NS_NewChannel(getter_AddRefs(channel), resolvedURI, nsContentUtils::GetSystemPrincipal(), nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, nsIContentPolicy::TYPE_OTHER)); nsCOMPtr fileChannel = do_QueryInterface(channel, &rv); MOZ_TRY(rv); nsCOMPtr requestedFile; MOZ_TRY(fileChannel->GetFile(getter_AddRefs(requestedFile))); /* * Make sure the file we resolved to is within the extension directory. */ // Normalize paths for sane comparisons. nsIFile::Contains depends on // it for reliable subpath checks. MOZ_TRY(extensionDir->Normalize()); MOZ_TRY(requestedFile->Normalize()); #if defined(XP_WIN) if (!widget::WinUtils::ResolveJunctionPointsAndSymLinks(extensionDir) || !widget::WinUtils::ResolveJunctionPointsAndSymLinks(requestedFile)) { return Err(NS_ERROR_FILE_ACCESS_DENIED); } #endif bool isResourceFromExtensionDir = false; MOZ_TRY(extensionDir->Contains(requestedFile, &isResourceFromExtensionDir)); if (!isResourceFromExtensionDir) { bool isAllowed = false; MOZ_TRY(AllowExternalResource(extensionDir, requestedFile, &isAllowed)); if (!isAllowed) { LogExternalResourceError(extensionDir, requestedFile); return Err(NS_ERROR_FILE_ACCESS_DENIED); } } nsCOMPtr inputStream; MOZ_TRY(NS_NewLocalFileInputStream(getter_AddRefs(inputStream), requestedFile, PR_RDONLY, -1, nsIFileInputStream::DEFER_OPEN)); return inputStream; } Result ExtensionProtocolHandler::NewFD(nsIURI* aChildURI, bool* aTerminateSender, NeckoParent::GetExtensionFDResolver& aResolve) { MOZ_ASSERT(!IsNeckoChild()); MOZ_TRY(aChildURI ? NS_OK : NS_ERROR_INVALID_ARG); MOZ_TRY(aTerminateSender ? NS_OK : NS_ERROR_INVALID_ARG); *aTerminateSender = true; nsresult rv; // Ensure this is a moz-extension URI bool isExtScheme = false; if (NS_FAILED(aChildURI->SchemeIs(EXTENSION_SCHEME, &isExtScheme)) || !isExtScheme) { return Err(NS_ERROR_UNKNOWN_PROTOCOL); } // For errors after this point, we want to propagate the error to // the child, but we don't force the child to be terminated. *aTerminateSender = false; nsAutoCString host; MOZ_TRY(aChildURI->GetAsciiHost(host)); // We expect the host string to map to a JAR file because the URI // should refer to a web accessible resource for an enabled extension. nsCOMPtr subURI; MOZ_TRY(GetSubstitution(host, getter_AddRefs(subURI))); nsCOMPtr jarURI = do_QueryInterface(subURI, &rv); MOZ_TRY(rv); nsCOMPtr innerFileURI; MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI))); nsCOMPtr innerFileURL = do_QueryInterface(innerFileURI, &rv); MOZ_TRY(rv); nsCOMPtr jarFile; MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile))); if (!mFileOpenerThread) { mFileOpenerThread = new LazyIdleThread(DEFAULT_THREAD_TIMEOUT_MS, NS_LITERAL_CSTRING("ExtensionProtocolHandler")); } RefPtr fileOpener = new ExtensionJARFileOpener(jarFile, aResolve); nsCOMPtr event = mozilla::NewRunnableMethod("ExtensionJarFileOpener", fileOpener, &ExtensionJARFileOpener::OpenFile); MOZ_TRY(mFileOpenerThread->Dispatch(event, nsIEventTarget::DISPATCH_NORMAL)); return Ok(); } // Set the channel's content type using the provided URI's type void SetContentType(nsIURI* aURI, nsIChannel* aChannel) { nsresult rv; nsCOMPtr mime = do_GetService("@mozilla.org/mime;1", &rv); if (NS_SUCCEEDED(rv)) { nsAutoCString contentType; rv = mime->GetTypeFromURI(aURI, contentType); if (NS_SUCCEEDED(rv)) { Unused << aChannel->SetContentType(contentType); } } } // Gets a SimpleChannel that wraps the provided ExtensionStreamGetter static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, ExtensionStreamGetter* aStreamGetter, nsIChannel** aRetVal) { nsCOMPtr channel = NS_NewSimpleChannel( aURI, aLoadinfo, aStreamGetter, [] (nsIStreamListener* listener, nsIChannel* simpleChannel, ExtensionStreamGetter* getter) -> RequestOrReason { MOZ_TRY(getter->GetAsync(listener, simpleChannel)); return RequestOrReason(nullptr); }); SetContentType(aURI, channel); channel.swap(*aRetVal); } // Gets a SimpleChannel that wraps the provided channel static void NewSimpleChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, nsIChannel* aChannel, nsIChannel** aRetVal) { nsCOMPtr channel = NS_NewSimpleChannel(aURI, aLoadinfo, aChannel, [] (nsIStreamListener* listener, nsIChannel* simpleChannel, nsIChannel* origChannel) -> RequestOrReason { nsresult rv = origChannel->AsyncOpen2(listener); if (NS_FAILED(rv)) { simpleChannel->Cancel(NS_BINDING_ABORTED); return RequestOrReason(rv); } return RequestOrReason(origChannel); }); SetContentType(aURI, channel); channel.swap(*aRetVal); } void ExtensionProtocolHandler::SubstituteRemoteFileChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedFileSpec, nsIChannel** aRetVal) { MOZ_ASSERT(IsNeckoChild()); RefPtr streamGetter = new ExtensionStreamGetter(aURI, aLoadinfo); NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal); } static Result LogCacheCheck(const nsIJARChannel* aJarChannel, nsIJARURI* aJarURI, bool aIsCached) { nsresult rv; nsCOMPtr innerFileURI; MOZ_TRY(aJarURI->GetJARFile(getter_AddRefs(innerFileURI))); nsCOMPtr innerFileURL = do_QueryInterface(innerFileURI, &rv); MOZ_TRY(rv); nsCOMPtr jarFile; MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile))); nsAutoCString uriSpec, jarSpec; Unused << aJarURI->GetSpec(uriSpec); Unused << innerFileURI->GetSpec(jarSpec); LOG("[JARChannel %p] Cache %s: %s (%s)", aJarChannel, aIsCached ? "hit" : "miss", uriSpec.get(), jarSpec.get()); return Ok(); } Result ExtensionProtocolHandler::SubstituteRemoteJarChannel(nsIURI* aURI, nsILoadInfo* aLoadinfo, nsACString& aResolvedSpec, nsIChannel** aRetVal) { MOZ_ASSERT(IsNeckoChild()); nsresult rv; // Build a JAR URI for this jar:file:// URI and use it to extract the // inner file URI. nsCOMPtr uri; MOZ_TRY(NS_NewURI(getter_AddRefs(uri), aResolvedSpec)); nsCOMPtr jarURI = do_QueryInterface(uri, &rv); MOZ_TRY(rv); nsCOMPtr jarChannel = do_QueryInterface(*aRetVal, &rv); MOZ_TRY(rv); bool isCached = false; MOZ_TRY(jarChannel->EnsureCached(&isCached)); if (MOZ_LOG_TEST(gExtProtocolLog, LogLevel::Debug)) { Unused << LogCacheCheck(jarChannel, jarURI, isCached); } if (isCached) { // Using a SimpleChannel with an ExtensionStreamGetter here (like the // non-cached JAR case) isn't needed to load the extension resource // because we don't need to ask the parent for an FD for the JAR, but // wrapping the JARChannel in a SimpleChannel allows HTTP forwarding to // moz-extension URI's to work because HTTP forwarding requires the // target channel implement nsIChildChannel. NewSimpleChannel(aURI, aLoadinfo, jarChannel.get(), aRetVal); return Ok(); } nsCOMPtr innerFileURI; MOZ_TRY(jarURI->GetJARFile(getter_AddRefs(innerFileURI))); nsCOMPtr innerFileURL = do_QueryInterface(innerFileURI, &rv); MOZ_TRY(rv); nsCOMPtr jarFile; MOZ_TRY(innerFileURL->GetFile(getter_AddRefs(jarFile))); RefPtr streamGetter = new ExtensionStreamGetter(aURI, aLoadinfo, jarChannel.forget(), jarFile); NewSimpleChannel(aURI, aLoadinfo, streamGetter, aRetVal); return Ok(); } } // namespace net } // namespace mozilla