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