Files
tubestation/dom/file/uri/BlobURLInputStream.cpp
Nika Layzell 2e6ea183a0 Bug 1818305 - Part 2: Add a streamStatus method to nsIInputStream, r=xpcom-reviewers,necko-reviewers,geckoview-reviewers,valentin,jesup,m_kato,mccr8
This is semantically similar to the existing available() method, however will
not block, and doesn't need to do the work to actually determine the number of
available bytes.

As part of this patch, I also fixed one available() implementation which was
incorrectly throwing NS_BASE_STREAM_WOULD_BLOCK.

Differential Revision: https://phabricator.services.mozilla.com/D170697
2023-03-15 19:52:34 +00:00

590 lines
18 KiB
C++

/* -*- 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