gecko-dev/xpcom/io/InputStreamLengthWrapper.cpp
Andrea Marchesini 4ebf5b4364 Bug 1496581 - Split nsISeekableStream in 2 classes: nsISeekableStream and nsITellableStream, f=mayhemer, r=froydnj
In the current code there are 3 main issues:

1. nsFileStream is not really thread-safe. There is nothing to protect the
internal members and we see crashes.

2. nsPipeInputStream doesn't implement ::Seek() method and that caused issues
in devtools when a nsHttpChannel sends POST data using a pipe. In order to fix
this, bug 1494176 added a check in nsHttpChannel: if the stream doesn't
implement ::Seek(), let's clone it. This was an hack around nsPipeInputStream,
and it's bad.

3. When nsHttpChannel sends POST data using a file stream, nsFileStream does
I/O on main-thread because of the issue 2. Plus, ::Seek() is called on the
main-thread causing issue 1.

Note that nsPipeInputStream implements only ::Tell(), of the nsISeekableStream
methods. It doesn't implement ::Seek() and it doesn't implement ::SetEOF().

With this patch I want to fix point 2 and point 3 (and consequentially issue 1
- but we need a separate fix for it - follow up). The patch does:

1. it splits nsISeekableStream in 2 interfaces: nsITellableStream and
nsISeekableStream.
2. nsPipeInputStream implements only nsITellableStream.  Doing this, we don't
need the ::Seek() check for point 2 in nsHttpChannel: a simple QI check is
enough.
3. Because we don't call ::Seek() in nsHttpChannel, nsFileStream doesn't do I/O
on the main-thread, and we don't crash doing so.
2018-10-18 13:35:35 +02:00

375 lines
10 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 "InputStreamLengthWrapper.h"
#include "mozilla/ipc/InputStreamUtils.h"
#include "nsISeekableStream.h"
#include "nsStreamUtils.h"
namespace mozilla {
using namespace ipc;
NS_IMPL_ADDREF(InputStreamLengthWrapper);
NS_IMPL_RELEASE(InputStreamLengthWrapper);
NS_INTERFACE_MAP_BEGIN(InputStreamLengthWrapper)
NS_INTERFACE_MAP_ENTRY(nsIInputStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream,
mWeakCloneableInputStream || !mInputStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
mWeakIPCSerializableInputStream || !mInputStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsISeekableStream,
mWeakSeekableInputStream || !mInputStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsITellableStream,
mWeakTellableInputStream || !mInputStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
mWeakAsyncInputStream || !mInputStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback,
mWeakAsyncInputStream || !mInputStream)
NS_INTERFACE_MAP_ENTRY(nsIInputStreamLength)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream)
NS_INTERFACE_MAP_END
/* static */ already_AddRefed<nsIInputStream>
InputStreamLengthWrapper::MaybeWrap(already_AddRefed<nsIInputStream> aInputStream,
int64_t aLength)
{
nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream);
MOZ_ASSERT(inputStream);
nsCOMPtr<nsIInputStreamLength> length = do_QueryInterface(inputStream);
if (length) {
return inputStream.forget();
}
nsCOMPtr<nsIAsyncInputStreamLength> asyncLength = do_QueryInterface(inputStream);
if (asyncLength) {
return inputStream.forget();
}
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(inputStream);
if (!asyncStream) {
return inputStream.forget();
}
inputStream = new InputStreamLengthWrapper(inputStream.forget(), aLength);
return inputStream.forget();
}
InputStreamLengthWrapper::InputStreamLengthWrapper(already_AddRefed<nsIInputStream> aInputStream,
int64_t aLength)
: mWeakCloneableInputStream(nullptr)
, mWeakIPCSerializableInputStream(nullptr)
, mWeakSeekableInputStream(nullptr)
, mWeakTellableInputStream(nullptr)
, mWeakAsyncInputStream(nullptr)
, mLength(aLength)
, mConsumed(false)
, mMutex("InputStreamLengthWrapper::mMutex")
{
MOZ_ASSERT(mLength >= 0);
nsCOMPtr<nsIInputStream> inputStream = std::move(aInputStream);
SetSourceStream(inputStream.forget());
}
InputStreamLengthWrapper::InputStreamLengthWrapper()
: mWeakCloneableInputStream(nullptr)
, mWeakIPCSerializableInputStream(nullptr)
, mWeakSeekableInputStream(nullptr)
, mWeakTellableInputStream(nullptr)
, mWeakAsyncInputStream(nullptr)
, mLength(-1)
, mConsumed(false)
, mMutex("InputStreamLengthWrapper::mMutex")
{}
InputStreamLengthWrapper::~InputStreamLengthWrapper() = default;
void
InputStreamLengthWrapper::SetSourceStream(already_AddRefed<nsIInputStream> aInputStream)
{
MOZ_ASSERT(!mInputStream);
mInputStream = std::move(aInputStream);
nsCOMPtr<nsICloneableInputStream> cloneableStream =
do_QueryInterface(mInputStream);
if (cloneableStream && SameCOMIdentity(mInputStream, cloneableStream)) {
mWeakCloneableInputStream = cloneableStream;
}
nsCOMPtr<nsIIPCSerializableInputStream> serializableStream =
do_QueryInterface(mInputStream);
if (serializableStream &&
SameCOMIdentity(mInputStream, serializableStream)) {
mWeakIPCSerializableInputStream = serializableStream;
}
nsCOMPtr<nsISeekableStream> seekableStream =
do_QueryInterface(mInputStream);
if (seekableStream && SameCOMIdentity(mInputStream, seekableStream)) {
mWeakSeekableInputStream = seekableStream;
}
nsCOMPtr<nsITellableStream> tellableStream =
do_QueryInterface(mInputStream);
if (tellableStream && SameCOMIdentity(mInputStream, tellableStream)) {
mWeakTellableInputStream = tellableStream;
}
nsCOMPtr<nsIAsyncInputStream> asyncInputStream =
do_QueryInterface(mInputStream);
if (asyncInputStream && SameCOMIdentity(mInputStream, asyncInputStream)) {
mWeakAsyncInputStream = asyncInputStream;
}
}
// nsIInputStream interface
NS_IMETHODIMP
InputStreamLengthWrapper::Close()
{
NS_ENSURE_STATE(mInputStream);
return mInputStream->Close();
}
NS_IMETHODIMP
InputStreamLengthWrapper::Available(uint64_t* aLength)
{
NS_ENSURE_STATE(mInputStream);
return mInputStream->Available(aLength);
}
NS_IMETHODIMP
InputStreamLengthWrapper::Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount)
{
NS_ENSURE_STATE(mInputStream);
mConsumed = true;
return mInputStream->Read(aBuffer, aCount, aReadCount);
}
NS_IMETHODIMP
InputStreamLengthWrapper::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
uint32_t aCount, uint32_t *aResult)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_IMETHODIMP
InputStreamLengthWrapper::IsNonBlocking(bool* aNonBlocking)
{
NS_ENSURE_STATE(mInputStream);
return mInputStream->IsNonBlocking(aNonBlocking);
}
// nsICloneableInputStream interface
NS_IMETHODIMP
InputStreamLengthWrapper::GetCloneable(bool* aCloneable)
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakCloneableInputStream);
mWeakCloneableInputStream->GetCloneable(aCloneable);
return NS_OK;
}
NS_IMETHODIMP
InputStreamLengthWrapper::Clone(nsIInputStream** aResult)
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakCloneableInputStream);
nsCOMPtr<nsIInputStream> clonedStream;
nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream));
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
nsCOMPtr<nsIInputStream> stream =
new InputStreamLengthWrapper(clonedStream.forget(), mLength);
stream.forget(aResult);
return NS_OK;
}
// nsIAsyncInputStream interface
NS_IMETHODIMP
InputStreamLengthWrapper::CloseWithStatus(nsresult aStatus)
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakAsyncInputStream);
mConsumed = true;
return mWeakAsyncInputStream->CloseWithStatus(aStatus);
}
NS_IMETHODIMP
InputStreamLengthWrapper::AsyncWait(nsIInputStreamCallback* aCallback,
uint32_t aFlags,
uint32_t aRequestedCount,
nsIEventTarget* aEventTarget)
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakAsyncInputStream);
nsCOMPtr<nsIInputStreamCallback> callback = this;
{
MutexAutoLock lock(mMutex);
if (mAsyncWaitCallback && aCallback) {
return NS_ERROR_FAILURE;
}
bool hadCallback = !!mAsyncWaitCallback;
mAsyncWaitCallback = aCallback;
if (!mAsyncWaitCallback) {
if (!hadCallback) {
// No pending operation.
return NS_OK;
}
// Abort current operation.
callback = nullptr;
}
}
return mWeakAsyncInputStream->AsyncWait(callback, aFlags, aRequestedCount,
aEventTarget);
}
// nsIInputStreamCallback
NS_IMETHODIMP
InputStreamLengthWrapper::OnInputStreamReady(nsIAsyncInputStream* aStream)
{
MOZ_ASSERT(mInputStream);
MOZ_ASSERT(mWeakAsyncInputStream);
MOZ_ASSERT(mWeakAsyncInputStream == aStream);
nsCOMPtr<nsIInputStreamCallback> callback;
{
MutexAutoLock lock(mMutex);
// We have been canceled in the meanwhile.
if (!mAsyncWaitCallback) {
return NS_OK;
}
callback.swap(mAsyncWaitCallback);
}
MOZ_ASSERT(callback);
return callback->OnInputStreamReady(this);
}
// nsIIPCSerializableInputStream
void
InputStreamLengthWrapper::Serialize(mozilla::ipc::InputStreamParams& aParams,
FileDescriptorArray& aFileDescriptors)
{
MOZ_ASSERT(mInputStream);
MOZ_ASSERT(mWeakIPCSerializableInputStream);
InputStreamLengthWrapperParams params;
InputStreamHelper::SerializeInputStream(mInputStream, params.stream(),
aFileDescriptors);
params.length() = mLength;
params.consumed() = mConsumed;
aParams = params;
}
bool
InputStreamLengthWrapper::Deserialize(const mozilla::ipc::InputStreamParams& aParams,
const FileDescriptorArray& aFileDescriptors)
{
MOZ_ASSERT(!mInputStream);
MOZ_ASSERT(!mWeakIPCSerializableInputStream);
if (aParams.type() !=
InputStreamParams::TInputStreamLengthWrapperParams) {
NS_ERROR("Received unknown parameters from the other process!");
return false;
}
const InputStreamLengthWrapperParams& params =
aParams.get_InputStreamLengthWrapperParams();
nsCOMPtr<nsIInputStream> stream =
InputStreamHelper::DeserializeInputStream(params.stream(),
aFileDescriptors);
if (!stream) {
NS_WARNING("Deserialize failed!");
return false;
}
SetSourceStream(stream.forget());
mLength = params.length();
mConsumed = params.consumed();
return true;
}
mozilla::Maybe<uint64_t>
InputStreamLengthWrapper::ExpectedSerializedLength()
{
if (!mInputStream || !mWeakIPCSerializableInputStream) {
return mozilla::Nothing();
}
return mWeakIPCSerializableInputStream->ExpectedSerializedLength();
}
// nsISeekableStream
NS_IMETHODIMP
InputStreamLengthWrapper::Seek(int32_t aWhence, int64_t aOffset)
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakSeekableInputStream);
mConsumed = true;
return mWeakSeekableInputStream->Seek(aWhence, aOffset);
}
NS_IMETHODIMP
InputStreamLengthWrapper::SetEOF()
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakSeekableInputStream);
mConsumed = true;
return mWeakSeekableInputStream->SetEOF();
}
// nsITellableStream
NS_IMETHODIMP
InputStreamLengthWrapper::Tell(int64_t *aResult)
{
NS_ENSURE_STATE(mInputStream);
NS_ENSURE_STATE(mWeakTellableInputStream);
return mWeakTellableInputStream->Tell(aResult);
}
// nsIInputStreamLength
NS_IMETHODIMP
InputStreamLengthWrapper::Length(int64_t* aLength)
{
NS_ENSURE_STATE(mInputStream);
*aLength = mLength;
return NS_OK;
}
} // namespace mozilla