1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "mozilla/Attributes.h"
7 #include "mozilla/Base64.h"
8 #include "nsComponentManagerUtils.h"
9 #include "nsIScriptableBase64Encoder.h"
10 #include "nsIInputStream.h"
11 #include "nsString.h"
12 
13 #include "gtest/gtest.h"
14 
15 struct Chunk {
ChunkChunk16   Chunk(uint32_t l, const char* c) : mLength(l), mData(c) {}
17 
18   uint32_t mLength;
19   const char* mData;
20 };
21 
22 struct Test {
TestTest23   Test(Chunk* c, const char* r) : mChunks(c), mResult(r) {}
24 
25   Chunk* mChunks;
26   const char* mResult;
27 };
28 
29 static Chunk kTest1Chunks[] = {Chunk(9, "Hello sir"), Chunk(0, nullptr)};
30 
31 static Chunk kTest2Chunks[] = {Chunk(3, "Hel"), Chunk(3, "lo "),
32                                Chunk(3, "sir"), Chunk(0, nullptr)};
33 
34 static Chunk kTest3Chunks[] = {Chunk(1, "I"), Chunk(0, nullptr)};
35 
36 static Chunk kTest4Chunks[] = {Chunk(2, "Hi"), Chunk(0, nullptr)};
37 
38 static Chunk kTest5Chunks[] = {Chunk(1, "B"), Chunk(2, "ob"),
39                                Chunk(0, nullptr)};
40 
41 static Chunk kTest6Chunks[] = {Chunk(2, "Bo"), Chunk(1, "b"),
42                                Chunk(0, nullptr)};
43 
44 static Chunk kTest7Chunks[] = {Chunk(1, "F"),     // Carry over 1
45                                Chunk(4, "iref"),  // Carry over 2
46                                Chunk(2, "ox"),    //            1
47                                Chunk(4, " is "),  //            2
48                                Chunk(2, "aw"),    //            1
49                                Chunk(4, "esom"),  //            2
50                                Chunk(2, "e!"),   Chunk(0, nullptr)};
51 
52 static Chunk kTest8Chunks[] = {Chunk(5, "ALL T"),
53                                Chunk(1, "H"),
54                                Chunk(4, "ESE "),
55                                Chunk(2, "WO"),
56                                Chunk(21, "RLDS ARE YOURS EXCEPT"),
57                                Chunk(9, " EUROPA. "),
58                                Chunk(25, "ATTEMPT NO LANDING THERE."),
59                                Chunk(0, nullptr)};
60 
61 static Test kTests[] = {
62     // Test 1, test a simple round string in one chunk
63     Test(kTest1Chunks, "SGVsbG8gc2ly"),
64     // Test 2, test a simple round string split into round chunks
65     Test(kTest2Chunks, "SGVsbG8gc2ly"),
66     // Test 3, test a single chunk that's 2 short
67     Test(kTest3Chunks, "SQ=="),
68     // Test 4, test a single chunk that's 1 short
69     Test(kTest4Chunks, "SGk="),
70     // Test 5, test a single chunk that's 2 short, followed by a chunk of 2
71     Test(kTest5Chunks, "Qm9i"),
72     // Test 6, test a single chunk that's 1 short, followed by a chunk of 1
73     Test(kTest6Chunks, "Qm9i"),
74     // Test 7, test alternating carryovers
75     Test(kTest7Chunks, "RmlyZWZveCBpcyBhd2Vzb21lIQ=="),
76     // Test 8, test a longish string
77     Test(kTest8Chunks,
78          "QUxMIFRIRVNFIFdPUkxEUyBBUkUgWU9VUlMgRVhDRVBUIEVVUk9QQS4gQVRURU1QVCBOT"
79          "yBMQU5ESU5HIFRIRVJFLg=="),
80     // Terminator
81     Test(nullptr, nullptr)};
82 
83 class FakeInputStream final : public nsIInputStream {
84   ~FakeInputStream() = default;
85 
86  public:
FakeInputStream()87   FakeInputStream()
88       : mTestNumber(0),
89         mTest(&kTests[0]),
90         mChunk(&mTest->mChunks[0]),
91         mClosed(false) {}
92 
93   NS_DECL_ISUPPORTS
94   NS_DECL_NSIINPUTSTREAM
95 
96   void Reset();
97   bool NextTest();
98   void CheckTest(nsACString& aResult);
99   void CheckTest(nsAString& aResult);
100 
101  private:
102   uint32_t mTestNumber;
103   const Test* mTest;
104   const Chunk* mChunk;
105   bool mClosed;
106 };
107 
NS_IMPL_ISUPPORTS(FakeInputStream,nsIInputStream)108 NS_IMPL_ISUPPORTS(FakeInputStream, nsIInputStream)
109 
110 NS_IMETHODIMP
111 FakeInputStream::Close() {
112   mClosed = true;
113   return NS_OK;
114 }
115 
116 NS_IMETHODIMP
Available(uint64_t * aAvailable)117 FakeInputStream::Available(uint64_t* aAvailable) {
118   *aAvailable = 0;
119 
120   if (mClosed) return NS_BASE_STREAM_CLOSED;
121 
122   const Chunk* chunk = mChunk;
123   while (chunk->mLength) {
124     *aAvailable += chunk->mLength;
125     chunk++;
126   }
127 
128   return NS_OK;
129 }
130 
131 NS_IMETHODIMP
Read(char * aBuffer,uint32_t aCount,uint32_t * aOut)132 FakeInputStream::Read(char* aBuffer, uint32_t aCount, uint32_t* aOut) {
133   return NS_ERROR_NOT_IMPLEMENTED;
134 }
135 
136 NS_IMETHODIMP
ReadSegments(nsWriteSegmentFun aWriter,void * aClosure,uint32_t aCount,uint32_t * aRead)137 FakeInputStream::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure,
138                               uint32_t aCount, uint32_t* aRead) {
139   *aRead = 0;
140 
141   if (mClosed) return NS_BASE_STREAM_CLOSED;
142 
143   while (mChunk->mLength) {
144     uint32_t written = 0;
145 
146     nsresult rv = (*aWriter)(this, aClosure, mChunk->mData, *aRead,
147                              mChunk->mLength, &written);
148 
149     *aRead += written;
150     NS_ENSURE_SUCCESS(rv, rv);
151 
152     mChunk++;
153   }
154 
155   return NS_OK;
156 }
157 
158 NS_IMETHODIMP
IsNonBlocking(bool * aIsBlocking)159 FakeInputStream::IsNonBlocking(bool* aIsBlocking) {
160   *aIsBlocking = false;
161   return NS_OK;
162 }
163 
Reset()164 void FakeInputStream::Reset() {
165   mClosed = false;
166   mChunk = &mTest->mChunks[0];
167 }
168 
NextTest()169 bool FakeInputStream::NextTest() {
170   mTestNumber++;
171   mTest = &kTests[mTestNumber];
172   mChunk = &mTest->mChunks[0];
173   mClosed = false;
174 
175   return mTest->mChunks ? true : false;
176 }
177 
CheckTest(nsACString & aResult)178 void FakeInputStream::CheckTest(nsACString& aResult) {
179   ASSERT_STREQ(aResult.BeginReading(), mTest->mResult);
180 }
181 
CheckTest(nsAString & aResult)182 void FakeInputStream::CheckTest(nsAString& aResult) {
183   ASSERT_TRUE(aResult.EqualsASCII(mTest->mResult))
184   << "Actual:   " << aResult.BeginReading() << std::endl
185   << "Expected: " << mTest->mResult;
186 }
187 
TEST(Base64,StreamEncoder)188 TEST(Base64, StreamEncoder)
189 {
190   nsCOMPtr<nsIScriptableBase64Encoder> encoder =
191       do_CreateInstance("@mozilla.org/scriptablebase64encoder;1");
192   ASSERT_TRUE(encoder);
193 
194   RefPtr<FakeInputStream> stream = new FakeInputStream();
195   do {
196     nsString wideString;
197     nsCString string;
198 
199     nsresult rv;
200     rv = encoder->EncodeToString(stream, 0, wideString);
201     ASSERT_TRUE(NS_SUCCEEDED(rv));
202 
203     stream->Reset();
204 
205     rv = encoder->EncodeToCString(stream, 0, string);
206     ASSERT_TRUE(NS_SUCCEEDED(rv));
207 
208     stream->CheckTest(wideString);
209     stream->CheckTest(string);
210   } while (stream->NextTest());
211 }
212 
213 struct EncodeDecodeTestCase {
214   const char* mInput;
215   const char* mOutput;
216 };
217 
218 static EncodeDecodeTestCase sRFC4648TestCases[] = {
219     {"", ""},
220     {"f", "Zg=="},
221     {"fo", "Zm8="},
222     {"foo", "Zm9v"},
223     {"foob", "Zm9vYg=="},
224     {"fooba", "Zm9vYmE="},
225     {"foobar", "Zm9vYmFy"},
226 };
227 
TEST(Base64,RFC4648Encoding)228 TEST(Base64, RFC4648Encoding)
229 {
230   for (auto& testcase : sRFC4648TestCases) {
231     nsDependentCString in(testcase.mInput);
232     nsAutoCString out;
233     nsresult rv = mozilla::Base64Encode(in, out);
234     ASSERT_TRUE(NS_SUCCEEDED(rv));
235     ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
236   }
237 
238   for (auto& testcase : sRFC4648TestCases) {
239     NS_ConvertUTF8toUTF16 in(testcase.mInput);
240     nsAutoString out;
241     nsresult rv = mozilla::Base64Encode(in, out);
242     ASSERT_TRUE(NS_SUCCEEDED(rv));
243     ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
244   }
245 }
246 
TEST(Base64,RFC4648Decoding)247 TEST(Base64, RFC4648Decoding)
248 {
249   for (auto& testcase : sRFC4648TestCases) {
250     nsDependentCString out(testcase.mOutput);
251     nsAutoCString in;
252     nsresult rv = mozilla::Base64Decode(out, in);
253     ASSERT_TRUE(NS_SUCCEEDED(rv));
254     ASSERT_TRUE(in.EqualsASCII(testcase.mInput));
255   }
256 
257   for (auto& testcase : sRFC4648TestCases) {
258     NS_ConvertUTF8toUTF16 out(testcase.mOutput);
259     nsAutoString in;
260     nsresult rv = mozilla::Base64Decode(out, in);
261     ASSERT_TRUE(NS_SUCCEEDED(rv));
262     ASSERT_TRUE(in.EqualsASCII(testcase.mInput));
263   }
264 }
265 
TEST(Base64,RFC4648DecodingRawPointers)266 TEST(Base64, RFC4648DecodingRawPointers)
267 {
268   for (auto& testcase : sRFC4648TestCases) {
269     size_t outputLength = strlen(testcase.mOutput);
270     size_t inputLength = strlen(testcase.mInput);
271 
272     // This will be allocated by Base64Decode.
273     char* buffer = nullptr;
274 
275     uint32_t binaryLength = 0;
276     nsresult rv = mozilla::Base64Decode(testcase.mOutput, outputLength, &buffer,
277                                         &binaryLength);
278     ASSERT_TRUE(NS_SUCCEEDED(rv));
279     ASSERT_EQ(binaryLength, inputLength);
280     ASSERT_STREQ(testcase.mInput, buffer);
281     free(buffer);
282   }
283 }
284 
285 static EncodeDecodeTestCase sNonASCIITestCases[] = {
286     {"\x80", "gA=="},
287     {"\xff", "/w=="},
288     {"\x80\x80", "gIA="},
289     {"\x80\x81", "gIE="},
290     {"\xff\xff", "//8="},
291     {"\x80\x80\x80", "gICA"},
292     {"\xff\xff\xff", "////"},
293     {"\x80\x80\x80\x80", "gICAgA=="},
294     {"\xff\xff\xff\xff", "/////w=="},
295 };
296 
TEST(Base64,NonASCIIEncoding)297 TEST(Base64, NonASCIIEncoding)
298 {
299   for (auto& testcase : sNonASCIITestCases) {
300     nsDependentCString in(testcase.mInput);
301     nsAutoCString out;
302     nsresult rv = mozilla::Base64Encode(in, out);
303     ASSERT_TRUE(NS_SUCCEEDED(rv));
304     ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
305   }
306 }
307 
TEST(Base64,NonASCIIEncodingWideString)308 TEST(Base64, NonASCIIEncodingWideString)
309 {
310   for (auto& testcase : sNonASCIITestCases) {
311     nsAutoString in, out;
312     // XXX Handles Latin1 despite the name
313     AppendASCIItoUTF16(nsDependentCString(testcase.mInput), in);
314     nsresult rv = mozilla::Base64Encode(in, out);
315     ASSERT_TRUE(NS_SUCCEEDED(rv));
316     ASSERT_TRUE(out.EqualsASCII(testcase.mOutput));
317   }
318 }
319 
TEST(Base64,NonASCIIDecoding)320 TEST(Base64, NonASCIIDecoding)
321 {
322   for (auto& testcase : sNonASCIITestCases) {
323     nsDependentCString out(testcase.mOutput);
324     nsAutoCString in;
325     nsresult rv = mozilla::Base64Decode(out, in);
326     ASSERT_TRUE(NS_SUCCEEDED(rv));
327     ASSERT_TRUE(in.Equals(testcase.mInput));
328   }
329 }
330 
TEST(Base64,NonASCIIDecodingWideString)331 TEST(Base64, NonASCIIDecodingWideString)
332 {
333   for (auto& testcase : sNonASCIITestCases) {
334     nsAutoString in, out;
335     // XXX Handles Latin1 despite the name
336     AppendASCIItoUTF16(nsDependentCString(testcase.mOutput), out);
337     nsresult rv = mozilla::Base64Decode(out, in);
338     ASSERT_TRUE(NS_SUCCEEDED(rv));
339     // Can't use EqualsASCII, because our comparison string isn't ASCII.
340     for (size_t i = 0; i < in.Length(); ++i) {
341       ASSERT_TRUE(((unsigned int)in[i] & 0xff00) == 0);
342       ASSERT_EQ((unsigned char)in[i], (unsigned char)testcase.mInput[i]);
343     }
344     ASSERT_TRUE(strlen(testcase.mInput) == in.Length());
345   }
346 }
347 
348 // For historical reasons, our wide string base64 encode routines mask off
349 // the high bits of non-latin1 wide strings.
TEST(Base64,EncodeNon8BitWideString)350 TEST(Base64, EncodeNon8BitWideString)
351 {
352   {
353     const nsAutoString non8Bit(u"\x1ff");
354     nsAutoString out;
355     nsresult rv = mozilla::Base64Encode(non8Bit, out);
356     ASSERT_TRUE(NS_SUCCEEDED(rv));
357     ASSERT_TRUE(out.EqualsLiteral("/w=="));
358   }
359   {
360     const nsAutoString non8Bit(u"\xfff");
361     nsAutoString out;
362     nsresult rv = mozilla::Base64Encode(non8Bit, out);
363     ASSERT_TRUE(NS_SUCCEEDED(rv));
364     ASSERT_TRUE(out.EqualsLiteral("/w=="));
365   }
366 }
367 
368 // For historical reasons, our wide string base64 decode routines mask off
369 // the high bits of non-latin1 wide strings.
TEST(Base64,DecodeNon8BitWideString)370 TEST(Base64, DecodeNon8BitWideString)
371 {
372   {
373     // This would be "/w==" in a nsCString
374     const nsAutoString non8Bit(u"\x12f\x177==");
375     const nsAutoString expectedOutput(u"\xff");
376     ASSERT_EQ(non8Bit.Length(), 4u);
377     nsAutoString out;
378     nsresult rv = mozilla::Base64Decode(non8Bit, out);
379     ASSERT_TRUE(NS_SUCCEEDED(rv));
380     ASSERT_TRUE(out.Equals(expectedOutput));
381   }
382   {
383     const nsAutoString non8Bit(u"\xf2f\xf77==");
384     const nsAutoString expectedOutput(u"\xff");
385     nsAutoString out;
386     nsresult rv = mozilla::Base64Decode(non8Bit, out);
387     ASSERT_TRUE(NS_SUCCEEDED(rv));
388     ASSERT_TRUE(out.Equals(expectedOutput));
389   }
390 }
391 
TEST(Base64,TruncateOnInvalidDecodeCString)392 TEST(Base64, TruncateOnInvalidDecodeCString)
393 {
394   NS_NAMED_LITERAL_CSTRING(invalid, "@@==");
395   nsAutoCString out("I should be truncated!");
396   nsresult rv = mozilla::Base64Decode(invalid, out);
397   ASSERT_TRUE(NS_FAILED(rv));
398   ASSERT_EQ(out.Length(), 0u);
399 }
400 
TEST(Base64,TruncateOnInvalidDecodeWideString)401 TEST(Base64, TruncateOnInvalidDecodeWideString)
402 {
403   NS_NAMED_LITERAL_STRING(invalid, "@@==");
404   nsAutoString out(u"I should be truncated!");
405   nsresult rv = mozilla::Base64Decode(invalid, out);
406   ASSERT_TRUE(NS_FAILED(rv));
407   ASSERT_EQ(out.Length(), 0u);
408 }
409 
410 // TODO: Add tests for OOM handling.
411