diff options
Diffstat (limited to 'dom/base/nsHostObjectProtocolHandler.cpp')
-rw-r--r-- | dom/base/nsHostObjectProtocolHandler.cpp | 1087 |
1 files changed, 1087 insertions, 0 deletions
diff --git a/dom/base/nsHostObjectProtocolHandler.cpp b/dom/base/nsHostObjectProtocolHandler.cpp new file mode 100644 index 000000000..f2fdfc530 --- /dev/null +++ b/dom/base/nsHostObjectProtocolHandler.cpp @@ -0,0 +1,1087 @@ +/* -*- 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 "nsHostObjectProtocolHandler.h" + +#include "DOMMediaStream.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/File.h" +#include "mozilla/dom/ipc/BlobChild.h" +#include "mozilla/dom/ipc/BlobParent.h" +#include "mozilla/dom/MediaSource.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/ModuleUtils.h" +#include "mozilla/Preferences.h" +#include "nsClassHashtable.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsHostObjectURI.h" +#include "nsIMemoryReporter.h" +#include "nsIPrincipal.h" +#include "nsIUUIDGenerator.h" +#include "nsNetUtil.h" + +#define RELEASING_TIMER 1000 + +using mozilla::DOMMediaStream; +using mozilla::dom::BlobImpl; +using mozilla::dom::MediaSource; +using mozilla::ErrorResult; +using mozilla::net::LoadInfo; +using mozilla::Move; +using mozilla::Unused; + +// ----------------------------------------------------------------------- +// Hash table +struct DataInfo +{ + enum ObjectType { + eBlobImpl, + eMediaStream, + eMediaSource + }; + + DataInfo(BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal) + : mObjectType(eBlobImpl) + , mBlobImpl(aBlobImpl) + , mPrincipal(aPrincipal) + {} + + DataInfo(DOMMediaStream* aMediaStream, nsIPrincipal* aPrincipal) + : mObjectType(eMediaStream) + , mMediaStream(aMediaStream) + , mPrincipal(aPrincipal) + {} + + DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal) + : mObjectType(eMediaSource) + , mMediaSource(aMediaSource) + , mPrincipal(aPrincipal) + {} + + ObjectType mObjectType; + + RefPtr<BlobImpl> mBlobImpl; + RefPtr<DOMMediaStream> mMediaStream; + RefPtr<MediaSource> mMediaSource; + + nsCOMPtr<nsIPrincipal> mPrincipal; + nsCString mStack; + + // WeakReferences of nsHostObjectURI objects. + nsTArray<nsWeakPtr> mURIs; +}; + +static nsClassHashtable<nsCStringHashKey, DataInfo>* gDataTable; + +static DataInfo* +GetDataInfo(const nsACString& aUri) +{ + if (!gDataTable) { + return nullptr; + } + + DataInfo* res; + + // Let's remove any fragment and query from this URI. + int32_t hasFragmentPos = aUri.FindChar('#'); + int32_t hasQueryPos = aUri.FindChar('?'); + + int32_t pos = -1; + if (hasFragmentPos >= 0 && hasQueryPos >= 0) { + pos = std::min(hasFragmentPos, hasQueryPos); + } else if (hasFragmentPos >= 0) { + pos = hasFragmentPos; + } else { + pos = hasQueryPos; + } + + if (pos < 0) { + gDataTable->Get(aUri, &res); + } else { + gDataTable->Get(StringHead(aUri, pos), &res); + } + + return res; +} + +static DataInfo* +GetDataInfoFromURI(nsIURI* aURI) +{ + if (!aURI) { + return nullptr; + } + + nsCString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return GetDataInfo(spec); +} + +// Memory reporting for the hash table. +namespace mozilla { + +void +BroadcastBlobURLRegistration(const nsACString& aURI, + BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBlobImpl); + + if (XRE_IsParentProcess()) { + dom::ContentParent::BroadcastBlobURLRegistration(aURI, aBlobImpl, + aPrincipal); + return; + } + + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + dom::BlobChild* actor = cc->GetOrCreateActorForBlobImpl(aBlobImpl); + if (NS_WARN_IF(!actor)) { + return; + } + + Unused << NS_WARN_IF(!cc->SendStoreAndBroadcastBlobURLRegistration( + nsCString(aURI), actor, IPC::Principal(aPrincipal))); +} + +void +BroadcastBlobURLUnregistration(const nsACString& aURI, DataInfo* aInfo) +{ + MOZ_ASSERT(aInfo); + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_IsParentProcess()) { + dom::ContentParent::BroadcastBlobURLUnregistration(aURI); + return; + } + + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + Unused << NS_WARN_IF(!cc->SendUnstoreAndBroadcastBlobURLUnregistration( + nsCString(aURI))); +} + +class HostObjectURLsReporter final : public nsIMemoryReporter +{ + ~HostObjectURLsReporter() {} + + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, + nsISupports* aData, bool aAnonymize) override + { + MOZ_COLLECT_REPORT( + "host-object-urls", KIND_OTHER, UNITS_COUNT, + gDataTable ? gDataTable->Count() : 0, + "The number of host objects stored for access via URLs " + "(e.g. blobs passed to URL.createObjectURL)."); + + return NS_OK; + } +}; + +NS_IMPL_ISUPPORTS(HostObjectURLsReporter, nsIMemoryReporter) + +class BlobURLsReporter final : public nsIMemoryReporter +{ + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback, + nsISupports* aData, bool aAnonymize) override + { + if (!gDataTable) { + return NS_OK; + } + + nsDataHashtable<nsPtrHashKey<BlobImpl>, uint32_t> refCounts; + + // Determine number of URLs per BlobImpl, to handle the case where it's > 1. + for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) { + if (iter.UserData()->mObjectType != DataInfo::eBlobImpl) { + continue; + } + + BlobImpl* blobImpl = iter.UserData()->mBlobImpl; + MOZ_ASSERT(blobImpl); + + refCounts.Put(blobImpl, refCounts.Get(blobImpl) + 1); + } + + for (auto iter = gDataTable->Iter(); !iter.Done(); iter.Next()) { + nsCStringHashKey::KeyType key = iter.Key(); + DataInfo* info = iter.UserData(); + + if (iter.UserData()->mObjectType == DataInfo::eBlobImpl) { + BlobImpl* blobImpl = iter.UserData()->mBlobImpl; + MOZ_ASSERT(blobImpl); + + NS_NAMED_LITERAL_CSTRING(desc, + "A blob URL allocated with URL.createObjectURL; the referenced " + "blob cannot be freed until all URLs for it have been explicitly " + "invalidated with URL.revokeObjectURL."); + nsAutoCString path, url, owner, specialDesc; + uint64_t size = 0; + uint32_t refCount = 1; + DebugOnly<bool> blobImplWasCounted; + + blobImplWasCounted = refCounts.Get(blobImpl, &refCount); + MOZ_ASSERT(blobImplWasCounted); + MOZ_ASSERT(refCount > 0); + + bool isMemoryFile = blobImpl->IsMemoryFile(); + + if (isMemoryFile) { + ErrorResult rv; + size = blobImpl->GetSize(rv); + if (NS_WARN_IF(rv.Failed())) { + rv.SuppressException(); + size = 0; + } + } + + path = isMemoryFile ? "memory-blob-urls/" : "file-blob-urls/"; + BuildPath(path, key, info, aAnonymize); + + if (refCount > 1) { + nsAutoCString addrStr; + + addrStr = "0x"; + addrStr.AppendInt((uint64_t)(BlobImpl*)blobImpl, 16); + + path += " "; + path.AppendInt(refCount); + path += "@"; + path += addrStr; + + specialDesc = desc; + specialDesc += "\n\nNOTE: This blob (address "; + specialDesc += addrStr; + specialDesc += ") has "; + specialDesc.AppendInt(refCount); + specialDesc += " URLs."; + if (isMemoryFile) { + specialDesc += " Its size is divided "; + specialDesc += refCount > 2 ? "among" : "between"; + specialDesc += " them in this report."; + } + } + + const nsACString& descString = specialDesc.IsEmpty() + ? static_cast<const nsACString&>(desc) + : static_cast<const nsACString&>(specialDesc); + if (isMemoryFile) { + aCallback->Callback(EmptyCString(), + path, + KIND_OTHER, + UNITS_BYTES, + size / refCount, + descString, + aData); + } else { + aCallback->Callback(EmptyCString(), + path, + KIND_OTHER, + UNITS_COUNT, + 1, + descString, + aData); + } + continue; + } + + // Just report the path for the DOMMediaStream or MediaSource. + nsAutoCString path; + path = iter.UserData()->mObjectType == DataInfo::eMediaSource + ? "media-source-urls/" : "dom-media-stream-urls/"; + BuildPath(path, key, info, aAnonymize); + + NS_NAMED_LITERAL_CSTRING(desc, + "An object URL allocated with URL.createObjectURL; the referenced " + "data cannot be freed until all URLs for it have been explicitly " + "invalidated with URL.revokeObjectURL."); + + aCallback->Callback(EmptyCString(), path, KIND_OTHER, UNITS_COUNT, 1, + desc, aData); + } + + return NS_OK; + } + + // Initialize info->mStack to record JS stack info, if enabled. + // The string generated here is used in ReportCallback, below. + static void GetJSStackForBlob(DataInfo* aInfo) + { + nsCString& stack = aInfo->mStack; + MOZ_ASSERT(stack.IsEmpty()); + const uint32_t maxFrames = Preferences::GetUint("memory.blob_report.stack_frames"); + + if (maxFrames == 0) { + return; + } + + nsCOMPtr<nsIStackFrame> frame = dom::GetCurrentJSStack(maxFrames); + + nsAutoCString origin; + nsCOMPtr<nsIURI> principalURI; + if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) + && principalURI) { + principalURI->GetPrePath(origin); + } + + // If we got a frame, we better have a current JSContext. This is cheating + // a bit; ideally we'd have our caller pass in a JSContext, or have + // GetCurrentJSStack() hand out the JSContext it found. + JSContext* cx = frame ? nsContentUtils::GetCurrentJSContext() : nullptr; + + for (uint32_t i = 0; frame; ++i) { + nsString fileNameUTF16; + int32_t lineNumber = 0; + + frame->GetFilename(cx, fileNameUTF16); + frame->GetLineNumber(cx, &lineNumber); + + if (!fileNameUTF16.IsEmpty()) { + NS_ConvertUTF16toUTF8 fileName(fileNameUTF16); + stack += "js("; + if (!origin.IsEmpty()) { + // Make the file name root-relative for conciseness if possible. + const char* originData; + uint32_t originLen; + + originLen = origin.GetData(&originData); + // If fileName starts with origin + "/", cut up to that "/". + if (fileName.Length() >= originLen + 1 && + memcmp(fileName.get(), originData, originLen) == 0 && + fileName[originLen] == '/') { + fileName.Cut(0, originLen); + } + } + fileName.ReplaceChar('/', '\\'); + stack += fileName; + if (lineNumber > 0) { + stack += ", line="; + stack.AppendInt(lineNumber); + } + stack += ")/"; + } + + nsCOMPtr<nsIStackFrame> caller; + nsresult rv = frame->GetCaller(cx, getter_AddRefs(caller)); + NS_ENSURE_SUCCESS_VOID(rv); + caller.swap(frame); + } + } + + private: + ~BlobURLsReporter() {} + + static void BuildPath(nsAutoCString& path, + nsCStringHashKey::KeyType aKey, + DataInfo* aInfo, + bool anonymize) + { + nsCOMPtr<nsIURI> principalURI; + nsAutoCString url, owner; + if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) && + principalURI != nullptr && + NS_SUCCEEDED(principalURI->GetSpec(owner)) && + !owner.IsEmpty()) { + owner.ReplaceChar('/', '\\'); + path += "owner("; + if (anonymize) { + path += "<anonymized>"; + } else { + path += owner; + } + path += ")"; + } else { + path += "owner unknown"; + } + path += "/"; + if (anonymize) { + path += "<anonymized-stack>"; + } else { + path += aInfo->mStack; + } + url = aKey; + url.ReplaceChar('/', '\\'); + if (anonymize) { + path += "<anonymized-url>"; + } else { + path += url; + } + } +}; + +NS_IMPL_ISUPPORTS(BlobURLsReporter, nsIMemoryReporter) + +class ReleasingTimerHolder final : public nsITimerCallback +{ +public: + NS_DECL_ISUPPORTS + + static void + Create(nsTArray<nsWeakPtr>&& aArray) + { + RefPtr<ReleasingTimerHolder> holder = new ReleasingTimerHolder(Move(aArray)); + holder->mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + + // If we are shutting down, we are not able to create a timer. + if (!holder->mTimer) { + return; + } + + nsresult rv = holder->mTimer->InitWithCallback(holder, RELEASING_TIMER, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS_VOID(rv); + } + + NS_IMETHOD + Notify(nsITimer* aTimer) override + { + for (uint32_t i = 0; i < mURIs.Length(); ++i) { + nsCOMPtr<nsIURI> uri = do_QueryReferent(mURIs[i]); + if (uri) { + static_cast<nsHostObjectURI*>(uri.get())->ForgetBlobImpl(); + } + } + + return NS_OK; + } + +private: + explicit ReleasingTimerHolder(nsTArray<nsWeakPtr>&& aArray) + : mURIs(aArray) + {} + + ~ReleasingTimerHolder() + {} + + nsTArray<nsWeakPtr> mURIs; + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(ReleasingTimerHolder, nsITimerCallback) + +} // namespace mozilla + +template<typename T> +static nsresult +AddDataEntryInternal(const nsACString& aURI, T aObject, + nsIPrincipal* aPrincipal) +{ + if (!gDataTable) { + gDataTable = new nsClassHashtable<nsCStringHashKey, DataInfo>; + } + + DataInfo* info = new DataInfo(aObject, aPrincipal); + mozilla::BlobURLsReporter::GetJSStackForBlob(info); + + gDataTable->Put(aURI, info); + return NS_OK; +} + +void +nsHostObjectProtocolHandler::Init(void) +{ + static bool initialized = false; + + if (!initialized) { + initialized = true; + RegisterStrongMemoryReporter(new mozilla::HostObjectURLsReporter()); + RegisterStrongMemoryReporter(new mozilla::BlobURLsReporter()); + } +} + +nsHostObjectProtocolHandler::nsHostObjectProtocolHandler() +{ + Init(); +} + +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + Init(); + + nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddDataEntryInternal(aUri, aBlobImpl, aPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal); + return NS_OK; +} + +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(DOMMediaStream* aMediaStream, + nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + Init(); + + nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddDataEntryInternal(aUri, aMediaStream, aPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(MediaSource* aMediaSource, + nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + Init(); + + nsresult rv = GenerateURIStringForBlobURL(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddDataEntryInternal(aUri, aMediaSource, aPrincipal); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +/* static */ nsresult +nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aURI, + nsIPrincipal* aPrincipal, + mozilla::dom::BlobImpl* aBlobImpl) +{ + return AddDataEntryInternal(aURI, aBlobImpl, aPrincipal); +} + +/* static */ bool +nsHostObjectProtocolHandler::GetAllBlobURLEntries( + nsTArray<mozilla::dom::BlobURLRegistrationData>& aRegistrations, + mozilla::dom::ContentParent* aCP) +{ + MOZ_ASSERT(aCP); + + if (!gDataTable) { + return true; + } + + for (auto iter = gDataTable->ConstIter(); !iter.Done(); iter.Next()) { + DataInfo* info = iter.UserData(); + MOZ_ASSERT(info); + + if (info->mObjectType != DataInfo::eBlobImpl) { + continue; + } + + MOZ_ASSERT(info->mBlobImpl); + mozilla::dom::PBlobParent* blobParent = + aCP->GetOrCreateActorForBlobImpl(info->mBlobImpl); + if (!blobParent) { + return false; + } + + aRegistrations.AppendElement(mozilla::dom::BlobURLRegistrationData( + nsCString(iter.Key()), blobParent, nullptr, + IPC::Principal(info->mPrincipal))); + } + + return true; +} + +/*static */ void +nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri, + bool aBroadcastToOtherProcesses) +{ + if (!gDataTable) { + return; + } + + DataInfo* info = GetDataInfo(aUri); + if (!info) { + return; + } + + if (aBroadcastToOtherProcesses && info->mObjectType == DataInfo::eBlobImpl) { + mozilla::BroadcastBlobURLUnregistration(aUri, info); + } + + if (!info->mURIs.IsEmpty()) { + mozilla::ReleasingTimerHolder::Create(Move(info->mURIs)); + } + + gDataTable->Remove(aUri); + if (gDataTable->Count() == 0) { + delete gDataTable; + gDataTable = nullptr; + } +} + +/* static */ void +nsHostObjectProtocolHandler::RemoveDataEntries() +{ + MOZ_ASSERT(XRE_IsContentProcess()); + + if (!gDataTable) { + return; + } + + gDataTable->Clear(); + delete gDataTable; + gDataTable = nullptr; +} + +/* static */ bool +nsHostObjectProtocolHandler::HasDataEntry(const nsACString& aUri) +{ + return !!GetDataInfo(aUri); +} + +/* static */ nsresult +nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme, + nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + nsresult rv; + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsID id; + rv = uuidgen->GenerateUUIDInPlace(&id); + NS_ENSURE_SUCCESS(rv, rv); + + char chars[NSID_LENGTH]; + id.ToProvidedString(chars); + + aUri = aScheme; + aUri.Append(':'); + + if (aPrincipal) { + nsAutoCString origin; + rv = nsContentUtils::GetASCIIOrigin(aPrincipal, origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + aUri.Append(origin); + aUri.Append('/'); + } + + aUri += Substring(chars + 1, chars + NSID_LENGTH - 2); + + return NS_OK; +} + +/* static */ nsresult +nsHostObjectProtocolHandler::GenerateURIStringForBlobURL(nsIPrincipal* aPrincipal, + nsACString& aUri) +{ + return + GenerateURIString(NS_LITERAL_CSTRING(BLOBURI_SCHEME), aPrincipal, aUri); +} + +/* static */ nsIPrincipal* +nsHostObjectProtocolHandler::GetDataEntryPrincipal(const nsACString& aUri) +{ + if (!gDataTable) { + return nullptr; + } + + DataInfo* res = GetDataInfo(aUri); + + if (!res) { + return nullptr; + } + + return res->mPrincipal; +} + +/* static */ void +nsHostObjectProtocolHandler::Traverse(const nsACString& aUri, + nsCycleCollectionTraversalCallback& aCallback) +{ + if (!gDataTable) { + return; + } + + DataInfo* res; + gDataTable->Get(aUri, &res); + if (!res) { + return; + } + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mBlobImpl"); + aCallback.NoteXPCOMChild(res->mBlobImpl); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaSource"); + aCallback.NoteXPCOMChild(res->mMediaSource); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mMediaStream"); + aCallback.NoteXPCOMChild(res->mMediaStream); +} + +// ----------------------------------------------------------------------- +// Protocol handler + +NS_IMPL_ISUPPORTS(nsHostObjectProtocolHandler, nsIProtocolHandler) + +NS_IMETHODIMP +nsHostObjectProtocolHandler::GetDefaultPort(int32_t *result) +{ + *result = -1; + return NS_OK; +} + +NS_IMETHODIMP +nsHostObjectProtocolHandler::GetProtocolFlags(uint32_t *result) +{ + *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_SUBSUMERS | + URI_NON_PERSISTABLE; + return NS_OK; +} + +NS_IMETHODIMP +nsHostObjectProtocolHandler::GetFlagsForURI(nsIURI *aURI, uint32_t *aResult) +{ + Unused << nsHostObjectProtocolHandler::GetProtocolFlags(aResult); + if (IsFontTableURI(aURI) || IsBlobURI(aURI)) { + *aResult |= URI_IS_LOCAL_RESOURCE; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBlobProtocolHandler::GetProtocolFlags(uint32_t *result) +{ + Unused << nsHostObjectProtocolHandler::GetProtocolFlags(result); + *result |= URI_IS_LOCAL_RESOURCE; + return NS_OK; +} + +NS_IMETHODIMP +nsHostObjectProtocolHandler::NewURI(const nsACString& aSpec, + const char *aCharset, + nsIURI *aBaseURI, + nsIURI **aResult) +{ + *aResult = nullptr; + nsresult rv; + + DataInfo* info = GetDataInfo(aSpec); + + RefPtr<nsHostObjectURI> uri; + if (info && info->mObjectType == DataInfo::eBlobImpl) { + MOZ_ASSERT(info->mBlobImpl); + uri = new nsHostObjectURI(info->mPrincipal, info->mBlobImpl); + } else { + uri = new nsHostObjectURI(nullptr, nullptr); + } + + rv = uri->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + + NS_TryToSetImmutable(uri); + uri.forget(aResult); + + if (info && info->mObjectType == DataInfo::eBlobImpl) { + info->mURIs.AppendElement(do_GetWeakReference(*aResult)); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsHostObjectProtocolHandler::NewChannel2(nsIURI* uri, + nsILoadInfo* aLoadInfo, + nsIChannel** result) +{ + *result = nullptr; + + nsCOMPtr<nsIURIWithBlobImpl> uriBlobImpl = do_QueryInterface(uri); + if (!uriBlobImpl) { + return NS_ERROR_DOM_BAD_URI; + } + + nsCOMPtr<nsISupports> tmp; + MOZ_ALWAYS_SUCCEEDS(uriBlobImpl->GetBlobImpl(getter_AddRefs(tmp))); + nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(tmp); + if (!blobImpl) { + return NS_ERROR_DOM_BAD_URI; + } + +#ifdef DEBUG + DataInfo* info = GetDataInfoFromURI(uri); + + // Info can be null, in case this blob URL has been revoked already. + if (info) { + nsCOMPtr<nsIURIWithPrincipal> uriPrinc = do_QueryInterface(uri); + nsCOMPtr<nsIPrincipal> principal; + uriPrinc->GetPrincipal(getter_AddRefs(principal)); + NS_ASSERTION(info->mPrincipal == principal, "Wrong principal!"); + } +#endif + + ErrorResult rv; + nsCOMPtr<nsIInputStream> stream; + blobImpl->GetInternalStream(getter_AddRefs(stream), rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + nsAutoString contentType; + blobImpl->GetType(contentType); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), + uri, + stream, + NS_ConvertUTF16toUTF8(contentType), + EmptyCString(), // aContentCharset + aLoadInfo); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + if (blobImpl->IsFile()) { + nsString filename; + blobImpl->GetName(filename); + channel->SetContentDispositionFilename(filename); + } + + uint64_t size = blobImpl->GetSize(rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + channel->SetOriginalURI(uri); + channel->SetContentType(NS_ConvertUTF16toUTF8(contentType)); + channel->SetContentLength(size); + + channel.forget(result); + + return NS_OK; +} + +NS_IMETHODIMP +nsHostObjectProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result) +{ + return NewChannel2(uri, nullptr, result); +} + +NS_IMETHODIMP +nsHostObjectProtocolHandler::AllowPort(int32_t port, const char *scheme, + bool *_retval) +{ + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +nsBlobProtocolHandler::GetScheme(nsACString &result) +{ + result.AssignLiteral(BLOBURI_SCHEME); + return NS_OK; +} + +NS_IMETHODIMP +nsFontTableProtocolHandler::GetProtocolFlags(uint32_t *result) +{ + Unused << nsHostObjectProtocolHandler::GetProtocolFlags(result); + *result |= URI_IS_LOCAL_RESOURCE; + return NS_OK; +} + +NS_IMETHODIMP +nsFontTableProtocolHandler::GetScheme(nsACString &result) +{ + result.AssignLiteral(FONTTABLEURI_SCHEME); + return NS_OK; +} + +nsresult +NS_GetBlobForBlobURI(nsIURI* aURI, BlobImpl** aBlob) +{ + NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs"); + + *aBlob = nullptr; + + DataInfo* info = GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != DataInfo::eBlobImpl) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<BlobImpl> blob = info->mBlobImpl; + blob.forget(aBlob); + return NS_OK; +} + +nsresult +NS_GetBlobForBlobURISpec(const nsACString& aSpec, BlobImpl** aBlob) +{ + *aBlob = nullptr; + + DataInfo* info = GetDataInfo(aSpec); + if (!info || info->mObjectType != DataInfo::eBlobImpl) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<BlobImpl> blob = info->mBlobImpl; + blob.forget(aBlob); + return NS_OK; +} + +nsresult +NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream) +{ + RefPtr<BlobImpl> blobImpl; + ErrorResult rv; + rv = NS_GetBlobForBlobURI(aURI, getter_AddRefs(blobImpl)); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + blobImpl->GetInternalStream(aStream, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +nsresult +NS_GetStreamForMediaStreamURI(nsIURI* aURI, mozilla::DOMMediaStream** aStream) +{ + NS_ASSERTION(IsMediaStreamURI(aURI), "Only call this with mediastream URIs"); + + DataInfo* info = GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != DataInfo::eMediaStream) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<DOMMediaStream> mediaStream = info->mMediaStream; + mediaStream.forget(aStream); + return NS_OK; +} + +NS_IMETHODIMP +nsFontTableProtocolHandler::NewURI(const nsACString& aSpec, + const char *aCharset, + nsIURI *aBaseURI, + nsIURI **aResult) +{ + RefPtr<nsIURI> uri; + + // Either you got here via a ref or a fonttable: uri + if (aSpec.Length() && aSpec.CharAt(0) == '#') { + nsresult rv = aBaseURI->CloneIgnoringRef(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + uri->SetRef(aSpec); + } else { + // Relative URIs (other than #ref) are not meaningful within the + // fonttable: scheme. + // If aSpec is a relative URI -other- than a bare #ref, + // this will leave uri empty, and we'll return a failure code below. + uri = new mozilla::net::nsSimpleURI(); + nsresult rv = uri->SetSpec(aSpec); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool schemeIs; + if (NS_FAILED(uri->SchemeIs(FONTTABLEURI_SCHEME, &schemeIs)) || !schemeIs) { + NS_WARNING("Non-fonttable spec in nsFontTableProtocolHander"); + return NS_ERROR_NOT_AVAILABLE; + } + + uri.forget(aResult); + return NS_OK; +} + +nsresult +NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource) +{ + NS_ASSERTION(IsMediaSourceURI(aURI), "Only call this with mediasource URIs"); + + *aSource = nullptr; + + DataInfo* info = GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != DataInfo::eMediaSource) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<MediaSource> mediaSource = info->mMediaSource; + mediaSource.forget(aSource); + return NS_OK; +} + +#define NS_BLOBPROTOCOLHANDLER_CID \ +{ 0xb43964aa, 0xa078, 0x44b2, \ + { 0xb0, 0x6b, 0xfd, 0x4d, 0x1b, 0x17, 0x2e, 0x66 } } + +#define NS_FONTTABLEPROTOCOLHANDLER_CID \ +{ 0x3fc8f04e, 0xd719, 0x43ca, \ + { 0x9a, 0xd0, 0x18, 0xee, 0x32, 0x02, 0x11, 0xf2 } } + +NS_GENERIC_FACTORY_CONSTRUCTOR(nsBlobProtocolHandler) +NS_GENERIC_FACTORY_CONSTRUCTOR(nsFontTableProtocolHandler) + +NS_DEFINE_NAMED_CID(NS_BLOBPROTOCOLHANDLER_CID); +NS_DEFINE_NAMED_CID(NS_FONTTABLEPROTOCOLHANDLER_CID); + +static const mozilla::Module::CIDEntry kHostObjectProtocolHandlerCIDs[] = { + { &kNS_BLOBPROTOCOLHANDLER_CID, false, nullptr, nsBlobProtocolHandlerConstructor }, + { &kNS_FONTTABLEPROTOCOLHANDLER_CID, false, nullptr, nsFontTableProtocolHandlerConstructor }, + { nullptr } +}; + +static const mozilla::Module::ContractIDEntry kHostObjectProtocolHandlerContracts[] = { + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX BLOBURI_SCHEME, &kNS_BLOBPROTOCOLHANDLER_CID }, + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX FONTTABLEURI_SCHEME, &kNS_FONTTABLEPROTOCOLHANDLER_CID }, + { nullptr } +}; + +static const mozilla::Module kHostObjectProtocolHandlerModule = { + mozilla::Module::kVersion, + kHostObjectProtocolHandlerCIDs, + kHostObjectProtocolHandlerContracts +}; + +NSMODULE_DEFN(HostObjectProtocolHandler) = &kHostObjectProtocolHandlerModule; + +bool IsType(nsIURI* aUri, DataInfo::ObjectType aType) +{ + DataInfo* info = GetDataInfoFromURI(aUri); + if (!info) { + return false; + } + + return info->mObjectType == aType; +} + +bool IsBlobURI(nsIURI* aUri) +{ + return IsType(aUri, DataInfo::eBlobImpl); +} + +bool IsMediaStreamURI(nsIURI* aUri) +{ + return IsType(aUri, DataInfo::eMediaStream); +} + +bool IsMediaSourceURI(nsIURI* aUri) +{ + return IsType(aUri, DataInfo::eMediaSource); +} |