/* -*- 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 "IPCBlobInputStream.h" #include "IPCBlobInputStreamChild.h" #include "IPCBlobInputStreamStorage.h" #include "mozilla/ipc/InputStreamParams.h" #include "nsIAsyncInputStream.h" #include "nsIStreamTransportService.h" #include "nsITransport.h" #include "nsNetCID.h" #include "nsStringStream.h" #include "SlicedInputStream.h" namespace mozilla { namespace dom { namespace { static NS_DEFINE_CID(kStreamTransportServiceCID, NS_STREAMTRANSPORTSERVICE_CID); class InputStreamCallbackRunnable final : public CancelableRunnable { public: // Note that the execution can be synchronous in case the event target is // null. static void Execute(nsIInputStreamCallback* aCallback, nsIEventTarget* aEventTarget, IPCBlobInputStream* aStream) { MOZ_ASSERT(aCallback); RefPtr runnable = new InputStreamCallbackRunnable(aCallback, aStream); nsCOMPtr target = aEventTarget; if (aEventTarget) { target->Dispatch(runnable, NS_DISPATCH_NORMAL); } else { runnable->Run(); } } NS_IMETHOD Run() override { mCallback->OnInputStreamReady(mStream); mCallback = nullptr; mStream = nullptr; return NS_OK; } private: InputStreamCallbackRunnable(nsIInputStreamCallback* aCallback, IPCBlobInputStream* aStream) : CancelableRunnable("dom::InputStreamCallbackRunnable") , mCallback(aCallback) , mStream(aStream) { MOZ_ASSERT(mCallback); MOZ_ASSERT(mStream); } nsCOMPtr mCallback; RefPtr mStream; }; class FileMetadataCallbackRunnable final : public CancelableRunnable { public: static void Execute(nsIFileMetadataCallback* aCallback, nsIEventTarget* aEventTarget, IPCBlobInputStream* aStream) { MOZ_ASSERT(aCallback); MOZ_ASSERT(aEventTarget); RefPtr runnable = new FileMetadataCallbackRunnable(aCallback, aStream); nsCOMPtr target = aEventTarget; target->Dispatch(runnable, NS_DISPATCH_NORMAL); } NS_IMETHOD Run() override { mCallback->OnFileMetadataReady(mStream); mCallback = nullptr; mStream = nullptr; return NS_OK; } private: FileMetadataCallbackRunnable(nsIFileMetadataCallback* aCallback, IPCBlobInputStream* aStream) : CancelableRunnable("dom::FileMetadataCallbackRunnable") , mCallback(aCallback) , mStream(aStream) { MOZ_ASSERT(mCallback); MOZ_ASSERT(mStream); } nsCOMPtr mCallback; RefPtr mStream; }; } // anonymous NS_IMPL_ADDREF(IPCBlobInputStream); NS_IMPL_RELEASE(IPCBlobInputStream); NS_INTERFACE_MAP_BEGIN(IPCBlobInputStream) NS_INTERFACE_MAP_ENTRY(nsIInputStream) NS_INTERFACE_MAP_ENTRY(nsIAsyncInputStream) NS_INTERFACE_MAP_ENTRY(nsIInputStreamCallback) NS_INTERFACE_MAP_ENTRY(nsICloneableInputStream) NS_INTERFACE_MAP_ENTRY(nsICloneableInputStreamWithRange) NS_INTERFACE_MAP_ENTRY(nsIIPCSerializableInputStream) NS_INTERFACE_MAP_ENTRY(nsIFileMetadata) NS_INTERFACE_MAP_ENTRY(nsIAsyncFileMetadata) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) NS_INTERFACE_MAP_END IPCBlobInputStream::IPCBlobInputStream(IPCBlobInputStreamChild* aActor) : mActor(aActor) , mState(eInit) , mStart(0) , mLength(0) { MOZ_ASSERT(aActor); mLength = aActor->Size(); if (XRE_IsParentProcess()) { nsCOMPtr stream; IPCBlobInputStreamStorage::Get()->GetStream(mActor->ID(), 0, mLength, getter_AddRefs(stream)); if (stream) { mState = eRunning; mRemoteStream = stream; } } } IPCBlobInputStream::~IPCBlobInputStream() { Close(); } // nsIInputStream interface NS_IMETHODIMP IPCBlobInputStream::Available(uint64_t* aLength) { // We don't have a remoteStream yet. Let's return the full known size. if (mState == eInit || mState == ePending) { *aLength = 0; return NS_OK; } if (mState == eRunning) { MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); nsresult rv = EnsureAsyncRemoteStream(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(mAsyncRemoteStream); return mAsyncRemoteStream->Available(aLength); } MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } NS_IMETHODIMP IPCBlobInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) { // Read is not available is we don't have a remoteStream. if (mState == eInit || mState == ePending) { return NS_BASE_STREAM_WOULD_BLOCK; } if (mState == eRunning) { MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); nsresult rv = EnsureAsyncRemoteStream(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(mAsyncRemoteStream); return mAsyncRemoteStream->Read(aBuffer, aCount, aReadCount); } MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } NS_IMETHODIMP IPCBlobInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, uint32_t aCount, uint32_t *aResult) { // ReadSegments is not available is we don't have a remoteStream. if (mState == eInit || mState == ePending) { return NS_BASE_STREAM_WOULD_BLOCK; } if (mState == eRunning) { MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); nsresult rv = EnsureAsyncRemoteStream(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(mAsyncRemoteStream); return mAsyncRemoteStream->ReadSegments(aWriter, aClosure, aCount, aResult); } MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } NS_IMETHODIMP IPCBlobInputStream::IsNonBlocking(bool* aNonBlocking) { *aNonBlocking = true; return NS_OK; } NS_IMETHODIMP IPCBlobInputStream::Close() { if (mActor) { mActor->ForgetStream(this); mActor = nullptr; } if (mAsyncRemoteStream) { mAsyncRemoteStream->Close(); mAsyncRemoteStream = nullptr; } if (mRemoteStream) { mRemoteStream->Close(); mRemoteStream = nullptr; } mInputStreamCallback = nullptr; mInputStreamCallbackEventTarget = nullptr; mFileMetadataCallback = nullptr; mFileMetadataCallbackEventTarget = nullptr; mState = eClosed; return NS_OK; } // nsICloneableInputStream interface NS_IMETHODIMP IPCBlobInputStream::GetCloneable(bool* aCloneable) { *aCloneable = mState != eClosed; return NS_OK; } NS_IMETHODIMP IPCBlobInputStream::Clone(nsIInputStream** aResult) { if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } MOZ_ASSERT(mActor); RefPtr stream = mActor->CreateStream(); if (!stream) { return NS_ERROR_FAILURE; } stream->InitWithExistingRange(mStart, mLength); stream.forget(aResult); return NS_OK; } // nsICloneableInputStreamWithRange interface NS_IMETHODIMP IPCBlobInputStream::CloneWithRange(uint64_t aStart, uint64_t aLength, nsIInputStream** aResult) { if (mState == eClosed) { return NS_BASE_STREAM_CLOSED; } // Too short or out of range. if (aLength == 0 || aStart >= mLength) { return NS_NewCStringInputStream(aResult, EmptyCString()); } MOZ_ASSERT(mActor); RefPtr stream = mActor->CreateStream(); if (!stream) { return NS_ERROR_FAILURE; } CheckedInt streamSize = mLength; streamSize -= aStart; if (!streamSize.isValid()) { return NS_ERROR_FAILURE; } if (aLength > streamSize.value()) { aLength = streamSize.value(); } stream->InitWithExistingRange(aStart + mStart, aLength); stream.forget(aResult); return NS_OK; } // nsIAsyncInputStream interface NS_IMETHODIMP IPCBlobInputStream::CloseWithStatus(nsresult aStatus) { return Close(); } NS_IMETHODIMP IPCBlobInputStream::AsyncWait(nsIInputStreamCallback* aCallback, uint32_t aFlags, uint32_t aRequestedCount, nsIEventTarget* aEventTarget) { // See IPCBlobInputStream.h for more information about this state machine. switch (mState) { // First call, we need to retrieve the stream from the parent actor. case eInit: MOZ_ASSERT(mActor); mInputStreamCallback = aCallback; mInputStreamCallbackEventTarget = aEventTarget; mState = ePending; mActor->StreamNeeded(this, aEventTarget); return NS_OK; // We are still waiting for the remote inputStream case ePending: if (mInputStreamCallback && aCallback) { return NS_ERROR_FAILURE; } mInputStreamCallback = aCallback; mInputStreamCallbackEventTarget = aEventTarget; return NS_OK; // We have the remote inputStream, let's check if we can execute the callback. case eRunning: return MaybeExecuteInputStreamCallback(aCallback, aEventTarget); // Stream is closed. default: MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } } void IPCBlobInputStream::StreamReady(nsIInputStream* aInputStream) { // We have been closed in the meantime. if (mState == eClosed) { if (aInputStream) { aInputStream->Close(); } return; } // If aInputStream is null, it means that the serialization went wrong or the // stream is not available anymore. We keep the state as pending just to block // any additional operation. if (!aInputStream) { return; } // Now it's the right time to apply a slice if needed. if (mStart > 0 || mLength < mActor->Size()) { aInputStream = new SlicedInputStream(aInputStream, mStart, mLength); } mRemoteStream = aInputStream; MOZ_ASSERT(mState == ePending); mState = eRunning; nsCOMPtr fileMetadataCallback; fileMetadataCallback.swap(mFileMetadataCallback); nsCOMPtr fileMetadataCallbackEventTarget; fileMetadataCallbackEventTarget.swap(mFileMetadataCallbackEventTarget); if (fileMetadataCallback) { FileMetadataCallbackRunnable::Execute(fileMetadataCallback, fileMetadataCallbackEventTarget, this); } nsCOMPtr inputStreamCallback; inputStreamCallback.swap(mInputStreamCallback); nsCOMPtr inputStreamCallbackEventTarget; inputStreamCallbackEventTarget.swap(mInputStreamCallbackEventTarget); if (inputStreamCallback) { MaybeExecuteInputStreamCallback(inputStreamCallback, inputStreamCallbackEventTarget); } } nsresult IPCBlobInputStream::MaybeExecuteInputStreamCallback(nsIInputStreamCallback* aCallback, nsIEventTarget* aCallbackEventTarget) { MOZ_ASSERT(mState == eRunning); MOZ_ASSERT(mRemoteStream || mAsyncRemoteStream); // If the callback has been already set, we return an error. if (mInputStreamCallback && aCallback) { return NS_ERROR_FAILURE; } mInputStreamCallback = aCallback; mInputStreamCallbackEventTarget = aCallbackEventTarget; if (!mInputStreamCallback) { return NS_OK; } nsresult rv = EnsureAsyncRemoteStream(); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } MOZ_ASSERT(mAsyncRemoteStream); return mAsyncRemoteStream->AsyncWait(this, 0, 0, aCallbackEventTarget); } void IPCBlobInputStream::InitWithExistingRange(uint64_t aStart, uint64_t aLength) { MOZ_ASSERT(mActor->Size() >= aStart + aLength); mStart = aStart; mLength = aLength; // In the child, we slice in StreamReady() when we set mState to eRunning. // But in the parent, we start out eRunning, so it's necessary to slice the // stream as soon as we have the information during the initialization phase // because the stream is immediately consumable. if (mState == eRunning && mRemoteStream && XRE_IsParentProcess() && (mStart > 0 || mLength < mActor->Size())) { mRemoteStream = new SlicedInputStream(mRemoteStream, mStart, mLength); } } // nsIInputStreamCallback NS_IMETHODIMP IPCBlobInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) { // We have been closed in the meantime. if (mState == eClosed) { return NS_OK; } MOZ_ASSERT(mState == eRunning); MOZ_ASSERT(mAsyncRemoteStream == aStream); // The callback has been canceled in the meantime. if (!mInputStreamCallback) { return NS_OK; } nsCOMPtr callback; callback.swap(mInputStreamCallback); nsCOMPtr callbackEventTarget; callbackEventTarget.swap(mInputStreamCallbackEventTarget); // This must be the last operation because the execution of the callback can // be synchronous. InputStreamCallbackRunnable::Execute(callback, callbackEventTarget, this); return NS_OK; } // nsIIPCSerializableInputStream void IPCBlobInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, FileDescriptorArray& aFileDescriptors) { mozilla::ipc::IPCBlobInputStreamParams params; params.id() = mActor->ID(); params.start() = mStart; params.length() = mLength; aParams = params; } bool IPCBlobInputStream::Deserialize(const mozilla::ipc::InputStreamParams& aParams, const FileDescriptorArray& aFileDescriptors) { MOZ_CRASH("This should never be called."); return false; } mozilla::Maybe IPCBlobInputStream::ExpectedSerializedLength() { return mozilla::Nothing(); } // nsIAsyncFileMetadata NS_IMETHODIMP IPCBlobInputStream::AsyncWait(nsIFileMetadataCallback* aCallback, nsIEventTarget* aEventTarget) { MOZ_ASSERT(!!aCallback == !!aEventTarget); // If we have the callback, we must have the event target. if (NS_WARN_IF(!!aCallback != !!aEventTarget)) { return NS_ERROR_FAILURE; } // See IPCBlobInputStream.h for more information about this state machine. switch (mState) { // First call, we need to retrieve the stream from the parent actor. case eInit: MOZ_ASSERT(mActor); mFileMetadataCallback = aCallback; mFileMetadataCallbackEventTarget = aEventTarget; mState = ePending; mActor->StreamNeeded(this, aEventTarget); return NS_OK; // We are still waiting for the remote inputStream case ePending: if (mFileMetadataCallback && aCallback) { return NS_ERROR_FAILURE; } mFileMetadataCallback = aCallback; mFileMetadataCallbackEventTarget = aEventTarget; return NS_OK; // We have the remote inputStream, let's check if we can execute the callback. case eRunning: FileMetadataCallbackRunnable::Execute(aCallback, aEventTarget, this); return NS_OK; // Stream is closed. default: MOZ_ASSERT(mState == eClosed); return NS_BASE_STREAM_CLOSED; } } // nsIFileMetadata NS_IMETHODIMP IPCBlobInputStream::GetSize(int64_t* aRetval) { nsCOMPtr fileMetadata = do_QueryInterface(mRemoteStream); if (!fileMetadata) { return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; } return fileMetadata->GetSize(aRetval); } NS_IMETHODIMP IPCBlobInputStream::GetLastModified(int64_t* aRetval) { nsCOMPtr fileMetadata = do_QueryInterface(mRemoteStream); if (!fileMetadata) { return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; } return fileMetadata->GetLastModified(aRetval); } NS_IMETHODIMP IPCBlobInputStream::GetFileDescriptor(PRFileDesc** aRetval) { nsCOMPtr fileMetadata = do_QueryInterface(mRemoteStream); if (!fileMetadata) { return mState == eClosed ? NS_BASE_STREAM_CLOSED : NS_ERROR_FAILURE; } return fileMetadata->GetFileDescriptor(aRetval); } nsresult IPCBlobInputStream::EnsureAsyncRemoteStream() { // We already have an async remote stream. if (mAsyncRemoteStream) { return NS_OK; } if (!mRemoteStream) { return NS_ERROR_FAILURE; } // If the stream is blocking, we want to make it unblocking using a pipe. bool nonBlocking = false; nsresult rv = mRemoteStream->IsNonBlocking(&nonBlocking); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr asyncStream = do_QueryInterface(mRemoteStream); if (!asyncStream || !nonBlocking) { nsCOMPtr sts = do_GetService(kStreamTransportServiceCID, &rv); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr transport; rv = sts->CreateInputTransport(mRemoteStream, /* aCloseWhenDone */ true, getter_AddRefs(transport)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } nsCOMPtr wrapper; rv = transport->OpenInputStream(/* aFlags */ 0, /* aSegmentSize */ 0, /* aSegmentCount */ 0, getter_AddRefs(wrapper)); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } asyncStream = do_QueryInterface(wrapper); } MOZ_ASSERT(asyncStream); mAsyncRemoteStream = asyncStream; mRemoteStream = nullptr; return NS_OK; } } // namespace dom } // namespace mozilla