diff --git a/xpcom/io/FixedBufferOutputStream.cpp b/xpcom/io/FixedBufferOutputStream.cpp new file mode 100644 index 000000000000..d2bd235a1505 --- /dev/null +++ b/xpcom/io/FixedBufferOutputStream.cpp @@ -0,0 +1,155 @@ +/* -*- 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 "FixedBufferOutputStream.h" + +#include "mozilla/RefPtr.h" +#include "mozilla/StreamBufferSinkImpl.h" +#include "nsStreamUtils.h" +#include "nsString.h" + +namespace mozilla { + +FixedBufferOutputStream::FixedBufferOutputStream( + UniquePtr&& aSink) + : mSink(std::move(aSink)), + mMutex("FixedBufferOutputStream::mMutex"), + mOffset(0), + mWriting(false), + mClosed(false) {} + +// static +RefPtr FixedBufferOutputStream::Create( + size_t aLength) { + MOZ_ASSERT(aLength); + + return MakeRefPtr(MakeUnique(aLength)); +} + +// static +RefPtr FixedBufferOutputStream::Create( + size_t aLength, const mozilla::fallible_t&) { + MOZ_ASSERT(aLength); + + auto sink = BufferSink::Alloc(aLength); + + if (NS_WARN_IF(!sink)) { + return nullptr; + } + + return MakeRefPtr(std::move(sink)); +} + +// static +RefPtr FixedBufferOutputStream::Create( + mozilla::Span aBuffer) { + return MakeRefPtr( + MakeUnique(aBuffer)); +} + +// static +RefPtr FixedBufferOutputStream::Create( + UniquePtr&& aSink) { + return MakeRefPtr(std::move(aSink)); +} + +nsDependentCSubstring FixedBufferOutputStream::WrittenData() { + MutexAutoLock autoLock(mMutex); + + return mSink->Slice(mOffset); +} + +NS_IMPL_ISUPPORTS(FixedBufferOutputStream, nsIOutputStream) + +NS_IMETHODIMP +FixedBufferOutputStream::Close() { + MutexAutoLock autoLock(mMutex); + + if (mWriting) { + return NS_ERROR_NOT_AVAILABLE; + } + + mClosed = true; + + return NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::Flush() { return NS_OK; } + +NS_IMETHODIMP +FixedBufferOutputStream::Write(const char* aBuf, uint32_t aCount, + uint32_t* _retval) { + return WriteSegments(NS_CopyBufferToSegment, const_cast(aBuf), aCount, + _retval); +} + +NS_IMETHODIMP +FixedBufferOutputStream::WriteFrom(nsIInputStream* aFromStream, uint32_t aCount, + uint32_t* _retval) { + return WriteSegments(NS_CopyStreamToSegment, aFromStream, aCount, _retval); +} + +NS_IMETHODIMP +FixedBufferOutputStream::WriteSegments(nsReadSegmentFun aReader, void* aClosure, + uint32_t aCount, uint32_t* _retval) { + MOZ_ASSERT(_retval); + + MutexAutoLock autoLock(mMutex); + + if (mWriting) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + size_t length = mSink->Data().Length(); + size_t offset = mOffset; + + MOZ_ASSERT(length >= offset, "Bad stream state!"); + + size_t maxCount = length - offset; + if (maxCount == 0) { + *_retval = 0; + return NS_OK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + mWriting = true; + + nsresult rv; + + { + MutexAutoUnlock autoUnlock(mMutex); + + rv = aReader(this, aClosure, mSink->Data().Elements() + offset, 0, aCount, + _retval); + } + + mWriting = false; + + if (NS_SUCCEEDED(rv)) { + MOZ_ASSERT(*_retval <= aCount, + "Reader should not read more than we asked it to read!"); + mOffset += *_retval; + } + + // As defined in nsIOutputStream.idl, do not pass reader func errors. + return NS_OK; +} + +NS_IMETHODIMP +FixedBufferOutputStream::IsNonBlocking(bool* _retval) { + *_retval = true; + return NS_OK; +} + +} // namespace mozilla diff --git a/xpcom/io/FixedBufferOutputStream.h b/xpcom/io/FixedBufferOutputStream.h new file mode 100644 index 000000000000..db7fba3c3abf --- /dev/null +++ b/xpcom/io/FixedBufferOutputStream.h @@ -0,0 +1,78 @@ +/* -*- 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/. */ + +#ifndef mozilla_FixedBufferOutputStream_h +#define mozilla_FixedBufferOutputStream_h + +#include +#include "mozilla/fallible.h" +#include "mozilla/Mutex.h" +#include "mozilla/Span.h" +#include "mozilla/UniquePtr.h" +#include "nsIOutputStream.h" +#include "nsStringFwd.h" + +template +class RefPtr; + +namespace mozilla { + +class StreamBufferSink; + +// An output stream so you can read your potentially-async input stream into +// a contiguous buffer using NS_AsyncCopy. Back when streams were more +// synchronous and people didn't know blocking I/O was bad, if you wanted to +// read a stream into a flat buffer, you could use NS_ReadInputStreamToString +// or NS_ReadInputStreamToBuffer. But those don't work with async streams. +// This can be used to replace hand-rolled Read/AsyncWait() loops. Because you +// specify the expected size up front, the buffer is pre-allocated so wasteful +// reallocations can be avoided. +class FixedBufferOutputStream final : public nsIOutputStream { + template + friend RefPtr MakeRefPtr(Args&&... aArgs); + + public: + // Factory method to get a FixedBufferOutputStream by allocating a char buffer + // with the given length. + static RefPtr Create(size_t aLength); + + // Factory method to get a FixedBufferOutputStream by allocating a char buffer + // with the given length, fallible version. + static RefPtr Create(size_t aLength, + const mozilla::fallible_t&); + + // Factory method to get a FixedBufferOutputStream from a preallocated char + // buffer. The output stream doesn't take ownership of the char buffer, so the + // char buffer must outlive the output stream (to avoid a use-after-free). + static RefPtr Create(mozilla::Span aBuffer); + + // Factory method to get a FixedBufferOutputStream from an arbitrary + // StreamBufferSink. + static RefPtr Create( + UniquePtr&& aSink); + + nsDependentCSubstring WrittenData(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOUTPUTSTREAM + + private: + explicit FixedBufferOutputStream(UniquePtr&& aSink); + + ~FixedBufferOutputStream() = default; + + const UniquePtr mSink; + + Mutex mMutex; + + size_t mOffset MOZ_GUARDED_BY(mMutex); + bool mWriting MOZ_GUARDED_BY(mMutex); + bool mClosed MOZ_GUARDED_BY(mMutex); +}; + +} // namespace mozilla + +#endif // mozilla_FixedBufferOutputStream_h diff --git a/xpcom/io/StreamBufferSink.h b/xpcom/io/StreamBufferSink.h new file mode 100644 index 000000000000..bf00527aea7a --- /dev/null +++ b/xpcom/io/StreamBufferSink.h @@ -0,0 +1,29 @@ +/* -*- 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/. */ + +#ifndef mozilla_StreamBufferSink_h +#define mozilla_StreamBufferSink_h + +#include +#include "mozilla/Span.h" +#include "nsString.h" + +namespace mozilla { + +class StreamBufferSink { + public: + virtual mozilla::Span Data() = 0; + + nsDependentCSubstring Slice(size_t aOffset) { + return nsDependentCSubstring(Data().First(aOffset)); + } + + virtual ~StreamBufferSink() = default; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSink_h diff --git a/xpcom/io/StreamBufferSinkImpl.h b/xpcom/io/StreamBufferSinkImpl.h new file mode 100644 index 000000000000..9fba413ef226 --- /dev/null +++ b/xpcom/io/StreamBufferSinkImpl.h @@ -0,0 +1,49 @@ +/* -*- 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/. */ + +#ifndef mozilla_StreamBufferSinkImpl_h +#define mozilla_StreamBufferSinkImpl_h + +#include "mozilla/Buffer.h" +#include "mozilla/StreamBufferSink.h" +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +class BufferSink final : public StreamBufferSink { + public: + explicit BufferSink(Buffer&& aBuffer) : mBuffer(std::move(aBuffer)) {} + + explicit BufferSink(size_t aLength) : mBuffer(aLength) {} + + static UniquePtr Alloc(size_t aLength) { + auto maybeBuffer = Buffer::Alloc(aLength); + if (!maybeBuffer) { + return nullptr; + } + + return MakeUnique(maybeBuffer.extract()); + } + + mozilla::Span Data() override { return mBuffer.AsWritableSpan(); } + + private: + Buffer mBuffer; +}; + +class nsBorrowedSink final : public StreamBufferSink { + public: + explicit nsBorrowedSink(mozilla::Span aBuffer) : mBuffer(aBuffer) {} + + mozilla::Span Data() override { return mBuffer; } + + private: + mozilla::Span mBuffer; +}; + +} // namespace mozilla + +#endif // mozilla_StreamBufferSinkImpl_h diff --git a/xpcom/io/moz.build b/xpcom/io/moz.build index 23e186217dcf..21a698be876d 100644 --- a/xpcom/io/moz.build +++ b/xpcom/io/moz.build @@ -92,6 +92,7 @@ EXPORTS += [ EXPORTS.mozilla += [ "Base64.h", "FilePreferences.h", + "FixedBufferOutputStream.h", "InputStreamLengthHelper.h", "InputStreamLengthWrapper.h", "NonBlockingAsyncInputStream.h", @@ -99,6 +100,8 @@ EXPORTS.mozilla += [ "SnappyCompressOutputStream.h", "SnappyFrameUtils.h", "SnappyUncompressInputStream.h", + "StreamBufferSink.h", + "StreamBufferSinkImpl.h", "StreamBufferSource.h", ] @@ -107,6 +110,7 @@ UNIFIED_SOURCES += [ "crc32c.c", "FileDescriptorFile.cpp", "FilePreferences.cpp", + "FixedBufferOutputStream.cpp", "InputStreamLengthHelper.cpp", "InputStreamLengthWrapper.cpp", "NonBlockingAsyncInputStream.cpp",