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