michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=8 sts=2 et sw=2 tw=80: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsHostObjectProtocolHandler.h" michael@0: #include "nsHostObjectURI.h" michael@0: #include "nsError.h" michael@0: #include "nsClassHashtable.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsIPrincipal.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsIDOMMediaStream.h" michael@0: #include "mozilla/dom/MediaSource.h" michael@0: #include "nsIMemoryReporter.h" michael@0: #include "mozilla/Preferences.h" michael@0: michael@0: // ----------------------------------------------------------------------- michael@0: // Hash table michael@0: struct DataInfo michael@0: { michael@0: // mObject is expected to be an nsIDOMBlob, nsIDOMMediaStream, or MediaSource michael@0: nsCOMPtr mObject; michael@0: nsCOMPtr mPrincipal; michael@0: nsCString mStack; michael@0: }; michael@0: michael@0: static nsClassHashtable* gDataTable; michael@0: michael@0: // Memory reporting for the hash table. michael@0: namespace mozilla { michael@0: michael@0: class HostObjectURLsReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CollectReports(nsIHandleReportCallback* aHandleReport, michael@0: nsISupports* aData) michael@0: { michael@0: return MOZ_COLLECT_REPORT( michael@0: "host-object-urls", KIND_OTHER, UNITS_COUNT, michael@0: gDataTable ? gDataTable->Count() : 0, michael@0: "The number of host objects stored for access via URLs " michael@0: "(e.g. blobs passed to URL.createObjectURL)."); michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(HostObjectURLsReporter, nsIMemoryReporter) michael@0: michael@0: class BlobURLsReporter MOZ_FINAL : public nsIMemoryReporter michael@0: { michael@0: public: michael@0: NS_DECL_ISUPPORTS michael@0: michael@0: NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback, michael@0: nsISupports* aData) michael@0: { michael@0: EnumArg env; michael@0: env.mCallback = aCallback; michael@0: env.mData = aData; michael@0: michael@0: if (gDataTable) { michael@0: gDataTable->EnumerateRead(CountCallback, &env); michael@0: gDataTable->EnumerateRead(ReportCallback, &env); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Initialize info->mStack to record JS stack info, if enabled. michael@0: // The string generated here is used in ReportCallback, below. michael@0: static void GetJSStackForBlob(DataInfo* aInfo) michael@0: { michael@0: nsCString& stack = aInfo->mStack; michael@0: MOZ_ASSERT(stack.IsEmpty()); michael@0: const uint32_t maxFrames = Preferences::GetUint("memory.blob_report.stack_frames"); michael@0: michael@0: if (maxFrames == 0) { michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: nsIXPConnect* xpc = nsContentUtils::XPConnect(); michael@0: nsCOMPtr frame; michael@0: rv = xpc->GetCurrentJSStack(getter_AddRefs(frame)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: michael@0: nsAutoCString origin; michael@0: nsCOMPtr principalURI; michael@0: if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) michael@0: && principalURI) { michael@0: principalURI->GetPrePath(origin); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < maxFrames && frame; ++i) { michael@0: nsString fileNameUTF16; michael@0: int32_t lineNumber = 0; michael@0: michael@0: frame->GetFilename(fileNameUTF16); michael@0: frame->GetLineNumber(&lineNumber); michael@0: michael@0: if (!fileNameUTF16.IsEmpty()) { michael@0: NS_ConvertUTF16toUTF8 fileName(fileNameUTF16); michael@0: stack += "js("; michael@0: if (!origin.IsEmpty()) { michael@0: // Make the file name root-relative for conciseness if possible. michael@0: const char* originData; michael@0: uint32_t originLen; michael@0: michael@0: originLen = origin.GetData(&originData); michael@0: // If fileName starts with origin + "/", cut up to that "/". michael@0: if (fileName.Length() >= originLen + 1 && michael@0: memcmp(fileName.get(), originData, originLen) == 0 && michael@0: fileName[originLen] == '/') { michael@0: fileName.Cut(0, originLen); michael@0: } michael@0: } michael@0: fileName.ReplaceChar('/', '\\'); michael@0: stack += fileName; michael@0: if (lineNumber > 0) { michael@0: stack += ", line="; michael@0: stack.AppendInt(lineNumber); michael@0: } michael@0: stack += ")/"; michael@0: } michael@0: michael@0: rv = frame->GetCaller(getter_AddRefs(frame)); michael@0: NS_ENSURE_SUCCESS_VOID(rv); michael@0: } michael@0: } michael@0: michael@0: private: michael@0: struct EnumArg { michael@0: nsIHandleReportCallback* mCallback; michael@0: nsISupports* mData; michael@0: nsDataHashtable, uint32_t> mRefCounts; michael@0: }; michael@0: michael@0: // Determine number of URLs per blob, to handle the case where it's > 1. michael@0: static PLDHashOperator CountCallback(nsCStringHashKey::KeyType aKey, michael@0: DataInfo* aInfo, michael@0: void* aUserArg) michael@0: { michael@0: EnumArg* envp = static_cast(aUserArg); michael@0: nsCOMPtr blob; michael@0: michael@0: blob = do_QueryInterface(aInfo->mObject); michael@0: if (blob) { michael@0: envp->mRefCounts.Put(blob, envp->mRefCounts.Get(blob) + 1); michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: static PLDHashOperator ReportCallback(nsCStringHashKey::KeyType aKey, michael@0: DataInfo* aInfo, michael@0: void* aUserArg) michael@0: { michael@0: EnumArg* envp = static_cast(aUserArg); michael@0: nsCOMPtr blob; michael@0: michael@0: blob = do_QueryInterface(aInfo->mObject); michael@0: if (blob) { michael@0: NS_NAMED_LITERAL_CSTRING michael@0: (desc, "A blob URL allocated with URL.createObjectURL; the referenced " michael@0: "blob cannot be freed until all URLs for it have been explicitly " michael@0: "invalidated with URL.revokeObjectURL."); michael@0: nsAutoCString path, url, owner, specialDesc; michael@0: nsCOMPtr principalURI; michael@0: uint64_t size = 0; michael@0: uint32_t refCount = 1; michael@0: DebugOnly blobWasCounted; michael@0: michael@0: blobWasCounted = envp->mRefCounts.Get(blob, &refCount); michael@0: MOZ_ASSERT(blobWasCounted); michael@0: MOZ_ASSERT(refCount > 0); michael@0: michael@0: bool isMemoryFile = blob->IsMemoryFile(); michael@0: michael@0: if (isMemoryFile) { michael@0: if (NS_FAILED(blob->GetSize(&size))) { michael@0: size = 0; michael@0: } michael@0: } michael@0: michael@0: path = isMemoryFile ? "memory-blob-urls/" : "file-blob-urls/"; michael@0: if (NS_SUCCEEDED(aInfo->mPrincipal->GetURI(getter_AddRefs(principalURI))) && michael@0: principalURI != nullptr && michael@0: NS_SUCCEEDED(principalURI->GetSpec(owner)) && michael@0: !owner.IsEmpty()) { michael@0: owner.ReplaceChar('/', '\\'); michael@0: path += "owner("; michael@0: path += owner; michael@0: path += ")"; michael@0: } else { michael@0: path += "owner unknown"; michael@0: } michael@0: path += "/"; michael@0: path += aInfo->mStack; michael@0: url = aKey; michael@0: url.ReplaceChar('/', '\\'); michael@0: path += url; michael@0: if (refCount > 1) { michael@0: nsAutoCString addrStr; michael@0: michael@0: addrStr = "0x"; michael@0: addrStr.AppendInt((uint64_t)(nsIDOMBlob*)blob, 16); michael@0: michael@0: path += " "; michael@0: path.AppendInt(refCount); michael@0: path += "@"; michael@0: path += addrStr; michael@0: michael@0: specialDesc = desc; michael@0: specialDesc += "\n\nNOTE: This blob (address "; michael@0: specialDesc += addrStr; michael@0: specialDesc += ") has "; michael@0: specialDesc.AppendInt(refCount); michael@0: specialDesc += " URLs."; michael@0: if (isMemoryFile) { michael@0: specialDesc += " Its size is divided "; michael@0: specialDesc += refCount > 2 ? "among" : "between"; michael@0: specialDesc += " them in this report."; michael@0: } michael@0: } michael@0: michael@0: const nsACString& descString = specialDesc.IsEmpty() michael@0: ? static_cast(desc) michael@0: : static_cast(specialDesc); michael@0: if (isMemoryFile) { michael@0: envp->mCallback->Callback(EmptyCString(), michael@0: path, michael@0: KIND_OTHER, michael@0: UNITS_BYTES, michael@0: size / refCount, michael@0: descString, michael@0: envp->mData); michael@0: } michael@0: else { michael@0: envp->mCallback->Callback(EmptyCString(), michael@0: path, michael@0: KIND_OTHER, michael@0: UNITS_COUNT, michael@0: 1, michael@0: descString, michael@0: envp->mData); michael@0: } michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(BlobURLsReporter, nsIMemoryReporter) michael@0: michael@0: } michael@0: michael@0: void michael@0: nsHostObjectProtocolHandler::Init(void) michael@0: { michael@0: static bool initialized = false; michael@0: michael@0: if (!initialized) { michael@0: initialized = true; michael@0: RegisterStrongMemoryReporter(new mozilla::HostObjectURLsReporter()); michael@0: RegisterStrongMemoryReporter(new mozilla::BlobURLsReporter()); michael@0: } michael@0: } michael@0: michael@0: nsHostObjectProtocolHandler::nsHostObjectProtocolHandler() michael@0: { michael@0: Init(); michael@0: } michael@0: michael@0: nsresult michael@0: nsHostObjectProtocolHandler::AddDataEntry(const nsACString& aScheme, michael@0: nsISupports* aObject, michael@0: nsIPrincipal* aPrincipal, michael@0: nsACString& aUri) michael@0: { michael@0: Init(); michael@0: michael@0: nsresult rv = GenerateURIString(aScheme, aUri); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: if (!gDataTable) { michael@0: gDataTable = new nsClassHashtable; michael@0: } michael@0: michael@0: DataInfo* info = new DataInfo; michael@0: michael@0: info->mObject = aObject; michael@0: info->mPrincipal = aPrincipal; michael@0: mozilla::BlobURLsReporter::GetJSStackForBlob(info); michael@0: michael@0: gDataTable->Put(aUri, info); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsHostObjectProtocolHandler::RemoveDataEntry(const nsACString& aUri) michael@0: { michael@0: if (gDataTable) { michael@0: nsCString uriIgnoringRef; michael@0: int32_t hashPos = aUri.FindChar('#'); michael@0: if (hashPos < 0) { michael@0: uriIgnoringRef = aUri; michael@0: } michael@0: else { michael@0: uriIgnoringRef = StringHead(aUri, hashPos); michael@0: } michael@0: gDataTable->Remove(uriIgnoringRef); michael@0: if (gDataTable->Count() == 0) { michael@0: delete gDataTable; michael@0: gDataTable = nullptr; michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsHostObjectProtocolHandler::GenerateURIString(const nsACString &aScheme, michael@0: nsACString& aUri) michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr uuidgen = michael@0: do_GetService("@mozilla.org/uuid-generator;1", &rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsID id; michael@0: rv = uuidgen->GenerateUUIDInPlace(&id); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: char chars[NSID_LENGTH]; michael@0: id.ToProvidedString(chars); michael@0: michael@0: aUri += aScheme; michael@0: aUri += NS_LITERAL_CSTRING(":"); michael@0: aUri += Substring(chars + 1, chars + NSID_LENGTH - 2); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: static DataInfo* michael@0: GetDataInfo(const nsACString& aUri) michael@0: { michael@0: if (!gDataTable) { michael@0: return nullptr; michael@0: } michael@0: michael@0: DataInfo* res; michael@0: nsCString uriIgnoringRef; michael@0: int32_t hashPos = aUri.FindChar('#'); michael@0: if (hashPos < 0) { michael@0: uriIgnoringRef = aUri; michael@0: } michael@0: else { michael@0: uriIgnoringRef = StringHead(aUri, hashPos); michael@0: } michael@0: gDataTable->Get(uriIgnoringRef, &res); michael@0: michael@0: return res; michael@0: } michael@0: michael@0: nsIPrincipal* michael@0: nsHostObjectProtocolHandler::GetDataEntryPrincipal(const nsACString& aUri) michael@0: { michael@0: if (!gDataTable) { michael@0: return nullptr; michael@0: } michael@0: michael@0: DataInfo* res = GetDataInfo(aUri); michael@0: michael@0: if (!res) { michael@0: return nullptr; michael@0: } michael@0: michael@0: return res->mPrincipal; michael@0: } michael@0: michael@0: void michael@0: nsHostObjectProtocolHandler::Traverse(const nsACString& aUri, michael@0: nsCycleCollectionTraversalCallback& aCallback) michael@0: { michael@0: if (!gDataTable) { michael@0: return; michael@0: } michael@0: michael@0: DataInfo* res; michael@0: gDataTable->Get(aUri, &res); michael@0: if (!res) { michael@0: return; michael@0: } michael@0: michael@0: NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCallback, "HostObjectProtocolHandler DataInfo.mObject"); michael@0: aCallback.NoteXPCOMChild(res->mObject); michael@0: } michael@0: michael@0: static nsISupports* michael@0: GetDataObject(nsIURI* aURI) michael@0: { michael@0: nsCString spec; michael@0: aURI->GetSpec(spec); michael@0: michael@0: DataInfo* info = GetDataInfo(spec); michael@0: return info ? info->mObject : nullptr; michael@0: } michael@0: michael@0: // ----------------------------------------------------------------------- michael@0: // Protocol handler michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHostObjectProtocolHandler, nsIProtocolHandler) michael@0: michael@0: NS_IMETHODIMP michael@0: nsHostObjectProtocolHandler::GetDefaultPort(int32_t *result) michael@0: { michael@0: *result = -1; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHostObjectProtocolHandler::GetProtocolFlags(uint32_t *result) michael@0: { michael@0: *result = URI_NORELATIVE | URI_NOAUTH | URI_LOADABLE_BY_SUBSUMERS | michael@0: URI_IS_LOCAL_RESOURCE | URI_NON_PERSISTABLE; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHostObjectProtocolHandler::NewURI(const nsACString& aSpec, michael@0: const char *aCharset, michael@0: nsIURI *aBaseURI, michael@0: nsIURI **aResult) michael@0: { michael@0: *aResult = nullptr; michael@0: nsresult rv; michael@0: michael@0: DataInfo* info = GetDataInfo(aSpec); michael@0: michael@0: nsRefPtr uri = michael@0: new nsHostObjectURI(info ? info->mPrincipal.get() : nullptr); michael@0: michael@0: rv = uri->SetSpec(aSpec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_TryToSetImmutable(uri); michael@0: uri.forget(aResult); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHostObjectProtocolHandler::NewChannel(nsIURI* uri, nsIChannel* *result) michael@0: { michael@0: *result = nullptr; michael@0: michael@0: nsCString spec; michael@0: uri->GetSpec(spec); michael@0: michael@0: DataInfo* info = GetDataInfo(spec); michael@0: michael@0: if (!info) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: nsCOMPtr blob = do_QueryInterface(info->mObject); michael@0: if (!blob) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: #ifdef DEBUG michael@0: { michael@0: nsCOMPtr uriPrinc = do_QueryInterface(uri); michael@0: nsCOMPtr principal; michael@0: uriPrinc->GetPrincipal(getter_AddRefs(principal)); michael@0: NS_ASSERTION(info->mPrincipal == principal, "Wrong principal!"); michael@0: } michael@0: #endif michael@0: michael@0: nsCOMPtr stream; michael@0: nsresult rv = blob->GetInternalStream(getter_AddRefs(stream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr channel; michael@0: rv = NS_NewInputStreamChannel(getter_AddRefs(channel), michael@0: uri, michael@0: stream); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr owner = do_QueryInterface(info->mPrincipal); michael@0: michael@0: nsString type; michael@0: rv = blob->GetType(type); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr file = do_QueryInterface(info->mObject); michael@0: if (file) { michael@0: nsString filename; michael@0: rv = file->GetName(filename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: channel->SetContentDispositionFilename(filename); michael@0: } michael@0: michael@0: uint64_t size; michael@0: rv = blob->GetSize(&size); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: channel->SetOwner(owner); michael@0: channel->SetOriginalURI(uri); michael@0: channel->SetContentType(NS_ConvertUTF16toUTF8(type)); michael@0: channel->SetContentLength(size); michael@0: michael@0: channel.forget(result); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHostObjectProtocolHandler::AllowPort(int32_t port, const char *scheme, michael@0: bool *_retval) michael@0: { michael@0: // don't override anything. michael@0: *_retval = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsBlobProtocolHandler::GetScheme(nsACString &result) michael@0: { michael@0: result.AssignLiteral(BLOBURI_SCHEME); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaStreamProtocolHandler::GetScheme(nsACString &result) michael@0: { michael@0: result.AssignLiteral(MEDIASTREAMURI_SCHEME); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsMediaSourceProtocolHandler::GetScheme(nsACString &result) michael@0: { michael@0: result.AssignLiteral(MEDIASOURCEURI_SCHEME); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFontTableProtocolHandler::GetScheme(nsACString &result) michael@0: { michael@0: result.AssignLiteral(FONTTABLEURI_SCHEME); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: NS_GetStreamForBlobURI(nsIURI* aURI, nsIInputStream** aStream) michael@0: { michael@0: NS_ASSERTION(IsBlobURI(aURI), "Only call this with blob URIs"); michael@0: michael@0: *aStream = nullptr; michael@0: michael@0: nsCOMPtr blob = do_QueryInterface(GetDataObject(aURI)); michael@0: if (!blob) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: return blob->GetInternalStream(aStream); michael@0: } michael@0: michael@0: nsresult michael@0: NS_GetStreamForMediaStreamURI(nsIURI* aURI, nsIDOMMediaStream** aStream) michael@0: { michael@0: NS_ASSERTION(IsMediaStreamURI(aURI), "Only call this with mediastream URIs"); michael@0: michael@0: *aStream = nullptr; michael@0: michael@0: nsCOMPtr stream = do_QueryInterface(GetDataObject(aURI)); michael@0: if (!stream) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: *aStream = stream; michael@0: NS_ADDREF(*aStream); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFontTableProtocolHandler::NewURI(const nsACString& aSpec, michael@0: const char *aCharset, michael@0: nsIURI *aBaseURI, michael@0: nsIURI **aResult) michael@0: { michael@0: nsRefPtr uri; michael@0: michael@0: // Either you got here via a ref or a fonttable: uri michael@0: if (aSpec.Length() && aSpec.CharAt(0) == '#') { michael@0: nsresult rv = aBaseURI->CloneIgnoringRef(getter_AddRefs(uri)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uri->SetRef(aSpec); michael@0: } else { michael@0: // Relative URIs (other than #ref) are not meaningful within the michael@0: // fonttable: scheme. michael@0: // If aSpec is a relative URI -other- than a bare #ref, michael@0: // this will leave uri empty, and we'll return a failure code below. michael@0: uri = new nsSimpleURI(); michael@0: uri->SetSpec(aSpec); michael@0: } michael@0: michael@0: bool schemeIs; michael@0: if (NS_FAILED(uri->SchemeIs(FONTTABLEURI_SCHEME, &schemeIs)) || !schemeIs) { michael@0: NS_WARNING("Non-fonttable spec in nsFontTableProtocolHander"); michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: michael@0: uri.forget(aResult); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: NS_GetSourceForMediaSourceURI(nsIURI* aURI, mozilla::dom::MediaSource** aSource) michael@0: { michael@0: NS_ASSERTION(IsMediaSourceURI(aURI), "Only call this with mediasource URIs"); michael@0: michael@0: *aSource = nullptr; michael@0: michael@0: nsCOMPtr source = do_QueryInterface(GetDataObject(aURI)); michael@0: if (!source) { michael@0: return NS_ERROR_DOM_BAD_URI; michael@0: } michael@0: michael@0: source.forget(aSource); michael@0: return NS_OK; michael@0: }