From 3ccb9d2674582a5578bd9ad1bd9c3b2e66ea8128 Mon Sep 17 00:00:00 2001 From: Andrea Marchesini Date: Fri, 8 Sep 2017 16:06:26 +0200 Subject: [PATCH] Bug 1397635 - Support for non-seekable stream in HTTP connection, r=bagder --- netwerk/base/PartiallySeekableInputStream.cpp | 332 ++++++++++++++++++ netwerk/base/PartiallySeekableInputStream.h | 61 ++++ netwerk/base/RequestContextService.cpp | 5 + netwerk/base/moz.build | 2 + netwerk/protocol/http/Http2Session.h | 1 + netwerk/protocol/http/HttpBaseChannel.cpp | 28 +- netwerk/protocol/http/HttpBaseChannel.h | 3 + netwerk/protocol/http/nsHttpChannel.cpp | 2 - netwerk/protocol/http/nsHttpChannel.h | 4 - .../TestPartiallySeekableInputStream.cpp | 225 ++++++++++++ netwerk/test/gtest/moz.build | 1 + 11 files changed, 652 insertions(+), 12 deletions(-) create mode 100644 netwerk/base/PartiallySeekableInputStream.cpp create mode 100644 netwerk/base/PartiallySeekableInputStream.h create mode 100644 netwerk/test/gtest/TestPartiallySeekableInputStream.cpp diff --git a/netwerk/base/PartiallySeekableInputStream.cpp b/netwerk/base/PartiallySeekableInputStream.cpp new file mode 100644 index 000000000000..7facdeb9dd22 --- /dev/null +++ b/netwerk/base/PartiallySeekableInputStream.cpp @@ -0,0 +1,332 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "PartiallySeekableInputStream.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsISeekableStream.h" +#include "nsStreamUtils.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ADDREF(PartiallySeekableInputStream); +NS_IMPL_RELEASE(PartiallySeekableInputStream); + +NS_INTERFACE_MAP_BEGIN(PartiallySeekableInputStream) + NS_INTERFACE_MAP_ENTRY(nsIInputStream) + NS_INTERFACE_MAP_ENTRY(nsISeekableStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsICloneableInputStream, + mWeakCloneableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIIPCSerializableInputStream, + mWeakIPCSerializableInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIAsyncInputStream, + mWeakAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIInputStreamCallback, + mWeakAsyncInputStream) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIInputStream) +NS_INTERFACE_MAP_END + +PartiallySeekableInputStream::PartiallySeekableInputStream(nsIInputStream* aInputStream, + uint64_t aBufferSize) + : mInputStream(aInputStream) + , mWeakCloneableInputStream(nullptr) + , mWeakIPCSerializableInputStream(nullptr) + , mWeakAsyncInputStream(nullptr) + , mBufferSize(aBufferSize) + , mPos(0) + , mClosed(false) +{ + MOZ_ASSERT(aInputStream); + +#ifdef DEBUG + nsCOMPtr seekableStream = do_QueryInterface(aInputStream); + MOZ_ASSERT(!seekableStream); +#endif + + nsCOMPtr cloneableStream = + do_QueryInterface(aInputStream); + if (cloneableStream && SameCOMIdentity(aInputStream, cloneableStream)) { + mWeakCloneableInputStream = cloneableStream; + } + + nsCOMPtr serializableStream = + do_QueryInterface(aInputStream); + if (serializableStream && + SameCOMIdentity(aInputStream, serializableStream)) { + mWeakIPCSerializableInputStream = serializableStream; + } + + nsCOMPtr asyncInputStream = + do_QueryInterface(aInputStream); + if (asyncInputStream && SameCOMIdentity(aInputStream, asyncInputStream)) { + mWeakAsyncInputStream = asyncInputStream; + } +} + +PartiallySeekableInputStream::~PartiallySeekableInputStream() +{} + +NS_IMETHODIMP +PartiallySeekableInputStream::Close() +{ + mInputStream->Close(); + mCachedBuffer.Clear(); + mPos = 0; + mClosed = true; + return NS_OK; +} + +// nsIInputStream interface + +NS_IMETHODIMP +PartiallySeekableInputStream::Available(uint64_t* aLength) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + nsresult rv = mInputStream->Available(aLength); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (mPos < mCachedBuffer.Length()) { + *aLength += mCachedBuffer.Length() - mPos; + } + + return NS_OK; +} + +NS_IMETHODIMP +PartiallySeekableInputStream::Read(char* aBuffer, uint32_t aCount, + uint32_t* aReadCount) +{ + *aReadCount = 0; + + if (mClosed) { + return NS_OK; + } + + uint32_t byteRead = 0; + + if (mPos < mCachedBuffer.Length()) { + // We are reading from the cached buffer. + byteRead = XPCOM_MIN(mCachedBuffer.Length() - mPos, (uint64_t)aCount); + memcpy(aBuffer, mCachedBuffer.Elements() + mPos, byteRead); + *aReadCount = byteRead; + mPos += byteRead; + } + + if (byteRead < aCount) { + MOZ_ASSERT(mPos >= mCachedBuffer.Length()); + MOZ_ASSERT_IF(mPos > mCachedBuffer.Length(), + mCachedBuffer.Length() == mBufferSize); + + // We can read from the stream. + uint32_t byteWritten; + nsresult rv = mInputStream->Read(aBuffer + byteRead, aCount - byteRead, + &byteWritten); + if (NS_WARN_IF(NS_FAILED(rv)) || byteWritten == 0) { + return rv; + } + + *aReadCount += byteWritten; + + // Maybe we have to cache something. + if (mPos < mBufferSize) { + uint32_t size = XPCOM_MIN(mPos + byteWritten, mBufferSize); + mCachedBuffer.SetLength(size); + memcpy(mCachedBuffer.Elements() + mPos, aBuffer + byteRead, size - mPos); + } + + mPos += byteWritten; + } + + return NS_OK; +} + +NS_IMETHODIMP +PartiallySeekableInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t *aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +PartiallySeekableInputStream::IsNonBlocking(bool* aNonBlocking) +{ + return mInputStream->IsNonBlocking(aNonBlocking); +} + +// nsICloneableInputStream interface + +NS_IMETHODIMP +PartiallySeekableInputStream::GetCloneable(bool* aCloneable) +{ + NS_ENSURE_STATE(mWeakCloneableInputStream); + + *aCloneable = true; + return NS_OK; +} + +NS_IMETHODIMP +PartiallySeekableInputStream::Clone(nsIInputStream** aResult) +{ + NS_ENSURE_STATE(mWeakCloneableInputStream); + + nsCOMPtr clonedStream; + nsresult rv = mWeakCloneableInputStream->Clone(getter_AddRefs(clonedStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr stream = + new PartiallySeekableInputStream(clonedStream, mBufferSize); + + stream.forget(aResult); + return NS_OK; +} + +// nsIAsyncInputStream interface + +NS_IMETHODIMP +PartiallySeekableInputStream::CloseWithStatus(nsresult aStatus) +{ + NS_ENSURE_STATE(mWeakAsyncInputStream); + + return mWeakAsyncInputStream->CloseWithStatus(aStatus); +} + +NS_IMETHODIMP +PartiallySeekableInputStream::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aEventTarget) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + NS_ENSURE_STATE(mWeakAsyncInputStream); + + if (mAsyncWaitCallback && aCallback) { + return NS_ERROR_FAILURE; + } + + mAsyncWaitCallback = aCallback; + + if (!mAsyncWaitCallback) { + return NS_OK; + } + + return mWeakAsyncInputStream->AsyncWait(this, aFlags, aRequestedCount, + aEventTarget); +} + +// nsIInputStreamCallback + +NS_IMETHODIMP +PartiallySeekableInputStream::OnInputStreamReady(nsIAsyncInputStream* aStream) +{ + MOZ_ASSERT(mWeakAsyncInputStream); + MOZ_ASSERT(mWeakAsyncInputStream == aStream); + + // We have been canceled in the meanwhile. + if (!mAsyncWaitCallback) { + return NS_OK; + } + + nsCOMPtr callback = mAsyncWaitCallback; + + mAsyncWaitCallback = nullptr; + + return callback->OnInputStreamReady(this); +} + +// nsIIPCSerializableInputStream + +void +PartiallySeekableInputStream::Serialize(mozilla::ipc::InputStreamParams& aParams, + FileDescriptorArray& aFileDescriptors) +{ + MOZ_ASSERT(mWeakIPCSerializableInputStream); + mozilla::ipc::InputStreamHelper::SerializeInputStream(mInputStream, aParams, + aFileDescriptors); +} + +bool +PartiallySeekableInputStream::Deserialize(const mozilla::ipc::InputStreamParams& aParams, + const FileDescriptorArray& aFileDescriptors) +{ + MOZ_CRASH("This method should never be called!"); + return false; +} + +mozilla::Maybe +PartiallySeekableInputStream::ExpectedSerializedLength() +{ + if (!mWeakIPCSerializableInputStream) { + return mozilla::Nothing(); + } + + return mWeakIPCSerializableInputStream->ExpectedSerializedLength(); +} + +// nsISeekableStream + +NS_IMETHODIMP +PartiallySeekableInputStream::Seek(int32_t aWhence, int64_t aOffset) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + int64_t offset; + + switch (aWhence) { + case NS_SEEK_SET: + offset = aOffset; + break; + case NS_SEEK_CUR: + offset = mPos + aOffset; + break; + case NS_SEEK_END: { + return NS_ERROR_NOT_IMPLEMENTED; + } + default: + return NS_ERROR_ILLEGAL_VALUE; + } + + if (offset < 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + + if ((uint64_t)offset >= mCachedBuffer.Length() || mPos > mBufferSize) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + mPos = offset; + return NS_OK; +} + +NS_IMETHODIMP +PartiallySeekableInputStream::Tell(int64_t *aResult) +{ + if (mClosed) { + return NS_BASE_STREAM_CLOSED; + } + + *aResult = mPos; + return NS_OK; +} + +NS_IMETHODIMP +PartiallySeekableInputStream::SetEOF() +{ + return Close(); +} + +} // net namespace +} // mozilla namespace diff --git a/netwerk/base/PartiallySeekableInputStream.h b/netwerk/base/PartiallySeekableInputStream.h new file mode 100644 index 000000000000..384a207e7e3d --- /dev/null +++ b/netwerk/base/PartiallySeekableInputStream.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 PartiallySeekableInputStream_h +#define PartiallySeekableInputStream_h + +#include "mozilla/Attributes.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsICloneableInputStream.h" +#include "nsIIPCSerializableInputStream.h" +#include "nsISeekableStream.h" + +namespace mozilla { +namespace net { + +// A wrapper for making a stream seekable for the first |aBufferSize| bytes. + +class PartiallySeekableInputStream final : public nsISeekableStream + , public nsIAsyncInputStream + , public nsICloneableInputStream + , public nsIIPCSerializableInputStream + , public nsIInputStreamCallback +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSISEEKABLESTREAM + NS_DECL_NSIASYNCINPUTSTREAM + NS_DECL_NSICLONEABLEINPUTSTREAM + NS_DECL_NSIIPCSERIALIZABLEINPUTSTREAM + NS_DECL_NSIINPUTSTREAMCALLBACK + + explicit PartiallySeekableInputStream(nsIInputStream* aInputStream, + uint64_t aBufferSize = 4096); + +private: + ~PartiallySeekableInputStream(); + + nsCOMPtr mInputStream; + + // Raw pointers because these are just QI of mInputStream. + nsICloneableInputStream* mWeakCloneableInputStream; + nsIIPCSerializableInputStream* mWeakIPCSerializableInputStream; + nsIAsyncInputStream* mWeakAsyncInputStream; + + nsCOMPtr mAsyncWaitCallback; + + nsTArray mCachedBuffer; + + uint64_t mBufferSize; + uint64_t mPos; + bool mClosed; +}; + +} // net namespace +} // mozilla namespace + +#endif // PartiallySeekableInputStream_h diff --git a/netwerk/base/RequestContextService.cpp b/netwerk/base/RequestContextService.cpp index 11fcd84c0d60..bf51d4f5000c 100644 --- a/netwerk/base/RequestContextService.cpp +++ b/netwerk/base/RequestContextService.cpp @@ -5,6 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsAutoPtr.h" +#include "nsIDocShell.h" +#include "nsIDocument.h" +#include "nsIDocumentLoader.h" #include "nsIObserverService.h" #include "nsIXULRuntime.h" #include "nsServiceManagerUtils.h" @@ -16,6 +19,8 @@ #include "mozilla/Services.h" #include "mozilla/TimeStamp.h" +#include "mozilla/net/NeckoChild.h" +#include "mozilla/net/NeckoCommon.h" #include "mozilla/net/PSpdyPush.h" #include "../protocol/http/nsHttpHandler.h" diff --git a/netwerk/base/moz.build b/netwerk/base/moz.build index 21f1e03a1078..73dbcb4f973c 100644 --- a/netwerk/base/moz.build +++ b/netwerk/base/moz.build @@ -175,6 +175,7 @@ EXPORTS.mozilla.net += [ 'Dashboard.h', 'DashboardTypes.h', 'MemoryDownloader.h', + 'PartiallySeekableInputStream.h', 'Predictor.h', 'ReferrerPolicy.h', 'SimpleChannelParent.h', @@ -241,6 +242,7 @@ UNIFIED_SOURCES += [ 'nsUnicharStreamLoader.cpp', 'nsURLHelper.cpp', 'nsURLParsers.cpp', + 'PartiallySeekableInputStream.cpp', 'PollableEvent.cpp', 'Predictor.cpp', 'ProxyAutoConfig.cpp', diff --git a/netwerk/protocol/http/Http2Session.h b/netwerk/protocol/http/Http2Session.h index 7a79baad6abf..87c4e8b9fcb9 100644 --- a/netwerk/protocol/http/Http2Session.h +++ b/netwerk/protocol/http/Http2Session.h @@ -12,6 +12,7 @@ #include "ASpdySession.h" #include "mozilla/Attributes.h" #include "mozilla/UniquePtr.h" +#include "mozilla/WeakPtr.h" #include "nsAHttpConnection.h" #include "nsClassHashtable.h" #include "nsDataHashtable.h" diff --git a/netwerk/protocol/http/HttpBaseChannel.cpp b/netwerk/protocol/http/HttpBaseChannel.cpp index d9a13d5199a7..8a5c4d8d9f45 100644 --- a/netwerk/protocol/http/HttpBaseChannel.cpp +++ b/netwerk/protocol/http/HttpBaseChannel.cpp @@ -57,6 +57,7 @@ #include "mozilla/BinarySearch.h" #include "mozilla/DebugOnly.h" #include "mozilla/Move.h" +#include "mozilla/net/PartiallySeekableInputStream.h" #include "nsIHttpHeaderVisitor.h" #include "nsIMIMEInputStream.h" #include "nsIXULRuntime.h" @@ -212,6 +213,8 @@ HttpBaseChannel::HttpBaseChannel() , mForceMainDocumentChannel(false) , mIsTrackingResource(false) , mLastRedirectFlags(0) + , mReqContentLength(0U) + , mReqContentLengthDetermined(false) { LOG(("Creating HttpBaseChannel @%p\n", this)); @@ -1012,10 +1015,10 @@ HttpBaseChannel::CloneUploadStream(nsIInputStream** aClonedStream) NS_IMETHODIMP HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream, - const nsACString &aContentType, - int64_t aContentLength, - const nsACString &aMethod, - bool aStreamHasHeaders) + const nsACString &aContentType, + int64_t aContentLength, + const nsACString &aMethod, + bool aStreamHasHeaders) { // Ensure stream is set and method is valid NS_ENSURE_TRUE(aStream, NS_ERROR_FAILURE); @@ -1055,6 +1058,18 @@ HttpBaseChannel::ExplicitSetUploadStream(nsIInputStream *aStream, } mUploadStreamHasHeaders = aStreamHasHeaders; + + // We already have the content length. We don't need to determinate it. + if (aContentLength > 0) { + mReqContentLength = aContentLength; + mReqContentLengthDetermined = true; + } + + nsCOMPtr seekable = do_QueryInterface(aStream); + if (!seekable) { + aStream = new PartiallySeekableInputStream(aStream); + } + mUploadStream = aStream; return NS_OK; } @@ -3411,8 +3426,9 @@ HttpBaseChannel::SetupReplacementChannel(nsIURI *newURI, if (mUploadStream && (uploadChannel2 || uploadChannel)) { // rewind upload stream nsCOMPtr seekable = do_QueryInterface(mUploadStream); - if (seekable) - seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + MOZ_ASSERT(seekable); + + seekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); // replicate original call to SetUploadStream... if (uploadChannel2) { diff --git a/netwerk/protocol/http/HttpBaseChannel.h b/netwerk/protocol/http/HttpBaseChannel.h index 9c381a1a3906..07bdb6119900 100644 --- a/netwerk/protocol/http/HttpBaseChannel.h +++ b/netwerk/protocol/http/HttpBaseChannel.h @@ -656,6 +656,9 @@ protected: // method. uint32_t mLastRedirectFlags; + uint64_t mReqContentLength; + bool mReqContentLengthDetermined; + nsString mIntegrityMetadata; // Classified channel's matched information diff --git a/netwerk/protocol/http/nsHttpChannel.cpp b/netwerk/protocol/http/nsHttpChannel.cpp index 640e1c3b685f..f430769d643d 100644 --- a/netwerk/protocol/http/nsHttpChannel.cpp +++ b/netwerk/protocol/http/nsHttpChannel.cpp @@ -337,8 +337,6 @@ nsHttpChannel::nsHttpChannel() , mStronglyFramed(false) , mUsedNetwork(0) , mAuthConnectionRestartable(0) - , mReqContentLengthDetermined(0) - , mReqContentLength(0U) , mPushedStream(nullptr) , mLocalBlocklist(false) , mOnTailUnblock(nullptr) diff --git a/netwerk/protocol/http/nsHttpChannel.h b/netwerk/protocol/http/nsHttpChannel.h index e4e9e03a9b42..2059044a0ca8 100644 --- a/netwerk/protocol/http/nsHttpChannel.h +++ b/netwerk/protocol/http/nsHttpChannel.h @@ -661,10 +661,6 @@ private: // the next authentication request can be sent on a whole new connection uint32_t mAuthConnectionRestartable : 1; - uint32_t mReqContentLengthDetermined : 1; - - uint64_t mReqContentLength; - nsTArray mRedirectFuncStack; // Needed for accurate DNS timing diff --git a/netwerk/test/gtest/TestPartiallySeekableInputStream.cpp b/netwerk/test/gtest/TestPartiallySeekableInputStream.cpp new file mode 100644 index 000000000000..dab1820e7d88 --- /dev/null +++ b/netwerk/test/gtest/TestPartiallySeekableInputStream.cpp @@ -0,0 +1,225 @@ +#include "gtest/gtest.h" + +#include "nsCOMPtr.h" +#include "nsIPipe.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "nsStringStream.h" +#include "mozilla/net/PartiallySeekableInputStream.h" + +using mozilla::net::PartiallySeekableInputStream; + +class NonSeekableStream final : public nsIInputStream +{ + nsCOMPtr mStream; + +public: + NS_DECL_THREADSAFE_ISUPPORTS + + explicit NonSeekableStream(const nsACString& aBuffer) + { + NS_NewCStringInputStream(getter_AddRefs(mStream), aBuffer); + } + + NS_IMETHOD + Available(uint64_t* aLength) override + { + return mStream->Available(aLength); + } + + NS_IMETHOD + Read(char* aBuffer, uint32_t aCount, uint32_t* aReadCount) override + { + return mStream->Read(aBuffer, aCount, aReadCount); + } + + NS_IMETHOD + ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t *aResult) override + { + return mStream->ReadSegments(aWriter, aClosure, aCount, aResult); + } + + NS_IMETHOD + Close() override + { + return mStream->Close(); + } + + NS_IMETHOD + IsNonBlocking(bool* aNonBlocking) override + { + return mStream->IsNonBlocking(aNonBlocking); + } + +private: + ~NonSeekableStream() {} +}; + +NS_IMPL_ISUPPORTS(NonSeekableStream, nsIInputStream) + +// Helper function for creating a non-seekable nsIInputStream + a +// PartiallySeekableInputStream. +PartiallySeekableInputStream* +CreateStream(uint32_t aSize, uint64_t aStreamSize, nsCString& aBuffer) +{ + aBuffer.SetLength(aSize); + for (uint32_t i = 0; i < aSize; ++i) { + aBuffer.BeginWriting()[i] = i % 10; + } + + RefPtr stream = new NonSeekableStream(aBuffer); + return new PartiallySeekableInputStream(stream, aStreamSize); +} + +// Simple reading. +TEST(TestPartiallySeekableInputStream, SimpleRead) { + const size_t kBufSize = 10; + + nsCString buf; + RefPtr psi = CreateStream(kBufSize, 5, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[kBufSize]; + uint32_t count; + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count))); + + // At this point, after reading more than the buffer size, seek is not + // allowed. + ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED, + psi->Seek(nsISeekableStream::NS_SEEK_SET, 0)); + + ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED, + psi->Seek(nsISeekableStream::NS_SEEK_END, 0)); + + ASSERT_EQ(NS_ERROR_NOT_IMPLEMENTED, + psi->Seek(nsISeekableStream::NS_SEEK_CUR, 0)); + + // Position is at the end of the stream. + int64_t pos; + ASSERT_EQ(NS_OK, psi->Tell(&pos)); + ASSERT_EQ((int64_t)kBufSize, pos); +} + +// Simple seek +TEST(TestPartiallySeekableInputStream, SimpleSeek) { + const size_t kBufSize = 10; + + nsCString buf; + RefPtr psi = CreateStream(kBufSize, 5, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + uint32_t count; + + { + char buf2[3]; + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, sizeof(buf2)); + ASSERT_TRUE(nsCString(buf.get(), sizeof(buf2)).Equals(nsCString(buf2, sizeof(buf2)))); + + int64_t pos; + ASSERT_EQ(NS_OK, psi->Tell(&pos)); + ASSERT_EQ((int64_t)sizeof(buf2), pos); + + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2), length); + } + + // Let's seek back to the beginning using NS_SEEK_SET + ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0)); + + { + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[3]; + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, sizeof(buf2)); + ASSERT_TRUE(nsCString(buf.get(), sizeof(buf2)).Equals(nsCString(buf2, sizeof(buf2)))); + + int64_t pos; + ASSERT_EQ(NS_OK, psi->Tell(&pos)); + ASSERT_EQ((int64_t)sizeof(buf2), pos); + + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2), length); + } + + // Let's seek back of 2 bytes using NS_SEEK_CUR + ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_CUR, -2)); + + { + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize - 1, length); + + char buf2[3]; + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, sizeof(buf2)); + ASSERT_TRUE(nsCString(buf.get() + 1, sizeof(buf2)).Equals(nsCString(buf2, sizeof(buf2)))); + + int64_t pos; + ASSERT_EQ(NS_OK, psi->Tell(&pos)); + ASSERT_EQ((int64_t)sizeof(buf2) + 1, pos); + + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize - sizeof(buf2) - 1, length); + } + + // Let's seek back to the beginning using NS_SEEK_SET + ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0)); + + { + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[kBufSize]; + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count))); + } +} + +// Full in cache +TEST(TestPartiallySeekableInputStream, FullCachedSeek) { + const size_t kBufSize = 10; + + nsCString buf; + RefPtr psi = CreateStream(kBufSize, 4096, buf); + + uint64_t length; + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + char buf2[kBufSize]; + uint32_t count; + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count))); + + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)0, length); + + ASSERT_EQ(NS_OK, psi->Seek(nsISeekableStream::NS_SEEK_SET, 0)); + + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)kBufSize, length); + + ASSERT_EQ(NS_OK, psi->Read(buf2, sizeof(buf2), &count)); + ASSERT_EQ(count, buf.Length()); + ASSERT_TRUE(nsCString(buf.get(), kBufSize).Equals(nsCString(buf2, count))); + + ASSERT_EQ(NS_OK, psi->Available(&length)); + ASSERT_EQ((uint64_t)0, length); +} diff --git a/netwerk/test/gtest/moz.build b/netwerk/test/gtest/moz.build index 6031abb32607..1b87db5984b8 100644 --- a/netwerk/test/gtest/moz.build +++ b/netwerk/test/gtest/moz.build @@ -7,6 +7,7 @@ UNIFIED_SOURCES += [ 'TestHeaders.cpp', 'TestHttpAuthUtils.cpp', + 'TestPartiallySeekableInputStream.cpp', 'TestProtocolProxyService.cpp', 'TestStandardURL.cpp', ]