mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-28 04:35:33 +00:00
34b0ef6798
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
369 lines
11 KiB
C++
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;
|
|
}
|