diff options
Diffstat (limited to '')
-rw-r--r-- | xpcom/tests/gtest/TestBase64.cpp | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/xpcom/tests/gtest/TestBase64.cpp b/xpcom/tests/gtest/TestBase64.cpp new file mode 100644 index 0000000000..55fbcefe38 --- /dev/null +++ b/xpcom/tests/gtest/TestBase64.cpp @@ -0,0 +1,454 @@ +/* -*- Mode: C; tab-width: 8; 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/. */ + +#include "mozilla/Attributes.h" +#include "mozilla/Base64.h" +#include "nsComponentManagerUtils.h" +#include "nsIScriptableBase64Encoder.h" +#include "nsIInputStream.h" +#include "nsString.h" + +#include "gtest/gtest.h" +#include "mozilla/gtest/MozAssertions.h" + +struct Chunk { + Chunk(uint32_t l, const char* c) : mLength(l), mData(c) {} + + uint32_t mLength; + const char* mData; +}; + +struct Test { + Test(Chunk* c, const char* r) : mChunks(c), mResult(r) {} + + Chunk* mChunks; + const char* mResult; +}; + +static Chunk kTest1Chunks[] = {Chunk(9, "Hello sir"), Chunk(0, nullptr)}; + +static Chunk kTest2Chunks[] = {Chunk(3, "Hel"), Chunk(3, "lo "), + Chunk(3, "sir"), Chunk(0, nullptr)}; + +static Chunk kTest3Chunks[] = {Chunk(1, "I"), Chunk(0, nullptr)}; + +static Chunk kTest4Chunks[] = {Chunk(2, "Hi"), Chunk(0, nullptr)}; + +static Chunk kTest5Chunks[] = {Chunk(1, "B"), Chunk(2, "ob"), + Chunk(0, nullptr)}; + +static Chunk kTest6Chunks[] = {Chunk(2, "Bo"), Chunk(1, "b"), + Chunk(0, nullptr)}; + +static Chunk kTest7Chunks[] = {Chunk(1, "F"), // Carry over 1 + Chunk(4, "iref"), // Carry over 2 + Chunk(2, "ox"), // 1 + Chunk(4, " is "), // 2 + Chunk(2, "aw"), // 1 + Chunk(4, "esom"), // 2 + Chunk(2, "e!"), Chunk(0, nullptr)}; + +static Chunk kTest8Chunks[] = {Chunk(5, "ALL T"), + Chunk(1, "H"), + Chunk(4, "ESE "), + Chunk(2, "WO"), + Chunk(21, "RLDS ARE YOURS EXCEPT"), + Chunk(9, " EUROPA. "), + Chunk(25, "ATTEMPT NO LANDING THERE."), + Chunk(0, nullptr)}; + +static Test kTests[] = { + // Test 1, test a simple round string in one chunk + Test(kTest1Chunks, "SGVsbG8gc2ly"), + // Test 2, test a simple round string split into round chunks + Test(kTest2Chunks, "SGVsbG8gc2ly"), + // Test 3, test a single chunk that's 2 short + Test(kTest3Chunks, "SQ=="), + // Test 4, test a single chunk that's 1 short + Test(kTest4Chunks, "SGk="), + // Test 5, test a single chunk that's 2 short, followed by a chunk of 2 + Test(kTest5Chunks, "Qm9i"), + // Test 6, test a single chunk that's 1 short, followed by a chunk of 1 + Test(kTest6Chunks, "Qm9i"), + // Test 7, test alternating carryovers + Test(kTest7Chunks, "RmlyZWZveCBpcyBhd2Vzb21lIQ=="), + // Test 8, test a longish string + Test(kTest8Chunks, + "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOT" + "yBMQU5ESU5HIFRIRVJFLg=="), + // Terminator + Test(nullptr, nullptr)}; + +class FakeInputStream final : public nsIInputStream { + ~FakeInputStream() = default; + + public: + FakeInputStream() + : mTestNumber(0), + mTest(&kTests[0]), + mChunk(&mTest->mChunks[0]), + mClosed(false) {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIINPUTSTREAM + + void Reset(); + bool NextTest(); + void CheckTest(nsACString& aResult); + void CheckTest(nsAString& aResult); + + private: + uint32_t mTestNumber; + const Test* mTest; + const Chunk* mChunk; + bool mClosed; +}; + +NS_IMPL_ISUPPORTS(FakeInputStream, nsIInputStream) + +NS_IMETHODIMP +FakeInputStream::Close() { + mClosed = true; + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::Available(uint64_t* aAvailable) { + *aAvailable = 0; + + if (mClosed) return NS_BASE_STREAM_CLOSED; + + const Chunk* chunk = mChunk; + while (chunk->mLength) { + *aAvailable += chunk->mLength; + chunk++; + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::StreamStatus() { + return mClosed ? NS_BASE_STREAM_CLOSED : NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aOut) { + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +FakeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* aRead) { + *aRead = 0; + + if (mClosed) return NS_BASE_STREAM_CLOSED; + + while (mChunk->mLength) { + uint32_t written = 0; + + nsresult rv = (*aWriter)(this, aClosure, mChunk->mData, *aRead, + mChunk->mLength, &written); + + *aRead += written; + NS_ENSURE_SUCCESS(rv, rv); + + mChunk++; + } + + return NS_OK; +} + +NS_IMETHODIMP +FakeInputStream::IsNonBlocking(bool* aIsBlocking) { + *aIsBlocking = false; + return NS_OK; +} + +void FakeInputStream::Reset() { + mClosed = false; + mChunk = &mTest->mChunks[0]; +} + +bool FakeInputStream::NextTest() { + mTestNumber++; + mTest = &kTests[mTestNumber]; + mChunk = &mTest->mChunks[0]; + mClosed = false; + + return mTest->mChunks != nullptr; +} + +void FakeInputStream::CheckTest(nsACString& aResult) { + ASSERT_STREQ(aResult.BeginReading(), mTest->mResult); +} + +void FakeInputStream::CheckTest(nsAString& aResult) { + ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult)) + << "Actual: " << NS_ConvertUTF16toUTF8(aResult).get() << '\n' + << "Expected: " << mTest->mResult; +} + +TEST(Base64, StreamEncoder) +{ + nsCOMPtr<nsIScriptableBase64Encoder> encoder = + do_CreateInstance("@mozilla.org/scriptablebase64encoder;1"); + ASSERT_TRUE(encoder); + + RefPtr<FakeInputStream> stream = new FakeInputStream(); + do { + nsString wideString; + nsCString string; + + nsresult rv; + rv = encoder->EncodeToString(stream, 0, wideString); + ASSERT_NS_SUCCEEDED(rv); + + stream->Reset(); + + rv = encoder->EncodeToCString(stream, 0, string); + ASSERT_NS_SUCCEEDED(rv); + + stream->CheckTest(wideString); + stream->CheckTest(string); + } while (stream->NextTest()); +} + +struct EncodeDecodeTestCase { + const char* mInput; + const char* mOutput; +}; + +static EncodeDecodeTestCase sRFC4648TestCases[] = { + {"", ""}, + {"f", "Zg=="}, + {"fo", "Zm8="}, + {"foo", "Zm9v"}, + {"foob", "Zm9vYg=="}, + {"fooba", "Zm9vYmE="}, + {"foobar", "Zm9vYmFy"}, +}; + +TEST(Base64, RFC4648Encoding) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoCString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } + + for (auto& testcase : sRFC4648TestCases) { + NS_ConvertUTF8toUTF16 in(testcase.mInput); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Encoding_TransformAndAppend_EmptyPrefix) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoString out; + nsresult rv = + mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Encoding_TransformAndAppend_NonEmptyPrefix) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString in(testcase.mInput); + nsAutoString out{u"foo"_ns}; + nsresult rv = + mozilla::Base64EncodeAppend(in.BeginReading(), in.Length(), out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(StringBeginsWith(out, u"foo"_ns)); + ASSERT_TRUE(Substring(out, 3).EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, RFC4648Decoding) +{ + for (auto& testcase : sRFC4648TestCases) { + nsDependentCString out(testcase.mOutput); + nsAutoCString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.EqualsASCII(testcase.mInput)); + } + + for (auto& testcase : sRFC4648TestCases) { + NS_ConvertUTF8toUTF16 out(testcase.mOutput); + nsAutoString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.EqualsASCII(testcase.mInput)); + } +} + +TEST(Base64, RFC4648DecodingRawPointers) +{ + for (auto& testcase : sRFC4648TestCases) { + size_t outputLength = strlen(testcase.mOutput); + size_t inputLength = strlen(testcase.mInput); + + // This will be allocated by Base64Decode. + char* buffer = nullptr; + + uint32_t binaryLength = 0; + nsresult rv = mozilla::Base64Decode(testcase.mOutput, outputLength, &buffer, + &binaryLength); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_EQ(binaryLength, inputLength); + ASSERT_STREQ(testcase.mInput, buffer); + free(buffer); + } +} + +static EncodeDecodeTestCase sNonASCIITestCases[] = { + {"\x80", "gA=="}, + {"\xff", "/w=="}, + {"\x80\x80", "gIA="}, + {"\x80\x81", "gIE="}, + {"\xff\xff", "//8="}, + {"\x80\x80\x80", "gICA"}, + {"\xff\xff\xff", "////"}, + {"\x80\x80\x80\x80", "gICAgA=="}, + {"\xff\xff\xff\xff", "/////w=="}, +}; + +TEST(Base64, NonASCIIEncoding) +{ + for (auto& testcase : sNonASCIITestCases) { + nsDependentCString in(testcase.mInput); + nsAutoCString out; + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, NonASCIIEncodingWideString) +{ + for (auto& testcase : sNonASCIITestCases) { + nsAutoString in, out; + // XXX Handles Latin1 despite the name + AppendASCIItoUTF16(nsDependentCString(testcase.mInput), in); + nsresult rv = mozilla::Base64Encode(in, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsASCII(testcase.mOutput)); + } +} + +TEST(Base64, NonASCIIDecoding) +{ + for (auto& testcase : sNonASCIITestCases) { + nsDependentCString out(testcase.mOutput); + nsAutoCString in; + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(in.Equals(testcase.mInput)); + } +} + +TEST(Base64, NonASCIIDecodingWideString) +{ + for (auto& testcase : sNonASCIITestCases) { + nsAutoString in, out; + // XXX Handles Latin1 despite the name + AppendASCIItoUTF16(nsDependentCString(testcase.mOutput), out); + nsresult rv = mozilla::Base64Decode(out, in); + ASSERT_NS_SUCCEEDED(rv); + // Can't use EqualsASCII, because our comparison string isn't ASCII. + for (size_t i = 0; i < in.Length(); ++i) { + ASSERT_TRUE(((unsigned int)in[i] & 0xff00) == 0); + ASSERT_EQ((unsigned char)in[i], (unsigned char)testcase.mInput[i]); + } + ASSERT_TRUE(strlen(testcase.mInput) == in.Length()); + } +} + +// For historical reasons, our wide string base64 encode routines mask off +// the high bits of non-latin1 wide strings. +TEST(Base64, EncodeNon8BitWideString) +{ + { + const nsAutoString non8Bit(u"\x1ff"); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsLiteral("/w==")); + } + { + const nsAutoString non8Bit(u"\xfff"); + nsAutoString out; + nsresult rv = mozilla::Base64Encode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.EqualsLiteral("/w==")); + } +} + +// For historical reasons, our wide string base64 decode routines mask off +// the high bits of non-latin1 wide strings. +TEST(Base64, DecodeNon8BitWideString) +{ + { + // This would be "/w==" in a nsCString + const nsAutoString non8Bit(u"\x12f\x177=="); + const nsAutoString expectedOutput(u"\xff"); + ASSERT_EQ(non8Bit.Length(), 4u); + nsAutoString out; + nsresult rv = mozilla::Base64Decode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.Equals(expectedOutput)); + } + { + const nsAutoString non8Bit(u"\xf2f\xf77=="); + const nsAutoString expectedOutput(u"\xff"); + nsAutoString out; + nsresult rv = mozilla::Base64Decode(non8Bit, out); + ASSERT_NS_SUCCEEDED(rv); + ASSERT_TRUE(out.Equals(expectedOutput)); + } +} + +TEST(Base64, DecodeWideTo8Bit) +{ + for (auto& testCase : sRFC4648TestCases) { + const nsAutoCString in8bit(testCase.mOutput); + const NS_ConvertUTF8toUTF16 inWide(testCase.mOutput); + nsAutoCString out2; + nsAutoCString out1; + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(inWide, out1))); + MOZ_RELEASE_ASSERT(NS_SUCCEEDED(mozilla::Base64Decode(in8bit, out2))); + ASSERT_EQ(out1, out2); + } +} + +TEST(Base64, TruncateOnInvalidDecodeCString) +{ + constexpr auto invalid = "@@=="_ns; + nsAutoCString out("I should be truncated!"); + nsresult rv = mozilla::Base64Decode(invalid, out); + ASSERT_NS_FAILED(rv); + ASSERT_EQ(out.Length(), 0u); +} + +TEST(Base64, TruncateOnInvalidDecodeWideString) +{ + constexpr auto invalid = u"@@=="_ns; + nsAutoString out(u"I should be truncated!"); + nsresult rv = mozilla::Base64Decode(invalid, out); + ASSERT_NS_FAILED(rv); + ASSERT_EQ(out.Length(), 0u); +} + +// TODO: Add tests for OOM handling. |