gecko-dev/netwerk/base/nsMIMEInputStream.cpp
Andrew Sutherland 34b0ef6798 Bug 1383518 - Part 2: nsMIMEInputStream should conditionally implement nsIAsyncInputStream. r=bkelly
NS_AsyncCopy aborts if it receives an NS_BASE_STREAM_WOULD_BLOCK error result
during copying and it is unable to QI the source stream to an
nsIAsyncInputStream.  IPCBlobInputStream can return this, especially if it's:
- A freshly created aggregate stream as part of form submission of a type=file
  where the Blob will come from the parent because of the file picker but the
  stream is being uploaded from the child.
- A ServiceWorker is involved, causing
  HttpBaseChannel::EnsureUploadStreamIsCloneable to trigger an NS_AsyncCopy
  very early in the process.

IPCBlobInputStream implements nsIAsyncInputStream, and nsMultiplexInputStream
does too (conditionally based on its child streams; if any are async, it takes
step to uniformly expose async behavior).  However, due to lack of sufficient
test coverage, nsMIMEInputStream did not get fixed as part of bug 1361443 when
nsMultiplexInputStream gained its nsIAsyncInputStream powers.  We address that
here in the same fashion.

Part 1 of this series addresses the test coverage issue.

--HG--
extra : rebase_source : 1cae03a314397b159c3985d97231c1e34cd5f079
2017-08-23 06:39:36 -04:00

369 lines
11 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/* 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/. */
/**
* The MIME stream separates headers and a datastream. It also allows
* automatic creation of the content-length header.
*/
#include "ipc/IPCMessageUtils.h"
#include "nsCOMPtr.h"
#include "nsComponentManagerUtils.h"
#include "nsIAsyncInputStream.h"
#include "nsIHttpHeaderVisitor.h"
#include "nsIMIMEInputStream.h"
#include "nsISeekableStream.h"
#include "nsString.h"
#include "nsMIMEInputStream.h"
#include "nsIClassInfoImpl.h"
#include "nsIIPCSerializableInputStream.h"
#include "mozilla/Move.h"
#include "mozilla/ipc/InputStreamUtils.h"
using namespace mozilla::ipc;
using mozilla::Maybe;
using mozilla::Move;
class nsMIMEInputStream : public nsIMIMEInputStream,
public nsISeekableStream,
public nsIIPCSerializableInputStream,
public nsIAsyncInputStream
{
virtual ~nsMIMEInputStream();
public:
nsMIMEInputStream();
NS_DECL_THREADSAFE_ISUPPORTS
NS_DECL_NSIINPUTSTREAM
NS_DECL_NSIMIMEINPUTSTREAM
NS_DECL_NSISEEKABLESTREAM
NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM
NS_DECL_NSIASYNCINPUTSTREAM
private:
void InitStreams();
struct MOZ_STACK_CLASS ReadSegmentsState {
nsCOMPtr<nsIInputStream> mThisStream;
nsWriteSegmentFun mWriter;
void* mClosure;
};
static nsresult ReadSegCb(nsIInputStream* aIn, void* aClosure,
const char* aFromRawSegment, uint32_t aToOffset,
uint32_t aCount, uint32_t *aWriteCount);
bool IsAsyncInputStream() const;
bool IsIPCSerializable() const;
nsTArray<HeaderEntry> mHeaders;
nsCOMPtr<nsIInputStream> mStream;
bool mStartedReading;
};
NS_IMPL_ADDREF(nsMIMEInputStream)
NS_IMPL_RELEASE(nsMIMEInputStream)
NS_IMPL_CLASSINFO(nsMIMEInputStream, nullptr, nsIClassInfo::THREADSAFE,
NS_MIMEINPUTSTREAM_CID)
NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream)
NS_INTERFACE_MAP_ENTRY(nsIMIMEInputStream)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsIInputStream, nsIMIMEInputStream)
NS_INTERFACE_MAP_ENTRY(nsISeekableStream)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream,
IsIPCSerializable())
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream,
IsAsyncInputStream())
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMIMEInputStream)
NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream)
NS_INTERFACE_MAP_END
NS_IMPL_CI_INTERFACE_GETTER(nsMIMEInputStream,
nsIMIMEInputStream,
nsIAsyncInputStream,
nsIInputStream,
nsISeekableStream)
nsMIMEInputStream::nsMIMEInputStream() : mStartedReading(false)
{
}
nsMIMEInputStream::~nsMIMEInputStream()
{
}
NS_IMETHODIMP
nsMIMEInputStream::AddHeader(const char *aName, const char *aValue)
{
NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
HeaderEntry* entry = mHeaders.AppendElement();
entry->name().Append(aName);
entry->value().Append(aValue);
return NS_OK;
}
NS_IMETHODIMP
nsMIMEInputStream::VisitHeaders(nsIHttpHeaderVisitor *visitor)
{
nsresult rv;
for (auto& header : mHeaders) {
rv = visitor->VisitHeader(header.name(), header.value());
if (NS_FAILED(rv)) {
return rv;
}
}
return NS_OK;
}
NS_IMETHODIMP
nsMIMEInputStream::SetData(nsIInputStream *aStream)
{
NS_ENSURE_FALSE(mStartedReading, NS_ERROR_FAILURE);
nsCOMPtr<nsISeekableStream> seekable = do_QueryInterface(aStream);
if (!seekable) {
return NS_ERROR_INVALID_ARG;
}
mStream = aStream;
return NS_OK;
}
NS_IMETHODIMP
nsMIMEInputStream::GetData(nsIInputStream **aStream)
{
NS_ENSURE_ARG_POINTER(aStream);
*aStream = mStream;
NS_IF_ADDREF(*aStream);
return NS_OK;
}
// set up the internal streams
void nsMIMEInputStream::InitStreams()
{
NS_ASSERTION(!mStartedReading,
"Don't call initStreams twice without rewinding");
mStartedReading = true;
}
#define INITSTREAMS \
if (!mStartedReading) { \
NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED); \
InitStreams(); \
}
// Reset mStartedReading when Seek-ing to start
NS_IMETHODIMP
nsMIMEInputStream::Seek(int32_t whence, int64_t offset)
{
NS_ENSURE_TRUE(mStream, NS_ERROR_UNEXPECTED);
nsresult rv;
nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
if (whence == NS_SEEK_SET && offset == 0) {
rv = stream->Seek(whence, offset);
if (NS_SUCCEEDED(rv))
mStartedReading = false;
}
else {
INITSTREAMS;
rv = stream->Seek(whence, offset);
}
return rv;
}
// Proxy ReadSegments since we need to be a good little nsIInputStream
NS_IMETHODIMP nsMIMEInputStream::ReadSegments(nsWriteSegmentFun aWriter,
void *aClosure, uint32_t aCount,
uint32_t *_retval)
{
INITSTREAMS;
ReadSegmentsState state;
// Disambiguate ambiguous nsIInputStream.
state.mThisStream = static_cast<nsIInputStream*>(
static_cast<nsIMIMEInputStream*>(this));
state.mWriter = aWriter;
state.mClosure = aClosure;
return mStream->ReadSegments(ReadSegCb, &state, aCount, _retval);
}
nsresult
nsMIMEInputStream::ReadSegCb(nsIInputStream* aIn, void* aClosure,
const char* aFromRawSegment,
uint32_t aToOffset, uint32_t aCount,
uint32_t *aWriteCount)
{
ReadSegmentsState* state = (ReadSegmentsState*)aClosure;
return (state->mWriter)(state->mThisStream,
state->mClosure,
aFromRawSegment,
aToOffset,
aCount,
aWriteCount);
}
/**
* Forward everything else to the mStream after calling InitStreams()
*/
// nsIInputStream
NS_IMETHODIMP nsMIMEInputStream::Close(void) { INITSTREAMS; return mStream->Close(); }
NS_IMETHODIMP nsMIMEInputStream::Available(uint64_t *_retval) { INITSTREAMS; return mStream->Available(_retval); }
NS_IMETHODIMP nsMIMEInputStream::Read(char * buf, uint32_t count, uint32_t *_retval) { INITSTREAMS; return mStream->Read(buf, count, _retval); }
NS_IMETHODIMP nsMIMEInputStream::IsNonBlocking(bool *aNonBlocking) { INITSTREAMS; return mStream->IsNonBlocking(aNonBlocking); }
// nsIAsyncInputStream
NS_IMETHODIMP
nsMIMEInputStream::CloseWithStatus(nsresult aStatus)
{
INITSTREAMS;
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
return asyncStream->CloseWithStatus(aStatus);
}
NS_IMETHODIMP
nsMIMEInputStream::AsyncWait(nsIInputStreamCallback* aCallback,
uint32_t aFlags, uint32_t aRequestedCount,
nsIEventTarget* aEventTarget)
{
INITSTREAMS;
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
return asyncStream->AsyncWait(
aCallback, aFlags, aRequestedCount, aEventTarget);
}
// nsISeekableStream
NS_IMETHODIMP nsMIMEInputStream::Tell(int64_t *_retval)
{
INITSTREAMS;
nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
return stream->Tell(_retval);
}
NS_IMETHODIMP nsMIMEInputStream::SetEOF(void) {
INITSTREAMS;
nsCOMPtr<nsISeekableStream> stream = do_QueryInterface(mStream);
return stream->SetEOF();
}
/**
* Factory method used by do_CreateInstance
*/
nsresult
nsMIMEInputStreamConstructor(nsISupports *outer, REFNSIID iid, void **result)
{
*result = nullptr;
if (outer)
return NS_ERROR_NO_AGGREGATION;
RefPtr<nsMIMEInputStream> inst = new nsMIMEInputStream();
if (!inst)
return NS_ERROR_OUT_OF_MEMORY;
return inst->QueryInterface(iid, result);
}
void
nsMIMEInputStream::Serialize(InputStreamParams& aParams,
FileDescriptorArray& aFileDescriptors)
{
MIMEInputStreamParams params;
if (mStream) {
InputStreamParams wrappedParams;
InputStreamHelper::SerializeInputStream(mStream, wrappedParams,
aFileDescriptors);
NS_ASSERTION(wrappedParams.type() != InputStreamParams::T__None,
"Wrapped stream failed to serialize!");
params.optionalStream() = wrappedParams;
}
else {
params.optionalStream() = mozilla::void_t();
}
params.headers() = mHeaders;
params.startedReading() = mStartedReading;
aParams = params;
}
bool
nsMIMEInputStream::Deserialize(const InputStreamParams& aParams,
const FileDescriptorArray& aFileDescriptors)
{
if (aParams.type() != InputStreamParams::TMIMEInputStreamParams) {
NS_ERROR("Received unknown parameters from the other process!");
return false;
}
const MIMEInputStreamParams& params =
aParams.get_MIMEInputStreamParams();
const OptionalInputStreamParams& wrappedParams = params.optionalStream();
mHeaders = params.headers();
mStartedReading = params.startedReading();
if (wrappedParams.type() == OptionalInputStreamParams::TInputStreamParams) {
nsCOMPtr<nsIInputStream> stream;
stream =
InputStreamHelper::DeserializeInputStream(wrappedParams.get_InputStreamParams(),
aFileDescriptors);
if (!stream) {
NS_WARNING("Failed to deserialize wrapped stream!");
return false;
}
mStream = stream;
}
else {
NS_ASSERTION(wrappedParams.type() == OptionalInputStreamParams::Tvoid_t,
"Unknown type for OptionalInputStreamParams!");
}
return true;
}
Maybe<uint64_t>
nsMIMEInputStream::ExpectedSerializedLength()
{
nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream);
return serializable ? serializable->ExpectedSerializedLength() : Nothing();
}
bool
nsMIMEInputStream::IsAsyncInputStream() const
{
nsCOMPtr<nsIAsyncInputStream> asyncStream = do_QueryInterface(mStream);
return !!asyncStream;
}
bool
nsMIMEInputStream::IsIPCSerializable() const
{
// If SetData() or Deserialize() has not be called yet, mStream is null.
if (!mStream) {
return true;
}
nsCOMPtr<nsIIPCSerializableInputStream> serializable = do_QueryInterface(mStream);
return !!serializable;
}