diff --git a/netwerk/base/nsMIMEInputStream.cpp b/netwerk/base/nsMIMEInputStream.cpp index 3b096ab68d45..bbe17e87af26 100644 --- a/netwerk/base/nsMIMEInputStream.cpp +++ b/netwerk/base/nsMIMEInputStream.cpp @@ -13,6 +13,7 @@ #include "nsCOMPtr.h" #include "nsComponentManagerUtils.h" #include "nsIAsyncInputStream.h" +#include "nsIInputStreamLength.h" #include "nsIHttpHeaderVisitor.h" #include "nsIMIMEInputStream.h" #include "nsISeekableStream.h" @@ -32,7 +33,10 @@ class nsMIMEInputStream : public nsIMIMEInputStream, public nsISeekableStream, public nsIIPCSerializableInputStream, public nsIAsyncInputStream, - public nsIInputStreamCallback + public nsIInputStreamCallback, + public nsIInputStreamLength, + public nsIAsyncInputStreamLength, + public nsIInputStreamLengthCallback { virtual ~nsMIMEInputStream() = default; @@ -46,6 +50,9 @@ public: NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM NS_DECL_NSIASYNCINPUTSTREAM NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIINPUTSTREAMLENGTH + NS_DECL_NSIASYNCINPUTSTREAMLENGTH + NS_DECL_NSIINPUTSTREAMLENGTHCALLBACK private: @@ -62,6 +69,8 @@ private: bool IsAsyncInputStream() const; bool IsIPCSerializable() const; + bool IsInputStreamLength() const; + bool IsAsyncInputStreamLength() const; nsTArray mHeaders; @@ -72,6 +81,9 @@ private: // This is protected by mutex. nsCOMPtr mAsyncWaitCallback; + + // This is protected by mutex. + nsCOMPtr mAsyncInputStreamLengthCallback; }; NS_IMPL_ADDREF(nsMIMEInputStream) @@ -91,6 +103,12 @@ NS_INTERFACE_MAP_BEGIN(nsMIMEInputStream) NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, IsAsyncInputStream()) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIMIMEInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLength, + IsInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStreamLength, + IsAsyncInputStreamLength()) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamLengthCallback, + IsAsyncInputStreamLength()) NS_IMPL_QUERY_CLASSINFO(nsMIMEInputStream) NS_INTERFACE_MAP_END @@ -392,6 +410,54 @@ nsMIMEInputStream::ExpectedSerializedLength() return serializable ? serializable->ExpectedSerializedLength() : Nothing(); } +NS_IMETHODIMP +nsMIMEInputStream::Length(int64_t* aLength) +{ + nsCOMPtr stream = do_QueryInterface(mStream); + if (NS_WARN_IF(!stream)) { + return NS_ERROR_FAILURE; + } + + return stream->Length(aLength); +} + +NS_IMETHODIMP +nsMIMEInputStream::AsyncLengthWait(nsIInputStreamLengthCallback* aCallback, + nsIEventTarget* aEventTarget) +{ + nsCOMPtr stream = do_QueryInterface(mStream); + if (NS_WARN_IF(!stream)) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr callback = aCallback ? this : nullptr; + { + MutexAutoLock lock(mMutex); + mAsyncInputStreamLengthCallback = aCallback; + } + + return stream->AsyncLengthWait(callback, aEventTarget); +} + +NS_IMETHODIMP +nsMIMEInputStream::OnInputStreamLengthReady(nsIAsyncInputStreamLength* aStream, + int64_t aLength) +{ + nsCOMPtr callback; + { + MutexAutoLock lock(mMutex); + // We have been canceled in the meanwhile. + if (!mAsyncInputStreamLengthCallback) { + return NS_OK; + } + + callback.swap(mAsyncInputStreamLengthCallback); + } + + MOZ_ASSERT(callback); + return callback->OnInputStreamLengthReady(this, aLength); +} + bool nsMIMEInputStream::IsAsyncInputStream() const { @@ -410,3 +476,17 @@ nsMIMEInputStream::IsIPCSerializable() const nsCOMPtr serializable = do_QueryInterface(mStream); return !!serializable; } + +bool +nsMIMEInputStream::IsInputStreamLength() const +{ + nsCOMPtr stream = do_QueryInterface(mStream); + return !!stream; +} + +bool +nsMIMEInputStream::IsAsyncInputStreamLength() const +{ + nsCOMPtr stream = do_QueryInterface(mStream); + return !!stream; +} diff --git a/netwerk/test/gtest/TestMIMEInputStream.cpp b/netwerk/test/gtest/TestMIMEInputStream.cpp new file mode 100644 index 000000000000..c6e078fa5087 --- /dev/null +++ b/netwerk/test/gtest/TestMIMEInputStream.cpp @@ -0,0 +1,260 @@ +#include "gtest/gtest.h" + +#include "Helpers.h" +#include "nsCOMPtr.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "nsIMIMEInputStream.h" + +using mozilla::GetCurrentThreadSerialEventTarget; +using mozilla::SpinEventLoopUntil; + +namespace { + +class SeekableLengthInputStream final : public testing::LengthInputStream + , public nsISeekableStream +{ +public: + SeekableLengthInputStream(const nsACString& aBuffer, + bool aIsInputStreamLength, + bool aIsAsyncInputStreamLength, + nsresult aLengthRv = NS_OK, + bool aNegativeValue = false) + : testing::LengthInputStream(aBuffer, aIsInputStreamLength, + aIsAsyncInputStreamLength, aLengthRv, + aNegativeValue) + {} + + NS_DECL_ISUPPORTS_INHERITED + + NS_IMETHOD + Seek(int32_t aWhence, int64_t aOffset) override + { + MOZ_CRASH("This method should not be called."); + return NS_ERROR_FAILURE; + } + + NS_IMETHOD + Tell(int64_t* aResult) override + { + MOZ_CRASH("This method should not be called."); + return NS_ERROR_FAILURE; + } + + NS_IMETHOD + SetEOF() override + { + MOZ_CRASH("This method should not be called."); + return NS_ERROR_FAILURE; + } + +private: + ~SeekableLengthInputStream() = default; +}; + +NS_IMPL_ISUPPORTS_INHERITED(SeekableLengthInputStream, + testing::LengthInputStream, + nsISeekableStream) + +} // anonymous + +// nsIInputStreamLength && nsIAsyncInputStreamLength + +TEST(TestNsMIMEInputStream, QIInputStreamLength) { + nsCString buf; + buf.AssignLiteral("Hello world"); + + for (int i = 0; i < 4; i++) { + nsCOMPtr mis; + { + RefPtr stream = + new SeekableLengthInputStream(buf, i % 2, i > 1); + + nsresult rv; + nsCOMPtr m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = do_QueryInterface(m); + ASSERT_TRUE(!!mis); + } + + { + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_EQ(!!(i % 2), !!qi); + } + + { + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_EQ(i > 1, !!qi); + } + } +} + +TEST(TestNsMIMEInputStream, InputStreamLength) { + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr mis; + { + RefPtr stream = + new SeekableLengthInputStream(buf, true, false); + + nsresult rv; + nsCOMPtr m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = do_QueryInterface(m); + ASSERT_TRUE(!!mis); + } + + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(buf.Length(), size); +} + +TEST(TestNsMIMEInputStream, NegativeInputStreamLength) { + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr mis; + { + RefPtr stream = + new SeekableLengthInputStream(buf, true, false, NS_OK, true); + + nsresult rv; + nsCOMPtr m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = do_QueryInterface(m); + ASSERT_TRUE(!!mis); + } + + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + int64_t size; + nsresult rv = qi->Length(&size); + ASSERT_EQ(NS_OK, rv); + ASSERT_EQ(-1, size); +} + +TEST(TestNsMIMEInputStream, AsyncInputStreamLength) { + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr mis; + { + RefPtr stream = + new SeekableLengthInputStream(buf, false, true); + + nsresult rv; + nsCOMPtr m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = do_QueryInterface(m); + ASSERT_TRUE(!!mis); + } + + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + RefPtr callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentThreadSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); })); + ASSERT_EQ(buf.Length(), callback->Size()); +} + +TEST(TestNsMIMEInputStream, NegativeAsyncInputStreamLength) { + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr mis; + { + RefPtr stream = + new SeekableLengthInputStream(buf, false, true, NS_OK, true); + + nsresult rv; + nsCOMPtr m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = do_QueryInterface(m); + ASSERT_TRUE(!!mis); + } + + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + RefPtr callback = new testing::LengthCallback(); + + nsresult rv = qi->AsyncLengthWait(callback, GetCurrentThreadSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback->Called(); })); + ASSERT_EQ(-1, callback->Size()); +} + +TEST(TestNsMIMEInputStream, AbortLengthCallback) { + nsCString buf; + buf.AssignLiteral("Hello world"); + + nsCOMPtr mis; + { + RefPtr stream = + new SeekableLengthInputStream(buf, false, true, NS_OK, true); + + nsresult rv; + nsCOMPtr m( + do_CreateInstance("@mozilla.org/network/mime-input-stream;1", &rv)); + ASSERT_EQ(NS_OK, rv); + + rv = m->SetData(stream); + ASSERT_EQ(NS_OK, rv); + + mis = do_QueryInterface(m); + ASSERT_TRUE(!!mis); + } + + nsCOMPtr qi = do_QueryInterface(mis); + ASSERT_TRUE(!!qi); + + RefPtr callback1 = new testing::LengthCallback(); + nsresult rv = qi->AsyncLengthWait(callback1, GetCurrentThreadSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + RefPtr callback2 = new testing::LengthCallback(); + rv = qi->AsyncLengthWait(callback2, GetCurrentThreadSerialEventTarget()); + ASSERT_EQ(NS_OK, rv); + + MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return callback2->Called(); })); + ASSERT_TRUE(!callback1->Called()); + ASSERT_EQ(-1, callback2->Size()); +} diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build index f58b3b90079e..927c5866b497 100644 --- a/netwerk/test/gtest/moz.build +++ b/netwerk/test/gtest/moz.build @@ -8,6 +8,7 @@ UNIFIED_SOURCES += [ 'TestBufferedInputStream.cpp', 'TestHeaders.cpp', 'TestHttpAuthUtils.cpp', + 'TestMIMEInputStream.cpp', 'TestMozURL.cpp', 'TestPartiallySeekableInputStream.cpp', 'TestProtocolProxyService.cpp', diff --git a/xpcom/tests/gtest/Helpers.h b/xpcom/tests/gtest/Helpers.h index 20d2f34c58f3..e99a06d19a16 100644 --- a/xpcom/tests/gtest/Helpers.h +++ b/xpcom/tests/gtest/Helpers.h @@ -95,9 +95,9 @@ private: }; // This class implements a simple nsIInputStreamLength stream. -class LengthInputStream final : public nsIInputStream - , public nsIInputStreamLength - , public nsIAsyncInputStreamLength +class LengthInputStream : public nsIInputStream + , public nsIInputStreamLength + , public nsIAsyncInputStreamLength { nsCOMPtr mStream; bool mIsInputStreamLength; @@ -180,8 +180,8 @@ public: return aEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL); } -private: - ~LengthInputStream() {} +protected: + virtual ~LengthInputStream() = default; }; class LengthCallback final : public nsIInputStreamLengthCallback