summaryrefslogtreecommitdiffstats
path: root/dom/file/uri/BlobURLInputStream.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/file/uri/BlobURLInputStream.cpp')
-rw-r--r--dom/file/uri/BlobURLInputStream.cpp589
1 files changed, 589 insertions, 0 deletions
diff --git a/dom/file/uri/BlobURLInputStream.cpp b/dom/file/uri/BlobURLInputStream.cpp
new file mode 100644
index 0000000000..7610281afc
--- /dev/null
+++ b/dom/file/uri/BlobURLInputStream.cpp
@@ -0,0 +1,589 @@
+/* -*- 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;
+ }
+
+ Maybe<nsID> agentClusterId;
+ Maybe<ClientInfo> clientInfo = loadInfo->GetClientInfo();
+ if (clientInfo.isSome()) {
+ agentClusterId = clientInfo->AgentClusterId();
+ }
+
+ 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(), agentClusterId,
+ 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(), agentClusterId)
+ ->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");
+ const RefPtr<BlobImpl> blobImpl = aBlobImpl;
+ nsAutoString blobContentType;
+ nsAutoCString channelContentType;
+
+ blobImpl->GetType(blobContentType);
+ 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