diff options
Diffstat (limited to '')
-rw-r--r-- | dom/file/uri/BlobURL.cpp | 171 | ||||
-rw-r--r-- | dom/file/uri/BlobURL.h | 123 | ||||
-rw-r--r-- | dom/file/uri/BlobURLChannel.cpp | 86 | ||||
-rw-r--r-- | dom/file/uri/BlobURLChannel.h | 37 | ||||
-rw-r--r-- | dom/file/uri/BlobURLInputStream.cpp | 604 | ||||
-rw-r--r-- | dom/file/uri/BlobURLInputStream.h | 81 | ||||
-rw-r--r-- | dom/file/uri/BlobURLProtocolHandler.cpp | 1016 | ||||
-rw-r--r-- | dom/file/uri/BlobURLProtocolHandler.h | 143 | ||||
-rw-r--r-- | dom/file/uri/components.conf | 24 | ||||
-rw-r--r-- | dom/file/uri/moz.build | 34 |
10 files changed, 2319 insertions, 0 deletions
diff --git a/dom/file/uri/BlobURL.cpp b/dom/file/uri/BlobURL.cpp new file mode 100644 index 0000000000..b3afd60437 --- /dev/null +++ b/dom/file/uri/BlobURL.cpp @@ -0,0 +1,171 @@ +/* -*- 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 "nsIClassInfoImpl.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" + +#include "mozilla/dom/BlobURL.h" +#include "mozilla/dom/BlobURLProtocolHandler.h" +#include "mozilla/ipc/BackgroundUtils.h" +#include "mozilla/ipc/URIUtils.h" + +using namespace mozilla::dom; + +static NS_DEFINE_CID(kThisSimpleURIImplementationCID, + NS_THIS_SIMPLEURI_IMPLEMENTATION_CID); + +NS_IMPL_ADDREF_INHERITED(BlobURL, mozilla::net::nsSimpleURI) +NS_IMPL_RELEASE_INHERITED(BlobURL, mozilla::net::nsSimpleURI) + +NS_IMPL_CLASSINFO(BlobURL, nullptr, nsIClassInfo::THREADSAFE, + NS_HOSTOBJECTURI_CID); +// Empty CI getter. We only need nsIClassInfo for Serialization +NS_IMPL_CI_INTERFACE_GETTER0(BlobURL) + +NS_INTERFACE_MAP_BEGIN(BlobURL) + if (aIID.Equals(kHOSTOBJECTURICID)) + foundInterface = static_cast<nsIURI*>(this); + else if (aIID.Equals(kThisSimpleURIImplementationCID)) { + // Need to return explicitly here, because if we just set foundInterface + // to null the NS_INTERFACE_MAP_END_INHERITING will end up calling into + // nsSimplURI::QueryInterface and finding something for this CID. + *aInstancePtr = nullptr; + return NS_NOINTERFACE; + } else + NS_IMPL_QUERY_CLASSINFO(BlobURL) +NS_INTERFACE_MAP_END_INHERITING(mozilla::net::nsSimpleURI) + +BlobURL::BlobURL() : mRevoked(false) {} + +// nsISerializable methods: + +NS_IMETHODIMP +BlobURL::Read(nsIObjectInputStream* aStream) { + MOZ_ASSERT_UNREACHABLE("Use nsIURIMutator.read() instead"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +nsresult BlobURL::ReadPrivate(nsIObjectInputStream* aStream) { + nsresult rv = mozilla::net::nsSimpleURI::ReadPrivate(aStream); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aStream->ReadBoolean(&mRevoked); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP +BlobURL::Write(nsIObjectOutputStream* aStream) { + nsresult rv = mozilla::net::nsSimpleURI::Write(aStream); + NS_ENSURE_SUCCESS(rv, rv); + + rv = aStream->WriteBoolean(mRevoked); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +NS_IMETHODIMP_(void) +BlobURL::Serialize(mozilla::ipc::URIParams& aParams) { + using namespace mozilla::ipc; + + HostObjectURIParams hostParams; + URIParams simpleParams; + + mozilla::net::nsSimpleURI::Serialize(simpleParams); + hostParams.simpleParams() = simpleParams; + + hostParams.revoked() = mRevoked; + + aParams = hostParams; +} + +bool BlobURL::Deserialize(const mozilla::ipc::URIParams& aParams) { + using namespace mozilla::ipc; + + if (aParams.type() != URIParams::THostObjectURIParams) { + NS_ERROR("Received unknown parameters from the other process!"); + return false; + } + + const HostObjectURIParams& hostParams = aParams.get_HostObjectURIParams(); + + if (!mozilla::net::nsSimpleURI::Deserialize(hostParams.simpleParams())) { + return false; + } + + mRevoked = hostParams.revoked(); + return true; +} + +nsresult BlobURL::SetScheme(const nsACString& aScheme) { + // Disallow setting the scheme, since that could cause us to be associated + // with a different protocol handler. + return NS_ERROR_FAILURE; +} + +// nsIURI methods: +nsresult BlobURL::CloneInternal( + mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode, + const nsACString& newRef, nsIURI** aClone) { + nsCOMPtr<nsIURI> simpleClone; + nsresult rv = mozilla::net::nsSimpleURI::CloneInternal( + aRefHandlingMode, newRef, getter_AddRefs(simpleClone)); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef DEBUG + RefPtr<BlobURL> uriCheck; + rv = simpleClone->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(uriCheck)); + MOZ_ASSERT(NS_SUCCEEDED(rv) && uriCheck); +#endif + + BlobURL* u = static_cast<BlobURL*>(simpleClone.get()); + u->mRevoked = mRevoked; + + simpleClone.forget(aClone); + return NS_OK; +} + +/* virtual */ +nsresult BlobURL::EqualsInternal( + nsIURI* aOther, mozilla::net::nsSimpleURI::RefHandlingEnum aRefHandlingMode, + bool* aResult) { + if (!aOther) { + *aResult = false; + return NS_OK; + } + + RefPtr<BlobURL> otherUri; + aOther->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(otherUri)); + if (!otherUri) { + *aResult = false; + return NS_OK; + } + + // Compare the member data that our base class knows about. + *aResult = + mozilla::net::nsSimpleURI::EqualsInternal(otherUri, aRefHandlingMode); + + // We don't want to compare the revoked flag. + return NS_OK; +} + +// Queries this list of interfaces. If none match, it queries mURI. +NS_IMPL_NSIURIMUTATOR_ISUPPORTS(BlobURL::Mutator, nsIURISetters, nsIURIMutator, + nsISerializable, nsIBlobURLMutator) + +NS_IMETHODIMP +BlobURL::Mutate(nsIURIMutator** aMutator) { + RefPtr<BlobURL::Mutator> mutator = new BlobURL::Mutator(); + nsresult rv = mutator->InitFromURI(this); + if (NS_FAILED(rv)) { + return rv; + } + mutator.forget(aMutator); + return NS_OK; +} diff --git a/dom/file/uri/BlobURL.h b/dom/file/uri/BlobURL.h new file mode 100644 index 0000000000..1e7a91daa4 --- /dev/null +++ b/dom/file/uri/BlobURL.h @@ -0,0 +1,123 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_BlobURL_h +#define mozilla_dom_BlobURL_h + +#include "nsCOMPtr.h" +#include "nsISerializable.h" +#include "nsSimpleURI.h" +#include "prtime.h" + +#define NS_HOSTOBJECTURI_CID \ + { \ + 0xf5475c51, 0x59a7, 0x4757, { \ + 0xb3, 0xd9, 0xe2, 0x11, 0xa9, 0x41, 0x08, 0x72 \ + } \ + } + +#define NS_IBLOBURLMUTATOR_IID \ + { \ + 0xf91e646d, 0xe87b, 0x485e, { \ + 0xbb, 0xc8, 0x0e, 0x8a, 0x2e, 0xe9, 0x87, 0xa9 \ + } \ + } + +class NS_NO_VTABLE nsIBlobURLMutator : public nsISupports { + public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IBLOBURLMUTATOR_IID) + NS_IMETHOD SetRevoked(bool aRevoked) = 0; +}; + +inline NS_DEFINE_CID(kHOSTOBJECTURICID, NS_HOSTOBJECTURI_CID); + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIBlobURLMutator, NS_IBLOBURLMUTATOR_IID) + +namespace mozilla::dom { + +/** + * These URIs refer to host objects with "blob" scheme. + */ +class BlobURL final : public mozilla::net::nsSimpleURI { + private: + BlobURL(); + + public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSISERIALIZABLE + + // Override CloneInternal() and EqualsInternal() + nsresult CloneInternal(RefHandlingEnum aRefHandlingMode, + const nsACString& newRef, nsIURI** aClone) override; + nsresult EqualsInternal(nsIURI* aOther, RefHandlingEnum aRefHandlingMode, + bool* aResult) override; + NS_IMETHOD_(void) Serialize(mozilla::ipc::URIParams& aParams) override; + + // Override StartClone to hand back a BlobURL + mozilla::net::nsSimpleURI* StartClone(RefHandlingEnum refHandlingMode, + const nsACString& newRef) override { + BlobURL* url = new BlobURL(); + SetRefOnClone(url, refHandlingMode, newRef); + return url; + } + + bool Revoked() const { return mRevoked; } + + NS_IMETHOD Mutate(nsIURIMutator** _retval) override; + + private: + ~BlobURL() override = default; + + nsresult SetScheme(const nsACString& aProtocol) override; + bool Deserialize(const mozilla::ipc::URIParams&); + nsresult ReadPrivate(nsIObjectInputStream* stream); + + bool mRevoked; + + public: + class Mutator final : public nsIURIMutator, + public BaseURIMutator<BlobURL>, + public nsIBlobURLMutator, + public nsISerializable { + NS_DECL_ISUPPORTS + NS_FORWARD_SAFE_NSIURISETTERS_RET(mURI) + NS_DEFINE_NSIMUTATOR_COMMON + + NS_IMETHOD + Write(nsIObjectOutputStream* aOutputStream) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + [[nodiscard]] NS_IMETHOD Read(nsIObjectInputStream* aStream) override { + return InitFromInputStream(aStream); + } + + NS_IMETHOD SetRevoked(bool aRevoked) override { + mURI->mRevoked = aRevoked; + return NS_OK; + } + + Mutator() = default; + + private: + ~Mutator() = default; + + friend class BlobURL; + }; + + friend BaseURIMutator<BlobURL>; +}; + +#define NS_HOSTOBJECTURIMUTATOR_CID \ + { \ + 0xbbe50ef2, 0x80eb, 0x469d, { \ + 0xb7, 0x0d, 0x02, 0x85, 0x82, 0x75, 0x38, 0x9f \ + } \ + } + +} // namespace mozilla::dom + +#endif /* mozilla_dom_BlobURL_h */ diff --git a/dom/file/uri/BlobURLChannel.cpp b/dom/file/uri/BlobURLChannel.cpp new file mode 100644 index 0000000000..6a7d4f1be6 --- /dev/null +++ b/dom/file/uri/BlobURLChannel.cpp @@ -0,0 +1,86 @@ +/* -*- 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 "BlobURLChannel.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/BlobURL.h" +#include "mozilla/dom/BlobURLInputStream.h" + +using namespace mozilla::dom; + +BlobURLChannel::BlobURLChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo) + : mContentStreamOpened(false) { + SetURI(aURI); + SetOriginalURI(aURI); + SetLoadInfo(aLoadInfo); + + // If we're sandboxed, make sure to clear any owner the channel + // might already have. + if (aLoadInfo && aLoadInfo->GetLoadingSandboxed()) { + SetOwner(nullptr); + } +} + +BlobURLChannel::~BlobURLChannel() = default; + +NS_IMETHODIMP +BlobURLChannel::SetContentType(const nsACString& aContentType) { + // If the blob type is empty, set the content type of the channel to the + // empty string. + if (aContentType.IsEmpty()) { + mContentType.Truncate(); + return NS_OK; + } + + return nsBaseChannel::SetContentType(aContentType); +} + +nsresult BlobURLChannel::OpenContentStream(bool aAsync, + nsIInputStream** aResult, + nsIChannel** aChannel) { + if (mContentStreamOpened) { + return NS_ERROR_ALREADY_OPENED; + } + + mContentStreamOpened = true; + + nsCOMPtr<nsIURI> uri; + nsresult rv = GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, NS_ERROR_MALFORMED_URI); + + RefPtr<BlobURL> blobURL; + rv = uri->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(blobURL)); + + if (NS_WARN_IF(NS_FAILED(rv)) || NS_WARN_IF(!blobURL)) { + return NS_ERROR_MALFORMED_URI; + } + + if (blobURL->Revoked()) { +#ifdef MOZ_WIDGET_ANDROID + nsCOMPtr<nsILoadInfo> loadInfo; + GetLoadInfo(getter_AddRefs(loadInfo)); + // if the channel was not triggered by the system principal, + // then we return here because the URL had been revoked + if (loadInfo && !loadInfo->TriggeringPrincipal()->IsSystemPrincipal()) { + return NS_ERROR_MALFORMED_URI; + } +#else + return NS_ERROR_MALFORMED_URI; +#endif + } + + nsCOMPtr<nsIInputStream> inputStream = + BlobURLInputStream::Create(this, blobURL); + if (NS_WARN_IF(!inputStream)) { + return NS_ERROR_MALFORMED_URI; + } + + EnableSynthesizedProgressEvents(true); + + inputStream.forget(aResult); + + return NS_OK; +} diff --git a/dom/file/uri/BlobURLChannel.h b/dom/file/uri/BlobURLChannel.h new file mode 100644 index 0000000000..232537171e --- /dev/null +++ b/dom/file/uri/BlobURLChannel.h @@ -0,0 +1,37 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_BlobURLChannel_h +#define mozilla_dom_BlobURLChannel_h + +#include "nsBaseChannel.h" +#include "nsCOMPtr.h" +#include "nsIInputStream.h" + +class nsIURI; + +namespace mozilla::dom { + +class BlobImpl; + +class BlobURLChannel final : public nsBaseChannel { + public: + BlobURLChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo); + + NS_IMETHOD SetContentType(const nsACString& aContentType) override; + + private: + ~BlobURLChannel() override; + + nsresult OpenContentStream(bool aAsync, nsIInputStream** aResult, + nsIChannel** aChannel) override; + + bool mContentStreamOpened; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_BlobURLChannel_h */ diff --git a/dom/file/uri/BlobURLInputStream.cpp b/dom/file/uri/BlobURLInputStream.cpp new file mode 100644 index 0000000000..34a1bb63e8 --- /dev/null +++ b/dom/file/uri/BlobURLInputStream.cpp @@ -0,0 +1,604 @@ +/* -*- 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 "BlobURLInputStream.h" +#include "BlobURL.h" +#include "BlobURLChannel.h" +#include "BlobURLProtocolHandler.h" + +#include "mozilla/ScopeExit.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "nsStreamUtils.h" +#include "nsMimeTypes.h" + +namespace mozilla::dom { + +NS_IMPL_ADDREF(BlobURLInputStream); +NS_IMPL_RELEASE(BlobURLInputStream); + +NS_INTERFACE_MAP_BEGIN(BlobURLInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength) + NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStreamLength) + NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIAsyncInputStream) +NS_INTERFACE_MAP_END + +/* static */ +already_AddRefed<nsIInputStream> BlobURLInputStream::Create( + BlobURLChannel* const aChannel, BlobURL* const aBlobURL) { + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!aChannel) || NS_WARN_IF(!aBlobURL)) { + return nullptr; + } + + nsAutoCString spec; + + nsresult rv = aBlobURL->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return MakeAndAddRef<BlobURLInputStream>(aChannel, spec); +} + +// from nsIInputStream interface +NS_IMETHODIMP BlobURLInputStream::Close() { + return CloseWithStatus(NS_BASE_STREAM_CLOSED); +} + +NS_IMETHODIMP BlobURLInputStream::Available(uint64_t* aLength) { + MutexAutoLock lock(mStateMachineMutex); + + if (mState == State::ERROR) { + MOZ_ASSERT(NS_FAILED(mError)); + return mError; + } + + if (mState == State::CLOSED) { + return NS_BASE_STREAM_CLOSED; + } + + if (mState == State::READY) { + MOZ_ASSERT(mAsyncInputStream); + return mAsyncInputStream->Available(aLength); + } + + return NS_OK; +} + +NS_IMETHODIMP BlobURLInputStream::StreamStatus() { + MutexAutoLock lock(mStateMachineMutex); + + if (mState == State::ERROR) { + MOZ_ASSERT(NS_FAILED(mError)); + return mError; + } + + if (mState == State::CLOSED) { + return NS_BASE_STREAM_CLOSED; + } + + if (mState == State::READY) { + MOZ_ASSERT(mAsyncInputStream); + return mAsyncInputStream->StreamStatus(); + } + + return NS_OK; +} + +NS_IMETHODIMP BlobURLInputStream::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) { + MutexAutoLock lock(mStateMachineMutex); + if (mState == State::ERROR) { + MOZ_ASSERT(NS_FAILED(mError)); + return mError; + } + + // Read() should not return NS_BASE_STREAM_CLOSED if stream is closed. + // A read count of 0 should indicate closed or consumed stream. + // See: + // https://searchfox.org/mozilla-central/rev/559b25eb41c1cbffcb90a34e008b8288312fcd25/xpcom/io/nsIInputStream.idl#104 + if (mState == State::CLOSED) { + *aReadCount = 0; + return NS_OK; + } + + if (mState == State::READY) { + MOZ_ASSERT(mAsyncInputStream); + nsresult rv = mAsyncInputStream->Read(aBuffer, aCount, aReadCount); + if (NS_SUCCEEDED(rv) && aReadCount && !*aReadCount) { + mState = State::CLOSED; + ReleaseUnderlyingStream(lock); + } + return rv; + } + + return NS_BASE_STREAM_WOULD_BLOCK; +} + +NS_IMETHODIMP BlobURLInputStream::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* aResult) { + // This means the caller will have to wrap the stream in an + // nsBufferedInputStream in order to use ReadSegments + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP BlobURLInputStream::IsNonBlocking(bool* aNonBlocking) { + *aNonBlocking = true; + return NS_OK; +} + +// from nsIAsyncInputStream interface +NS_IMETHODIMP BlobURLInputStream::CloseWithStatus(nsresult aStatus) { + MutexAutoLock lock(mStateMachineMutex); + if (mState == State::READY) { + MOZ_ASSERT(mAsyncInputStream); + mAsyncInputStream->CloseWithStatus(aStatus); + } + + mState = State::CLOSED; + ReleaseUnderlyingStream(lock); + return NS_OK; +} + +NS_IMETHODIMP BlobURLInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) { + MutexAutoLock lock(mStateMachineMutex); + + if (mState == State::ERROR) { + MOZ_ASSERT(NS_FAILED(mError)); + return NS_ERROR_FAILURE; + } + + // Pre-empting a valid callback with another is not allowed. + if (NS_WARN_IF(mAsyncWaitCallback && aCallback && + mAsyncWaitCallback != aCallback)) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitTarget = aEventTarget; + mAsyncWaitRequestedCount = aRequestedCount; + mAsyncWaitFlags = aFlags; + mAsyncWaitCallback = aCallback; + + if (mState == State::INITIAL) { + mState = State::WAITING; + // RetrieveBlobData will execute NotifyWWaitTarget() when retrieve succeeds + // or fails + if (NS_IsMainThread()) { + RetrieveBlobData(lock); + return NS_OK; + } + + nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod( + "BlobURLInputStream::CallRetrieveBlobData", this, + &BlobURLInputStream::CallRetrieveBlobData); + NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL); + return NS_OK; + } + + if (mState == State::WAITING) { + // RetrieveBlobData is already in progress and will execute + // NotifyWaitTargets when retrieve succeeds or fails + return NS_OK; + } + + if (mState == State::READY) { + // Ask the blob's input stream if reading is possible or not + return mAsyncInputStream->AsyncWait( + mAsyncWaitCallback ? this : nullptr, mAsyncWaitFlags, + mAsyncWaitRequestedCount, mAsyncWaitTarget); + } + + MOZ_ASSERT(mState == State::CLOSED); + NotifyWaitTargets(lock); + return NS_OK; +} + +// from nsIInputStreamLength interface +NS_IMETHODIMP BlobURLInputStream::Length(int64_t* aLength) { + MutexAutoLock lock(mStateMachineMutex); + + if (mState == State::CLOSED) { + return NS_BASE_STREAM_CLOSED; + } + + if (mState == State::ERROR) { + MOZ_ASSERT(NS_FAILED(mError)); + return NS_ERROR_FAILURE; + } + + if (mState == State::READY) { + *aLength = mBlobSize; + return NS_OK; + } + return NS_BASE_STREAM_WOULD_BLOCK; +} + +// from nsIAsyncInputStreamLength interface +NS_IMETHODIMP BlobURLInputStream::AsyncLengthWait( + nsIInputStreamLengthCallback* aCallback, nsIEventTarget* aEventTarget) { + MutexAutoLock lock(mStateMachineMutex); + + if (mState == State::ERROR) { + MOZ_ASSERT(NS_FAILED(mError)); + return mError; + } + + // Pre-empting a valid callback with another is not allowed. + if (mAsyncLengthWaitCallback && aCallback) { + return NS_ERROR_FAILURE; + } + + mAsyncLengthWaitTarget = aEventTarget; + mAsyncLengthWaitCallback = aCallback; + + if (mState == State::INITIAL) { + mState = State::WAITING; + // RetrieveBlobData will execute NotifyWWaitTarget() when retrieve succeeds + // or fails + if (NS_IsMainThread()) { + RetrieveBlobData(lock); + return NS_OK; + } + + nsCOMPtr<nsIRunnable> runnable = mozilla::NewRunnableMethod( + "BlobURLInputStream::CallRetrieveBlobData", this, + &BlobURLInputStream::CallRetrieveBlobData); + NS_DispatchToMainThread(runnable.forget(), NS_DISPATCH_NORMAL); + return NS_OK; + } + + if (mState == State::WAITING) { + // RetrieveBlobData is already in progress and will execute + // NotifyWaitTargets when retrieve succeeds or fails + return NS_OK; + } + + // Since here the state must be READY (in which case the size of the blob is + // already known) or CLOSED, callback can be called immediately + NotifyWaitTargets(lock); + return NS_OK; +} + +// from nsIInputStreamCallback interface +NS_IMETHODIMP BlobURLInputStream::OnInputStreamReady( + nsIAsyncInputStream* aStream) { + nsCOMPtr<nsIInputStreamCallback> callback; + + { + MutexAutoLock lock(mStateMachineMutex); + MOZ_ASSERT_IF(mAsyncInputStream, aStream == mAsyncInputStream); + + // aborted in the meantime + if (!mAsyncWaitCallback) { + return NS_OK; + } + + mAsyncWaitCallback.swap(callback); + mAsyncWaitTarget = nullptr; + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamReady(this); +} + +// from nsIInputStreamLengthCallback interface +NS_IMETHODIMP BlobURLInputStream::OnInputStreamLengthReady( + nsIAsyncInputStreamLength* aStream, int64_t aLength) { + nsCOMPtr<nsIInputStreamLengthCallback> callback; + { + MutexAutoLock lock(mStateMachineMutex); + + // aborted in the meantime + if (!mAsyncLengthWaitCallback) { + return NS_OK; + } + + mAsyncLengthWaitCallback.swap(callback); + mAsyncLengthWaitCallback = nullptr; + } + + return callback->OnInputStreamLengthReady(this, aLength); +} + +// private: +BlobURLInputStream::~BlobURLInputStream() { + if (mChannel) { + NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", mChannel.forget()); + } +} + +BlobURLInputStream::BlobURLInputStream(BlobURLChannel* const aChannel, + nsACString& aBlobURLSpec) + : mChannel(aChannel), + mBlobURLSpec(std::move(aBlobURLSpec)), + mStateMachineMutex("BlobURLInputStream::mStateMachineMutex"), + mState(State::INITIAL), + mError(NS_OK), + mBlobSize(-1), + mAsyncWaitFlags(), + mAsyncWaitRequestedCount() {} + +void BlobURLInputStream::WaitOnUnderlyingStream( + const MutexAutoLock& aProofOfLock) { + if (mAsyncWaitCallback || mAsyncWaitTarget) { + // AsyncWait should be called on the underlying stream + mAsyncInputStream->AsyncWait(mAsyncWaitCallback ? this : nullptr, + mAsyncWaitFlags, mAsyncWaitRequestedCount, + mAsyncWaitTarget); + } + + if (mAsyncLengthWaitCallback || mAsyncLengthWaitTarget) { + // AsyncLengthWait should be called on the underlying stream + nsCOMPtr<nsIAsyncInputStreamLength> asyncStreamLength = + do_QueryInterface(mAsyncInputStream); + if (asyncStreamLength) { + asyncStreamLength->AsyncLengthWait( + mAsyncLengthWaitCallback ? this : nullptr, mAsyncLengthWaitTarget); + } + } +} + +void BlobURLInputStream::CallRetrieveBlobData() { + MutexAutoLock lock(mStateMachineMutex); + RetrieveBlobData(lock); +} + +void BlobURLInputStream::RetrieveBlobData(const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); + + MOZ_ASSERT(mState == State::WAITING); + + auto cleanupOnEarlyExit = MakeScopeExit([&] { + mState = State::ERROR; + mError = NS_ERROR_FAILURE; + NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", mChannel.forget()); + NotifyWaitTargets(aProofOfLock); + }); + + nsCOMPtr<nsILoadInfo> loadInfo = mChannel->LoadInfo(); + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIPrincipal> loadingPrincipal; + if (NS_WARN_IF(NS_FAILED(loadInfo->GetTriggeringPrincipal( + getter_AddRefs(triggeringPrincipal)))) || + NS_WARN_IF(!triggeringPrincipal)) { + NS_WARNING("Failed to get owning channel's triggering principal"); + return; + } + + if (NS_WARN_IF(NS_FAILED( + loadInfo->GetLoadingPrincipal(getter_AddRefs(loadingPrincipal))))) { + NS_WARNING("Failed to get owning channel's loading principal"); + return; + } + + nsCOMPtr<nsICookieJarSettings> cookieJarSettings; + loadInfo->GetCookieJarSettings(getter_AddRefs(cookieJarSettings)); + + nsAutoString partKey; + cookieJarSettings->GetPartitionKey(partKey); + + if (XRE_IsParentProcess() || !BlobURLSchemeIsHTTPOrHTTPS(mBlobURLSpec)) { + RefPtr<BlobImpl> blobImpl; + + // Since revoked blobs are also retrieved, it is possible that the blob no + // longer exists (due to the 5 second timeout) when execution reaches here + if (!BlobURLProtocolHandler::GetDataEntry( + mBlobURLSpec, getter_AddRefs(blobImpl), loadingPrincipal, + triggeringPrincipal, loadInfo->GetOriginAttributes(), + loadInfo->GetInnerWindowID(), NS_ConvertUTF16toUTF8(partKey), + true /* AlsoIfRevoked */)) { + NS_WARNING("Failed to get data entry principal. URL revoked?"); + return; + } + + if (NS_WARN_IF( + NS_FAILED(StoreBlobImplStream(blobImpl.forget(), aProofOfLock)))) { + return; + } + + mState = State::READY; + + // By design, execution can only reach here when a caller has called + // AsyncWait or AsyncLengthWait on this stream. The underlying stream is + // valid, but the caller should not be informed until that stream has data + // to read or it is closed. + WaitOnUnderlyingStream(aProofOfLock); + + cleanupOnEarlyExit.release(); + return; + } + + ContentChild* contentChild{ContentChild::GetSingleton()}; + MOZ_ASSERT(contentChild); + + const RefPtr<BlobURLInputStream> self = this; + + cleanupOnEarlyExit.release(); + + contentChild + ->SendBlobURLDataRequest( + mBlobURLSpec, triggeringPrincipal, loadingPrincipal, + loadInfo->GetOriginAttributes(), loadInfo->GetInnerWindowID(), + NS_ConvertUTF16toUTF8(partKey)) + ->Then( + GetCurrentSerialEventTarget(), __func__, + [self](const BlobURLDataRequestResult& aResult) { + MutexAutoLock lock(self->mStateMachineMutex); + if (aResult.type() == BlobURLDataRequestResult::TIPCBlob) { + if (self->mState == State::WAITING) { + RefPtr<BlobImpl> blobImpl = + IPCBlobUtils::Deserialize(aResult.get_IPCBlob()); + if (blobImpl && self->StoreBlobImplStream(blobImpl.forget(), + lock) == NS_OK) { + self->mState = State::READY; + // By design, execution can only reach here when a caller has + // called AsyncWait or AsyncLengthWait on this stream. The + // underlying stream is valid, but the caller should not be + // informed until that stream has data to read or it is + // closed. + self->WaitOnUnderlyingStream(lock); + return; + } + } else { + MOZ_ASSERT(self->mState == State::CLOSED); + // Callback can be called immediately + self->NotifyWaitTargets(lock); + return; + } + } + NS_WARNING("Blob data was not retrieved!"); + self->mState = State::ERROR; + self->mError = aResult.type() == BlobURLDataRequestResult::Tnsresult + ? aResult.get_nsresult() + : NS_ERROR_FAILURE; + NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", + self->mChannel.forget()); + self->NotifyWaitTargets(lock); + }, + [self](mozilla::ipc::ResponseRejectReason aReason) { + MutexAutoLock lock(self->mStateMachineMutex); + NS_WARNING("IPC call to SendBlobURLDataRequest failed!"); + self->mState = State::ERROR; + self->mError = NS_ERROR_FAILURE; + NS_ReleaseOnMainThread("BlobURLInputStream::mChannel", + self->mChannel.forget()); + self->NotifyWaitTargets(lock); + }); +} + +nsresult BlobURLInputStream::StoreBlobImplStream( + already_AddRefed<BlobImpl> aBlobImpl, const MutexAutoLock& aProofOfLock) { + MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread"); + RefPtr<BlobImpl> blobImpl = aBlobImpl; + nsAutoString blobContentType; + nsAutoCString channelContentType; + + // If a Range header was in the request then fetch/XHR will have set a + // ContentRange on the channel earlier so we may slice the blob now. + blobImpl->GetType(blobContentType); + const Maybe<nsBaseChannel::ContentRange>& contentRange = + mChannel->GetContentRange(); + if (contentRange.isSome()) { + IgnoredErrorResult result; + uint64_t start = contentRange->Start(); + uint64_t end = contentRange->End(); + RefPtr<BlobImpl> slice = + blobImpl->CreateSlice(start, end - start + 1, blobContentType, result); + if (!result.Failed()) { + blobImpl = slice; + } + } + + mChannel->GetContentType(channelContentType); + // A empty content type is the correct channel content type in the case of a + // fetch of a blob where the type was not set. It is invalid in others cases + // such as a XHR (See https://xhr.spec.whatwg.org/#response-mime-type). The + // XMLHttpRequestMainThread will set the channel content type to the correct + // fallback value before this point, so we need to be careful to only override + // it when the blob type is valid. + if (!blobContentType.IsEmpty() || + channelContentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { + mChannel->SetContentType(NS_ConvertUTF16toUTF8(blobContentType)); + } + + auto cleanupOnExit = MakeScopeExit([&] { mChannel = nullptr; }); + + if (blobImpl->IsFile()) { + nsAutoString filename; + blobImpl->GetName(filename); + + // Don't overwrite existing name. + nsString ignored; + bool hasName = + NS_SUCCEEDED(mChannel->GetContentDispositionFilename(ignored)); + + if (!filename.IsEmpty() && !hasName) { + mChannel->SetContentDispositionFilename(filename); + } + } + + mozilla::ErrorResult errorResult; + + mBlobSize = blobImpl->GetSize(errorResult); + + if (NS_WARN_IF(errorResult.Failed())) { + return errorResult.StealNSResult(); + } + + mChannel->SetContentLength(mBlobSize); + + nsCOMPtr<nsIInputStream> inputStream; + blobImpl->CreateInputStream(getter_AddRefs(inputStream), errorResult); + + if (NS_WARN_IF(errorResult.Failed())) { + return errorResult.StealNSResult(); + } + + if (NS_WARN_IF(!inputStream)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = NS_MakeAsyncNonBlockingInputStream( + inputStream.forget(), getter_AddRefs(mAsyncInputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_WARN_IF(!mAsyncInputStream)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return NS_OK; +} + +void BlobURLInputStream::NotifyWaitTargets(const MutexAutoLock& aProofOfLock) { + if (mAsyncWaitCallback) { + auto callback = mAsyncWaitTarget + ? NS_NewInputStreamReadyEvent( + "BlobURLInputStream::OnInputStreamReady", + mAsyncWaitCallback, mAsyncWaitTarget) + : mAsyncWaitCallback; + + mAsyncWaitCallback = nullptr; + mAsyncWaitTarget = nullptr; + callback->OnInputStreamReady(this); + } + + if (mAsyncLengthWaitCallback) { + const RefPtr<BlobURLInputStream> self = this; + nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction( + "BlobURLInputStream::OnInputStreamLengthReady", [self] { + self->mAsyncLengthWaitCallback->OnInputStreamLengthReady( + self, self->mBlobSize); + }); + + mAsyncLengthWaitCallback = nullptr; + + if (mAsyncLengthWaitTarget) { + mAsyncLengthWaitTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + mAsyncLengthWaitTarget = nullptr; + } else { + runnable->Run(); + } + } +} + +void BlobURLInputStream::ReleaseUnderlyingStream( + const MutexAutoLock& aProofOfLock) { + mAsyncInputStream = nullptr; + mBlobSize = -1; +} + +} // namespace mozilla::dom diff --git a/dom/file/uri/BlobURLInputStream.h b/dom/file/uri/BlobURLInputStream.h new file mode 100644 index 0000000000..b9215e5ffd --- /dev/null +++ b/dom/file/uri/BlobURLInputStream.h @@ -0,0 +1,81 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_BlobURLInputStream_h +#define mozilla_dom_BlobURLInputStream_h + +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/Mutex.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsIInputStreamLength.h" + +namespace mozilla::dom { + +class BlobURL; +class BlobURLChannel; +class BlobURLInputStream final : public nsIAsyncInputStream, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamCallback, + public nsIInputStreamLengthCallback { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK + + static already_AddRefed<nsIInputStream> Create(BlobURLChannel* const aChannel, + BlobURL* const aBlobURL); + + BlobURLInputStream(BlobURLChannel* const aChannel, nsACString& aBlobURLSpec); + + private: + enum class State { INITIAL, READY, WAITING, CLOSED, ERROR }; + + ~BlobURLInputStream(); + + void WaitOnUnderlyingStream(const MutexAutoLock& aProofOfLock); + + // This method should only be used to call RetrieveBlobData in a different + // thread + void CallRetrieveBlobData(); + + void RetrieveBlobData(const MutexAutoLock& aProofOfLock); + + nsresult StoreBlobImplStream(already_AddRefed<BlobImpl> aBlobImpl, + const MutexAutoLock& aProofOfLock); + void NotifyWaitTargets(const MutexAutoLock& aProofOfLock); + void ReleaseUnderlyingStream(const MutexAutoLock& aProofOfLock); + + RefPtr<BlobURLChannel> mChannel; + const nsCString mBlobURLSpec; + + // Non-recursive mutex introduced in order to guard access to mState, mError + // and mAsyncInputStream + Mutex mStateMachineMutex MOZ_UNANNOTATED; + State mState; + // Stores the error code if stream is in error state + nsresult mError; + + int64_t mBlobSize; + + nsCOMPtr<nsIAsyncInputStream> mAsyncInputStream; + nsCOMPtr<nsIInputStreamCallback> mAsyncWaitCallback; + nsCOMPtr<nsIEventTarget> mAsyncWaitTarget; + uint32_t mAsyncWaitFlags; + uint32_t mAsyncWaitRequestedCount; + + nsCOMPtr<nsIInputStreamLengthCallback> mAsyncLengthWaitCallback; + nsCOMPtr<nsIEventTarget> mAsyncLengthWaitTarget; +}; + +} // namespace mozilla::dom + +#endif /* mozilla_dom_BlobURLInputStream_h */ diff --git a/dom/file/uri/BlobURLProtocolHandler.cpp b/dom/file/uri/BlobURLProtocolHandler.cpp new file mode 100644 index 0000000000..3e2d8e788e --- /dev/null +++ b/dom/file/uri/BlobURLProtocolHandler.cpp @@ -0,0 +1,1016 @@ +/* -*- 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 "BlobURLProtocolHandler.h" +#include "BlobURLChannel.h" +#include "mozilla/dom/BlobURL.h" + +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/Exceptions.h" +#include "mozilla/dom/BlobImpl.h" +#include "mozilla/dom/IPCBlobUtils.h" +#include "mozilla/dom/MediaSource.h" +#include "mozilla/ipc/IPCStreamUtils.h" +#include "mozilla/glean/GleanMetrics.h" +#include "mozilla/AppShutdown.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Maybe.h" +#include "mozilla/NullPrincipal.h" +#include "mozilla/OriginAttributes.h" +#include "mozilla/Preferences.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/ScopeExit.h" +#include "nsClassHashtable.h" +#include "nsContentUtils.h" +#include "nsError.h" +#include "nsIAsyncShutdown.h" +#include "nsIDUtils.h" +#include "nsIException.h" // for nsIStackFrame +#include "nsIMemoryReporter.h" +#include "nsIPrincipal.h" +#include "nsIUUIDGenerator.h" +#include "nsNetUtil.h" +#include "nsReadableUtils.h" + +#define RELEASING_TIMER 5000 + +namespace mozilla { + +using namespace ipc; + +namespace dom { + +// ----------------------------------------------------------------------- +// Hash table +struct DataInfo { + enum ObjectType { eBlobImpl, eMediaSource }; + + DataInfo(mozilla::dom::BlobImpl* aBlobImpl, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) + : mObjectType(eBlobImpl), + mBlobImpl(aBlobImpl), + mPrincipal(aPrincipal), + mPartitionKey(aPartitionKey), + mRevoked(false) { + MOZ_ASSERT(aPrincipal); + } + + DataInfo(MediaSource* aMediaSource, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) + : mObjectType(eMediaSource), + mMediaSource(aMediaSource), + mPrincipal(aPrincipal), + mPartitionKey(aPartitionKey), + mRevoked(false) { + MOZ_ASSERT(aPrincipal); + } + + ObjectType mObjectType; + + RefPtr<BlobImpl> mBlobImpl; + RefPtr<MediaSource> mMediaSource; + + nsCOMPtr<nsIPrincipal> mPrincipal; + + nsCString mPartitionKey; + + nsCString mStack; + + // When a blobURL is revoked, we keep it alive for RELEASING_TIMER + // milliseconds in order to support pending operations such as navigation, + // download and so on. + bool mRevoked; +}; + +// The mutex is locked whenever gDataTable is changed, or if gDataTable +// is accessed off-main-thread. +static StaticMutex sMutex MOZ_UNANNOTATED; + +// All changes to gDataTable must happen on the main thread, while locking +// sMutex. Reading from gDataTable on the main thread may happen without +// locking, since no changes are possible. Reading it from another thread +// must also lock sMutex to prevent data races. +static nsClassHashtable<nsCStringHashKey, mozilla::dom::DataInfo>* gDataTable; + +static mozilla::dom::DataInfo* GetDataInfo(const nsACString& aUri, + bool aAlsoIfRevoked = false) { + if (!gDataTable) { + return nullptr; + } + + // Let's remove any fragment from this URI. + int32_t fragmentPos = aUri.FindChar('#'); + + mozilla::dom::DataInfo* res; + if (fragmentPos < 0) { + res = gDataTable->Get(aUri); + } else { + res = gDataTable->Get(StringHead(aUri, fragmentPos)); + } + + if (!aAlsoIfRevoked && res && res->mRevoked) { + return nullptr; + } + + return res; +} + +static mozilla::dom::DataInfo* GetDataInfoFromURI(nsIURI* aURI, + bool aAlsoIfRevoked = false) { + if (!aURI) { + return nullptr; + } + + nsCString spec; + nsresult rv = aURI->GetSpec(spec); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return GetDataInfo(spec, aAlsoIfRevoked); +} + +// Memory reporting for the hash table. +void BroadcastBlobURLRegistration(const nsACString& aURI, + mozilla::dom::BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aBlobImpl); + MOZ_ASSERT(aPrincipal); + + if (XRE_IsParentProcess()) { + dom::ContentParent::BroadcastBlobURLRegistration(aURI, aBlobImpl, + aPrincipal, aPartitionKey); + return; + } + + IPCBlob ipcBlob; + nsresult rv = IPCBlobUtils::Serialize(aBlobImpl, ipcBlob); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + (void)NS_WARN_IF(!cc->SendStoreAndBroadcastBlobURLRegistration( + nsCString(aURI), ipcBlob, aPrincipal, aPartitionKey)); +} + +void BroadcastBlobURLUnregistration(const nsCString& aURI, + nsIPrincipal* aPrincipal) { + MOZ_ASSERT(NS_IsMainThread()); + + if (XRE_IsParentProcess()) { + dom::ContentParent::BroadcastBlobURLUnregistration(aURI, aPrincipal); + return; + } + + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + if (cc) { + (void)NS_WARN_IF( + !cc->SendUnstoreAndBroadcastBlobURLUnregistration(aURI, aPrincipal)); + } +} + +class BlobURLsReporter final : public nsIMemoryReporter { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD CollectReports(nsIHandleReportCallback* aCallback, + nsISupports* aData, bool aAnonymize) override { + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + if (!gDataTable) { + return NS_OK; + } + + nsTHashMap<nsPtrHashKey<mozilla::dom::BlobImpl>, uint32_t> refCounts; + + // Determine number of URLs per mozilla::dom::BlobImpl, to handle the case + // where it's > 1. + for (const auto& entry : *gDataTable) { + if (entry.GetWeak()->mObjectType != mozilla::dom::DataInfo::eBlobImpl) { + continue; + } + + mozilla::dom::BlobImpl* blobImpl = entry.GetWeak()->mBlobImpl; + MOZ_ASSERT(blobImpl); + + refCounts.LookupOrInsert(blobImpl, 0) += 1; + } + + for (const auto& entry : *gDataTable) { + nsCStringHashKey::KeyType key = entry.GetKey(); + mozilla::dom::DataInfo* info = entry.GetWeak(); + + if (entry.GetWeak()->mObjectType == mozilla::dom::DataInfo::eBlobImpl) { + mozilla::dom::BlobImpl* blobImpl = entry.GetWeak()->mBlobImpl; + MOZ_ASSERT(blobImpl); + + constexpr auto 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."_ns; + 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)(mozilla::dom::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(""_ns, path, KIND_OTHER, UNITS_BYTES, + size / refCount, descString, aData); + } else { + aCallback->Callback(""_ns, path, KIND_OTHER, UNITS_COUNT, 1, + descString, aData); + } + continue; + } + + // Just report the path for the MediaSource. + nsAutoCString path; + path = "media-source-urls/"; + BuildPath(path, key, info, aAnonymize); + + constexpr auto 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."_ns; + + aCallback->Callback(""_ns, 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(mozilla::dom::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; + + aInfo->mPrincipal->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; + + while (frame) { + nsString fileNameUTF16; + frame->GetFilename(cx, fileNameUTF16); + + int32_t lineNumber = frame->GetLineNumber(cx); + + 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 += ")/"; + } + + frame = frame->GetCaller(cx); + } + } + + private: + ~BlobURLsReporter() = default; + + static void BuildPath(nsAutoCString& path, nsCStringHashKey::KeyType aKey, + mozilla::dom::DataInfo* aInfo, bool anonymize) { + nsAutoCString url, owner; + aInfo->mPrincipal->GetAsciiSpec(owner); + if (!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 Runnable, + public nsITimerCallback, + public nsIAsyncShutdownBlocker { + public: + NS_DECL_ISUPPORTS_INHERITED + + static void Create(const nsACString& aURI) { + MOZ_ASSERT(NS_IsMainThread()); + + RefPtr<ReleasingTimerHolder> holder = new ReleasingTimerHolder(aURI); + + // BlobURLProtocolHandler::RemoveDataEntry potentially happens late. We are + // prepared to RevokeUri synchronously if we run after XPCOMWillShutdown, + // but we need at least to be able to dispatch to the main thread here. + auto raii = MakeScopeExit([holder] { holder->CancelTimerAndRevokeURI(); }); + + nsresult rv = SchedulerGroup::Dispatch(holder.forget()); + NS_ENSURE_SUCCESS_VOID(rv); + + raii.release(); + } + + // Runnable interface + + NS_IMETHOD + Run() override { + RefPtr<ReleasingTimerHolder> self = this; + auto raii = MakeScopeExit([self] { self->CancelTimerAndRevokeURI(); }); + + nsresult rv = NS_NewTimerWithCallback( + getter_AddRefs(mTimer), this, RELEASING_TIMER, nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, NS_OK); + + nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase(); + NS_ENSURE_TRUE(!!phase, NS_OK); + + rv = phase->AddBlocker(this, NS_LITERAL_STRING_FROM_CSTRING(__FILE__), + __LINE__, u"ReleasingTimerHolder shutdown"_ns); + NS_ENSURE_SUCCESS(rv, NS_OK); + + raii.release(); + return NS_OK; + } + + // nsITimerCallback interface + + NS_IMETHOD + Notify(nsITimer* aTimer) override { + RevokeURI(); + return NS_OK; + } + +#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY + using nsINamed::GetName; +#endif + + // nsIAsyncShutdownBlocker interface + + NS_IMETHOD + GetName(nsAString& aName) override { + aName.AssignLiteral("ReleasingTimerHolder for blobURL: "); + aName.Append(NS_ConvertUTF8toUTF16(mURI)); + return NS_OK; + } + + NS_IMETHOD + BlockShutdown(nsIAsyncShutdownClient* aClient) override { + CancelTimerAndRevokeURI(); + return NS_OK; + } + + NS_IMETHOD + GetState(nsIPropertyBag**) override { return NS_OK; } + + private: + explicit ReleasingTimerHolder(const nsACString& aURI) + : Runnable("ReleasingTimerHolder"), mURI(aURI) {} + + ~ReleasingTimerHolder() override = default; + + void RevokeURI() { + // Remove the shutting down blocker + nsCOMPtr<nsIAsyncShutdownClient> phase = GetShutdownPhase(); + if (phase) { + phase->RemoveBlocker(this); + } + + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + mozilla::dom::DataInfo* info = + GetDataInfo(mURI, true /* We care about revoked dataInfo */); + if (!info) { + // Already gone! + return; + } + + MOZ_ASSERT(info->mRevoked); + + StaticMutexAutoLock lock(sMutex); + gDataTable->Remove(mURI); + if (gDataTable->Count() == 0) { + delete gDataTable; + gDataTable = nullptr; + } + } + + void CancelTimerAndRevokeURI() { + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + RevokeURI(); + } + + static nsCOMPtr<nsIAsyncShutdownClient> GetShutdownPhase() { + nsCOMPtr<nsIAsyncShutdownService> svc = services::GetAsyncShutdownService(); + NS_ENSURE_TRUE(!!svc, nullptr); + + nsCOMPtr<nsIAsyncShutdownClient> phase; + nsresult rv = svc->GetXpcomWillShutdown(getter_AddRefs(phase)); + NS_ENSURE_SUCCESS(rv, nullptr); + + return phase; + } + + nsCString mURI; + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS_INHERITED(ReleasingTimerHolder, Runnable, nsITimerCallback, + nsIAsyncShutdownBlocker) + +template <typename T> +static void AddDataEntryInternal(const nsACString& aURI, T aObject, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) { + MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only"); + StaticMutexAutoLock lock(sMutex); + if (!gDataTable) { + gDataTable = new nsClassHashtable<nsCStringHashKey, mozilla::dom::DataInfo>; + } + + mozilla::UniquePtr<mozilla::dom::DataInfo> info = + mozilla::MakeUnique<mozilla::dom::DataInfo>(aObject, aPrincipal, + aPartitionKey); + BlobURLsReporter::GetJSStackForBlob(info.get()); + + gDataTable->InsertOrUpdate(aURI, std::move(info)); +} + +void BlobURLProtocolHandler::Init(void) { + static bool initialized = false; + + if (!initialized) { + initialized = true; + RegisterStrongMemoryReporter(new BlobURLsReporter()); + } +} + +BlobURLProtocolHandler::BlobURLProtocolHandler() { Init(); } + +BlobURLProtocolHandler::~BlobURLProtocolHandler() = default; + +/* static */ +nsresult BlobURLProtocolHandler::AddDataEntry(mozilla::dom::BlobImpl* aBlobImpl, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, + nsACString& aUri) { + MOZ_ASSERT(aBlobImpl); + MOZ_ASSERT(aPrincipal); + + Init(); + + nsresult rv = GenerateURIString(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + AddDataEntryInternal(aUri, aBlobImpl, aPrincipal, aPartitionKey); + + BroadcastBlobURLRegistration(aUri, aBlobImpl, aPrincipal, aPartitionKey); + return NS_OK; +} + +/* static */ +nsresult BlobURLProtocolHandler::AddDataEntry(MediaSource* aMediaSource, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, + nsACString& aUri) { + MOZ_ASSERT(aMediaSource); + MOZ_ASSERT(aPrincipal); + + Init(); + + nsresult rv = GenerateURIString(aPrincipal, aUri); + NS_ENSURE_SUCCESS(rv, rv); + + AddDataEntryInternal(aUri, aMediaSource, aPrincipal, aPartitionKey); + return NS_OK; +} + +/* static */ +void BlobURLProtocolHandler::AddDataEntry(const nsACString& aURI, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, + mozilla::dom::BlobImpl* aBlobImpl) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aBlobImpl); + AddDataEntryInternal(aURI, aBlobImpl, aPrincipal, aPartitionKey); +} + +/* static */ +bool BlobURLProtocolHandler::ForEachBlobURL( + std::function<bool(mozilla::dom::BlobImpl*, nsIPrincipal*, const nsCString&, + const nsACString&, bool aRevoked)>&& aCb) { + MOZ_ASSERT(NS_IsMainThread()); + + if (!gDataTable) { + return false; + } + + for (const auto& entry : *gDataTable) { + mozilla::dom::DataInfo* info = entry.GetWeak(); + MOZ_ASSERT(info); + + if (info->mObjectType != mozilla::dom::DataInfo::eBlobImpl) { + continue; + } + + MOZ_ASSERT(info->mBlobImpl); + if (!aCb(info->mBlobImpl, info->mPrincipal, info->mPartitionKey, + entry.GetKey(), info->mRevoked)) { + return false; + } + } + + return true; +} + +/*static */ +void BlobURLProtocolHandler::RemoveDataEntry(const nsACString& aUri, + bool aBroadcastToOtherProcesses) { + MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only"); + if (!gDataTable) { + return; + } + mozilla::dom::DataInfo* info = GetDataInfo(aUri); + if (!info) { + return; + } + + { + StaticMutexAutoLock lock(sMutex); + info->mRevoked = true; + } + + if (aBroadcastToOtherProcesses && + info->mObjectType == mozilla::dom::DataInfo::eBlobImpl) { + BroadcastBlobURLUnregistration(nsCString(aUri), info->mPrincipal); + } + + // The timer will take care of removing the entry for real after + // RELEASING_TIMER milliseconds. In the meantime, the mozilla::dom::DataInfo, + // marked as revoked, will not be exposed. + ReleasingTimerHolder::Create(aUri); +} + +/*static */ +bool BlobURLProtocolHandler::RemoveDataEntry(const nsACString& aUri, + nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey) { + MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only"); + if (!gDataTable) { + return false; + } + + mozilla::dom::DataInfo* info = GetDataInfo(aUri); + if (!info) { + return false; + } + + if (!aPrincipal || !aPrincipal->Subsumes(info->mPrincipal)) { + return false; + } + + if (StaticPrefs::privacy_partition_bloburl_per_partition_key() && + !aPartitionKey.IsEmpty() && !info->mPartitionKey.IsEmpty() && + !aPartitionKey.Equals(info->mPartitionKey)) { + return false; + } + + RemoveDataEntry(aUri, true); + return true; +} + +/* static */ +void BlobURLProtocolHandler::RemoveDataEntries() { + MOZ_ASSERT(NS_IsMainThread(), "changing gDataTable is main-thread only"); + StaticMutexAutoLock lock(sMutex); + if (!gDataTable) { + return; + } + + gDataTable->Clear(); + delete gDataTable; + gDataTable = nullptr; +} + +/* static */ +bool BlobURLProtocolHandler::HasDataEntry(const nsACString& aUri) { + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + return !!GetDataInfo(aUri); +} + +/* static */ +nsresult BlobURLProtocolHandler::GenerateURIString(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); + + aUri.AssignLiteral(BLOBURI_SCHEME); + aUri.Append(':'); + + if (aPrincipal) { + nsAutoCString origin; + rv = aPrincipal->GetWebExposedOriginSerialization(origin); + if (NS_FAILED(rv)) { + origin.AssignLiteral("null"); + } + + aUri.Append(origin); + aUri.Append('/'); + } + + aUri += NSID_TrimBracketsASCII(id); + + return NS_OK; +} + +/* static */ +bool BlobURLProtocolHandler::GetDataEntry( + const nsACString& aUri, mozilla::dom::BlobImpl** aBlobImpl, + nsIPrincipal* aLoadingPrincipal, nsIPrincipal* aTriggeringPrincipal, + const OriginAttributes& aOriginAttributes, uint64_t aInnerWindowId, + const nsCString& aPartitionKey, bool aAlsoIfRevoked) { + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + MOZ_ASSERT(aTriggeringPrincipal); + + if (!gDataTable) { + return false; + } + + mozilla::dom::DataInfo* info = GetDataInfo(aUri, aAlsoIfRevoked); + if (!info) { + return false; + } + + // We want to be sure that we stop the creation of the channel if the blob + // URL is copy-and-pasted on a different context (ex. private browsing or + // containers). + // + // We also allow the system principal to create the channel regardless of + // the OriginAttributes. This is primarily for the benefit of mechanisms + // like the Download API that explicitly create a channel with the system + // principal and which is never mutated to have a non-zero + // mPrivateBrowsingId or container. + + if ((NS_WARN_IF(!aLoadingPrincipal) || + !aLoadingPrincipal->IsSystemPrincipal()) && + NS_WARN_IF(!ChromeUtils::IsOriginAttributesEqualIgnoringFPD( + aOriginAttributes, + BasePrincipal::Cast(info->mPrincipal)->OriginAttributesRef()))) { + return false; + } + + if (NS_WARN_IF(!aTriggeringPrincipal->Subsumes(info->mPrincipal))) { + return false; + } + + if (StaticPrefs::privacy_partition_bloburl_per_partition_key() && + !aPartitionKey.IsEmpty() && !info->mPartitionKey.IsEmpty() && + !aPartitionKey.Equals(info->mPartitionKey)) { + mozilla::glean::bloburl::resolve_stopped.Add(); + nsAutoString localizedMsg; + AutoTArray<nsString, 1> param; + CopyUTF8toUTF16(aUri, *param.AppendElement()); + nsresult rv = nsContentUtils::FormatLocalizedString( + nsContentUtils::eDOM_PROPERTIES, "PartitionKeyDifferentError", param, + localizedMsg); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + nsContentUtils::ReportToConsoleByWindowID( + localizedMsg, nsIScriptError::errorFlag, "DOM"_ns, aInnerWindowId); + return false; + } + + RefPtr<mozilla::dom::BlobImpl> blobImpl = info->mBlobImpl; + blobImpl.forget(aBlobImpl); + + return true; +} + +/* static */ +void BlobURLProtocolHandler::Traverse( + const nsACString& aUri, nsCycleCollectionTraversalCallback& aCallback) { + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + if (!gDataTable) { + return; + } + + mozilla::dom::DataInfo* res; + gDataTable->Get(aUri, &res); + if (!res) { + return; + } + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( + aCallback, "BlobURLProtocolHandler mozilla::dom::DataInfo.mBlobImpl"); + aCallback.NoteXPCOMChild(res->mBlobImpl); + + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME( + aCallback, "BlobURLProtocolHandler mozilla::dom::DataInfo.mMediaSource"); + aCallback.NoteXPCOMChild(static_cast<EventTarget*>(res->mMediaSource)); +} + +NS_IMPL_ISUPPORTS(BlobURLProtocolHandler, nsIProtocolHandler, + nsISupportsWeakReference) + +/* static */ nsresult BlobURLProtocolHandler::CreateNewURI( + const nsACString& aSpec, const char* aCharset, nsIURI* aBaseURI, + nsIURI** aResult) { + *aResult = nullptr; + + // This method can be called on any thread, which is why we lock the mutex + // for read access to gDataTable. + bool revoked = true; + { + StaticMutexAutoLock lock(sMutex); + mozilla::dom::DataInfo* info = GetDataInfo(aSpec); + if (info && info->mObjectType == mozilla::dom::DataInfo::eBlobImpl) { + revoked = info->mRevoked; + } + } + + return NS_MutateURI(new BlobURL::Mutator()) + .SetSpec(aSpec) + .Apply(&nsIBlobURLMutator::SetRevoked, revoked) + .Finalize(aResult); +} + +NS_IMETHODIMP +BlobURLProtocolHandler::NewChannel(nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIChannel** aResult) { + auto channel = MakeRefPtr<BlobURLChannel>(aURI, aLoadInfo); + channel.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +BlobURLProtocolHandler::AllowPort(int32_t port, const char* scheme, + bool* _retval) { + // don't override anything. + *_retval = false; + return NS_OK; +} + +NS_IMETHODIMP +BlobURLProtocolHandler::GetScheme(nsACString& result) { + result.AssignLiteral(BLOBURI_SCHEME); + return NS_OK; +} + +/* static */ +bool BlobURLProtocolHandler::GetBlobURLPrincipal(nsIURI* aURI, + nsIPrincipal** aPrincipal) { + MOZ_ASSERT(aURI); + MOZ_ASSERT(aPrincipal); + + RefPtr<BlobURL> blobURL; + nsresult rv = + aURI->QueryInterface(kHOSTOBJECTURICID, getter_AddRefs(blobURL)); + if (NS_FAILED(rv) || !blobURL) { + return false; + } + + StaticMutexAutoLock lock(sMutex); + mozilla::dom::DataInfo* info = + GetDataInfoFromURI(aURI, true /*aAlsoIfRevoked */); + if (!info || info->mObjectType != mozilla::dom::DataInfo::eBlobImpl || + !info->mBlobImpl) { + return false; + } + + nsCOMPtr<nsIPrincipal> principal; + + if (blobURL->Revoked()) { + principal = NullPrincipal::Create( + BasePrincipal::Cast(info->mPrincipal)->OriginAttributesRef()); + } else { + principal = info->mPrincipal; + } + + principal.forget(aPrincipal); + return true; +} + +bool BlobURLProtocolHandler::IsBlobURLBroadcastPrincipal( + nsIPrincipal* aPrincipal) { + return aPrincipal->IsSystemPrincipal() || + aPrincipal->GetIsAddonOrExpandedAddonPrincipal(); +} + +} // namespace dom +} // namespace mozilla + +nsresult NS_GetBlobForBlobURI(nsIURI* aURI, mozilla::dom::BlobImpl** aBlob) { + *aBlob = nullptr; + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + mozilla::dom::DataInfo* info = + mozilla::dom::GetDataInfoFromURI(aURI, false /* aAlsoIfRevoked */); + if (!info || info->mObjectType != mozilla::dom::DataInfo::eBlobImpl) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<mozilla::dom::BlobImpl> blob = info->mBlobImpl; + blob.forget(aBlob); + return NS_OK; +} + +nsresult NS_GetBlobForBlobURISpec(const nsACString& aSpec, + mozilla::dom::BlobImpl** aBlob, + bool aAlsoIfRevoked) { + *aBlob = nullptr; + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + + mozilla::dom::DataInfo* info = + mozilla::dom::GetDataInfo(aSpec, aAlsoIfRevoked); + if (!info || info->mObjectType != mozilla::dom::DataInfo::eBlobImpl || + !info->mBlobImpl) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<mozilla::dom::BlobImpl> blob = info->mBlobImpl; + blob.forget(aBlob); + return NS_OK; +} + +// Blob requests may specify a range header. We parse, validate, and +// store that info here, and save it on the nsBaseChannel, where it +// can be accessed by BlobURLInputStream::StoreBlobImplStream. +nsresult NS_SetChannelContentRangeForBlobURI(nsIChannel* aChannel, nsIURI* aURI, + nsACString& aRangeHeader) { + MOZ_ASSERT(aChannel); + MOZ_ASSERT(aURI); + RefPtr<mozilla::dom::BlobImpl> blobImpl; + if (NS_FAILED(NS_GetBlobForBlobURI(aURI, getter_AddRefs(blobImpl)))) { + return NS_BINDING_FAILED; + } + mozilla::IgnoredErrorResult result; + int64_t size = static_cast<int64_t>(blobImpl->GetSize(result)); + if (result.Failed()) { + return NS_ERROR_NO_CONTENT; + } + nsBaseChannel* bchan = static_cast<nsBaseChannel*>(aChannel); + MOZ_ASSERT(bchan); + if (!bchan->SetContentRange(aRangeHeader, size)) { + return NS_ERROR_NET_PARTIAL_TRANSFER; + } + return NS_OK; +} + +nsresult NS_GetSourceForMediaSourceURI(nsIURI* aURI, + mozilla::dom::MediaSource** aSource) { + *aSource = nullptr; + + MOZ_ASSERT(NS_IsMainThread(), + "without locking gDataTable is main-thread only"); + mozilla::dom::DataInfo* info = mozilla::dom::GetDataInfoFromURI(aURI); + if (!info || info->mObjectType != mozilla::dom::DataInfo::eMediaSource) { + return NS_ERROR_DOM_BAD_URI; + } + + RefPtr<mozilla::dom::MediaSource> mediaSource = info->mMediaSource; + mediaSource.forget(aSource); + return NS_OK; +} + +namespace mozilla::dom { + +bool IsType(nsIURI* aUri, mozilla::dom::DataInfo::ObjectType aType) { + // We lock because this may be called off-main-thread + StaticMutexAutoLock lock(sMutex); + mozilla::dom::DataInfo* info = GetDataInfoFromURI(aUri); + if (!info) { + return false; + } + + return info->mObjectType == aType; +} + +bool IsBlobURI(nsIURI* aUri) { + return IsType(aUri, mozilla::dom::DataInfo::eBlobImpl); +} + +bool BlobURLSchemeIsHTTPOrHTTPS(const nsACString& aUri) { + return (StringBeginsWith(aUri, "blob:http://"_ns) || + StringBeginsWith(aUri, "blob:https://"_ns)); +} + +bool IsMediaSourceURI(nsIURI* aUri) { + return IsType(aUri, mozilla::dom::DataInfo::eMediaSource); +} + +} // namespace mozilla::dom diff --git a/dom/file/uri/BlobURLProtocolHandler.h b/dom/file/uri/BlobURLProtocolHandler.h new file mode 100644 index 0000000000..c34fcfbd4c --- /dev/null +++ b/dom/file/uri/BlobURLProtocolHandler.h @@ -0,0 +1,143 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_BlobURLProtocolHandler_h +#define mozilla_dom_BlobURLProtocolHandler_h + +#include "mozilla/Attributes.h" +#include "nsIProtocolHandler.h" +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsTArray.h" +#include "nsWeakReference.h" +#include <functional> + +#define BLOBURI_SCHEME "blob" + +class nsIPrincipal; + +namespace mozilla { +class BlobURLsReporter; +class OriginAttributes; +template <class T> +class Maybe; + +namespace dom { + +class BlobImpl; +class BlobURLRegistrationData; +class ContentParent; +class MediaSource; + +class BlobURLProtocolHandler final : public nsIProtocolHandler, + public nsSupportsWeakReference { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLHANDLER + + BlobURLProtocolHandler(); + + static nsresult CreateNewURI(const nsACString& aSpec, const char* aCharset, + nsIURI* aBaseURI, nsIURI** result); + + // Methods for managing uri->object mapping + // AddDataEntry creates the URI with the given scheme and returns it in aUri + static nsresult AddDataEntry(BlobImpl*, nsIPrincipal*, + const nsCString& aPartitionKey, + nsACString& aUri); + static nsresult AddDataEntry(MediaSource*, nsIPrincipal*, + const nsCString& aPartitionKey, + nsACString& aUri); + // IPC only + static void AddDataEntry(const nsACString& aURI, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey, BlobImpl* aBlobImpl); + + // These methods revoke a blobURL. Because some operations could still be in + // progress, the revoking consists in marking the blobURL as revoked and in + // removing it after RELEASING_TIMER milliseconds. + static void RemoveDataEntry(const nsACString& aUri, + bool aBroadcastToOTherProcesses = true); + // Returns true if the entry was allowed to be removed. + static bool RemoveDataEntry(const nsACString& aUri, nsIPrincipal* aPrincipal, + const nsCString& aPartitionKey); + + static void RemoveDataEntries(); + + static bool HasDataEntry(const nsACString& aUri); + + static bool GetDataEntry(const nsACString& aUri, BlobImpl** aBlobImpl, + nsIPrincipal* aLoadingPrincipal, + nsIPrincipal* aTriggeringPrincipal, + const OriginAttributes& aOriginAttributes, + uint64_t aInnerWindowId, + const nsCString& aPartitionKey, + bool aAlsoIfRevoked = false); + + static void Traverse(const nsACString& aUri, + nsCycleCollectionTraversalCallback& aCallback); + + // Main-thread only method to invoke a helper function that gets called for + // every known and recently revoked Blob URL. The helper function should + // return true to keep going or false to stop enumerating (presumably because + // of an unexpected XPCOM or IPC error). This method returns false if already + // shutdown or if the helper method returns false, true otherwise. + static bool ForEachBlobURL( + std::function<bool(BlobImpl*, nsIPrincipal*, const nsCString&, + const nsACString&, bool aRevoked)>&& aCb); + + // This method returns false if aURI is not a known BlobURL. Otherwise it + // returns true. + // + // When true is returned, the aPrincipal out param is meaningful. It gets + // set to the principal that a channel loaded from the blob would get if + // the blob is not already revoked and to a NullPrincipal if the blob is + // revoked. + // + // This means that for a revoked blob URL this method may either return + // false or return true and hand out a NullPrincipal in aPrincipal, + // depending on whether the "remove it from the hashtable" timer has + // fired. See RemoveDataEntry(). + static bool GetBlobURLPrincipal(nsIURI* aURI, nsIPrincipal** aPrincipal); + + // Check if metadata about Blob URLs created with this principal should be + // broadcast into every content process. This is currently the case for + // extension blob URLs and system principal blob URLs, as they can be loaded + // by system code and content scripts respectively. + static bool IsBlobURLBroadcastPrincipal(nsIPrincipal* aPrincipal); + + private: + ~BlobURLProtocolHandler(); + + static void Init(); + + // If principal is not null, its origin will be used to generate the URI. + static nsresult GenerateURIString(nsIPrincipal* aPrincipal, nsACString& aUri); +}; + +bool IsBlobURI(nsIURI* aUri); +bool IsMediaSourceURI(nsIURI* aUri); + +// Return true if inner scheme of blobURL is http or https, false otherwise. +bool BlobURLSchemeIsHTTPOrHTTPS(const nsACString& aUri); + +} // namespace dom +} // namespace mozilla + +extern nsresult NS_GetBlobForBlobURI(nsIURI* aURI, + mozilla::dom::BlobImpl** aBlob); + +extern nsresult NS_GetBlobForBlobURISpec(const nsACString& aSpec, + mozilla::dom::BlobImpl** aBlob, + bool aAlsoIfRevoked = false); + +extern nsresult NS_SetChannelContentRangeForBlobURI(nsIChannel* aChannel, + nsIURI* aURI, + nsACString& aRangeHeader); + +extern nsresult NS_GetSourceForMediaSourceURI( + nsIURI* aURI, mozilla::dom::MediaSource** aSource); + +#endif /* mozilla_dom_BlobURLProtocolHandler_h */ diff --git a/dom/file/uri/components.conf b/dom/file/uri/components.conf new file mode 100644 index 0000000000..190a3a6c54 --- /dev/null +++ b/dom/file/uri/components.conf @@ -0,0 +1,24 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Classes = [ + { + 'cid': '{b43964aa-a078-44b2-b06b-fd4d1b172e66}', + 'contract_ids': ['@mozilla.org/network/protocol;1?name=blob'], + 'type': 'mozilla::dom::BlobURLProtocolHandler', + 'headers': ['mozilla/dom/BlobURLProtocolHandler.h'], + 'protocol_config': { + 'scheme': 'blob', + 'flags': [ + 'URI_NORELATIVE', + 'URI_NOAUTH', + 'URI_LOADABLE_BY_SUBSUMERS', + 'URI_NON_PERSISTABLE', + 'URI_IS_LOCAL_RESOURCE', + ], + }, + }, +] diff --git a/dom/file/uri/moz.build b/dom/file/uri/moz.build new file mode 100644 index 0000000000..a1c4aed54c --- /dev/null +++ b/dom/file/uri/moz.build @@ -0,0 +1,34 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +with Files("**"): + BUG_COMPONENT = ("Core", "DOM: File") + +EXPORTS.mozilla.dom += [ + "BlobURL.h", + "BlobURLInputStream.h", + "BlobURLProtocolHandler.h", +] + +UNIFIED_SOURCES += [ + "BlobURL.cpp", + "BlobURLChannel.cpp", + "BlobURLInputStream.cpp", + "BlobURLProtocolHandler.cpp", +] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +LOCAL_INCLUDES += [ + "/dom/file", + "/netwerk/base", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" |