Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : #include "Base64.h"
8 :
9 : #include "mozilla/ScopeExit.h"
10 : #include "mozilla/UniquePtrExtensions.h"
11 : #include "nsIInputStream.h"
12 : #include "nsString.h"
13 : #include "nsTArray.h"
14 :
15 : #include "plbase64.h"
16 :
17 : namespace {
18 :
19 : // BEGIN base64 encode code copied and modified from NSPR
20 : const unsigned char* base =
21 : (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
22 : "abcdefghijklmnopqrstuvwxyz"
23 : "0123456789+/";
24 :
25 : template<typename T>
26 : static void
27 0 : Encode3to4(const unsigned char* aSrc, T* aDest)
28 : {
29 0 : uint32_t b32 = (uint32_t)0;
30 0 : int i, j = 18;
31 :
32 0 : for (i = 0; i < 3; ++i) {
33 0 : b32 <<= 8;
34 0 : b32 |= (uint32_t)aSrc[i];
35 : }
36 :
37 0 : for (i = 0; i < 4; ++i) {
38 0 : aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)];
39 0 : j -= 6;
40 : }
41 0 : }
42 :
43 : template<typename T>
44 : static void
45 0 : Encode2to4(const unsigned char* aSrc, T* aDest)
46 : {
47 0 : aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)];
48 0 : aDest[1] = base[(uint32_t)(((aSrc[0] & 0x03) << 4) | ((aSrc[1] >> 4) & 0x0F))];
49 0 : aDest[2] = base[(uint32_t)((aSrc[1] & 0x0F) << 2)];
50 0 : aDest[3] = (unsigned char)'=';
51 0 : }
52 :
53 : template<typename T>
54 : static void
55 0 : Encode1to4(const unsigned char* aSrc, T* aDest)
56 : {
57 0 : aDest[0] = base[(uint32_t)((aSrc[0] >> 2) & 0x3F)];
58 0 : aDest[1] = base[(uint32_t)((aSrc[0] & 0x03) << 4)];
59 0 : aDest[2] = (unsigned char)'=';
60 0 : aDest[3] = (unsigned char)'=';
61 0 : }
62 :
63 : template<typename T>
64 : static void
65 0 : Encode(const unsigned char* aSrc, uint32_t aSrcLen, T* aDest)
66 : {
67 0 : while (aSrcLen >= 3) {
68 0 : Encode3to4(aSrc, aDest);
69 0 : aSrc += 3;
70 0 : aDest += 4;
71 0 : aSrcLen -= 3;
72 : }
73 :
74 0 : switch (aSrcLen) {
75 : case 2:
76 0 : Encode2to4(aSrc, aDest);
77 0 : break;
78 : case 1:
79 0 : Encode1to4(aSrc, aDest);
80 0 : break;
81 : case 0:
82 0 : break;
83 : default:
84 0 : NS_NOTREACHED("coding error");
85 : }
86 0 : }
87 :
88 : // END base64 encode code copied and modified from NSPR.
89 :
90 : template<typename T>
91 : struct EncodeInputStream_State
92 : {
93 : unsigned char c[3];
94 : uint8_t charsOnStack;
95 : typename T::char_type* buffer;
96 : };
97 :
98 : template<typename T>
99 : nsresult
100 0 : EncodeInputStream_Encoder(nsIInputStream* aStream,
101 : void* aClosure,
102 : const char* aFromSegment,
103 : uint32_t aToOffset,
104 : uint32_t aCount,
105 : uint32_t* aWriteCount)
106 : {
107 0 : NS_ASSERTION(aCount > 0, "Er, what?");
108 :
109 : EncodeInputStream_State<T>* state =
110 0 : static_cast<EncodeInputStream_State<T>*>(aClosure);
111 :
112 : // If we have any data left from last time, encode it now.
113 0 : uint32_t countRemaining = aCount;
114 0 : const unsigned char* src = (const unsigned char*)aFromSegment;
115 0 : if (state->charsOnStack) {
116 : unsigned char firstSet[4];
117 0 : if (state->charsOnStack == 1) {
118 0 : firstSet[0] = state->c[0];
119 0 : firstSet[1] = src[0];
120 0 : firstSet[2] = (countRemaining > 1) ? src[1] : '\0';
121 0 : firstSet[3] = '\0';
122 : } else /* state->charsOnStack == 2 */ {
123 0 : firstSet[0] = state->c[0];
124 0 : firstSet[1] = state->c[1];
125 0 : firstSet[2] = src[0];
126 0 : firstSet[3] = '\0';
127 : }
128 0 : Encode(firstSet, 3, state->buffer);
129 0 : state->buffer += 4;
130 0 : countRemaining -= (3 - state->charsOnStack);
131 0 : src += (3 - state->charsOnStack);
132 0 : state->charsOnStack = 0;
133 : }
134 :
135 : // Encode the bulk of the
136 0 : uint32_t encodeLength = countRemaining - countRemaining % 3;
137 0 : MOZ_ASSERT(encodeLength % 3 == 0,
138 : "Should have an exact number of triplets!");
139 0 : Encode(src, encodeLength, state->buffer);
140 0 : state->buffer += (encodeLength / 3) * 4;
141 0 : src += encodeLength;
142 0 : countRemaining -= encodeLength;
143 :
144 : // We must consume all data, so if there's some data left stash it
145 0 : *aWriteCount = aCount;
146 :
147 0 : if (countRemaining) {
148 : // We should never have a full triplet left at this point.
149 0 : MOZ_ASSERT(countRemaining < 3, "We should have encoded more!");
150 0 : state->c[0] = src[0];
151 0 : state->c[1] = (countRemaining == 2) ? src[1] : '\0';
152 0 : state->charsOnStack = countRemaining;
153 : }
154 :
155 0 : return NS_OK;
156 : }
157 :
158 : template<typename T>
159 : nsresult
160 0 : EncodeInputStream(nsIInputStream* aInputStream,
161 : T& aDest,
162 : uint32_t aCount,
163 : uint32_t aOffset)
164 : {
165 : nsresult rv;
166 0 : uint64_t count64 = aCount;
167 :
168 0 : if (!aCount) {
169 0 : rv = aInputStream->Available(&count64);
170 0 : if (NS_WARN_IF(NS_FAILED(rv))) {
171 0 : return rv;
172 : }
173 : // if count64 is over 4GB, it will be failed at the below condition,
174 : // then will return NS_ERROR_OUT_OF_MEMORY
175 0 : aCount = (uint32_t)count64;
176 : }
177 :
178 : uint64_t countlong =
179 0 : (count64 + 2) / 3 * 4; // +2 due to integer math.
180 0 : if (countlong + aOffset > UINT32_MAX) {
181 0 : return NS_ERROR_OUT_OF_MEMORY;
182 : }
183 :
184 0 : uint32_t count = uint32_t(countlong);
185 :
186 0 : if (!aDest.SetLength(count + aOffset, mozilla::fallible)) {
187 0 : return NS_ERROR_OUT_OF_MEMORY;
188 : }
189 :
190 : EncodeInputStream_State<T> state;
191 0 : state.charsOnStack = 0;
192 0 : state.c[2] = '\0';
193 0 : state.buffer = aOffset + aDest.BeginWriting();
194 :
195 0 : while (1) {
196 0 : uint32_t read = 0;
197 :
198 0 : rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>,
199 : (void*)&state,
200 : aCount,
201 : &read);
202 0 : if (NS_FAILED(rv)) {
203 0 : if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
204 0 : MOZ_CRASH("Not implemented for async streams!");
205 : }
206 0 : if (rv == NS_ERROR_NOT_IMPLEMENTED) {
207 0 : MOZ_CRASH("Requires a stream that implements ReadSegments!");
208 : }
209 0 : return rv;
210 : }
211 :
212 0 : if (!read) {
213 0 : break;
214 : }
215 : }
216 :
217 : // Finish encoding if anything is left
218 0 : if (state.charsOnStack) {
219 0 : Encode(state.c, state.charsOnStack, state.buffer);
220 : }
221 :
222 0 : if (aDest.Length()) {
223 : // May belong to an nsCString with an unallocated buffer, so only null
224 : // terminate if there is a need to.
225 0 : *aDest.EndWriting() = '\0';
226 : }
227 :
228 0 : return NS_OK;
229 : }
230 :
231 : static const char kBase64URLAlphabet[] =
232 : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
233 :
234 : // Maps an encoded character to a value in the Base64 URL alphabet, per
235 : // RFC 4648, Table 2. Invalid input characters map to UINT8_MAX.
236 : static const uint8_t kBase64URLDecodeTable[] = {
237 : 255, 255, 255, 255, 255, 255, 255, 255,
238 : 255, 255, 255, 255, 255, 255, 255, 255,
239 : 255, 255, 255, 255, 255, 255, 255, 255,
240 : 255, 255, 255, 255, 255, 255, 255, 255,
241 : 255, 255, 255, 255, 255, 255, 255, 255,
242 : 255, 255, 255, 255, 255,
243 : 62 /* - */,
244 : 255, 255,
245 : 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */
246 : 255, 255, 255, 255, 255, 255, 255,
247 : 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
248 : 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* A - Z */
249 : 255, 255, 255, 255,
250 : 63 /* _ */,
251 : 255,
252 : 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
253 : 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, /* a - z */
254 : 255, 255, 255, 255,
255 : };
256 :
257 : bool
258 0 : Base64URLCharToValue(char aChar, uint8_t* aValue) {
259 0 : uint8_t index = static_cast<uint8_t>(aChar);
260 0 : *aValue = kBase64URLDecodeTable[index & 0x7f];
261 0 : return (*aValue != 255) && !(index & ~0x7f);
262 : }
263 :
264 : } // namespace
265 :
266 : namespace mozilla {
267 :
268 : nsresult
269 0 : Base64EncodeInputStream(nsIInputStream* aInputStream,
270 : nsACString& aDest,
271 : uint32_t aCount,
272 : uint32_t aOffset)
273 : {
274 0 : return EncodeInputStream<nsACString>(aInputStream, aDest, aCount, aOffset);
275 : }
276 :
277 : nsresult
278 0 : Base64EncodeInputStream(nsIInputStream* aInputStream,
279 : nsAString& aDest,
280 : uint32_t aCount,
281 : uint32_t aOffset)
282 : {
283 0 : return EncodeInputStream<nsAString>(aInputStream, aDest, aCount, aOffset);
284 : }
285 :
286 : nsresult
287 0 : Base64Encode(const char* aBinary, uint32_t aBinaryLen, char** aBase64)
288 : {
289 : // Check for overflow.
290 0 : if (aBinaryLen > (UINT32_MAX / 4) * 3) {
291 0 : return NS_ERROR_FAILURE;
292 : }
293 :
294 : // Don't ask PR_Base64Encode to encode empty strings.
295 0 : if (aBinaryLen == 0) {
296 0 : *aBase64 = (char*)moz_xmalloc(1);
297 0 : (*aBase64)[0] = '\0';
298 0 : return NS_OK;
299 : }
300 :
301 0 : *aBase64 = nullptr;
302 0 : uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4;
303 :
304 : // Add one byte for null termination.
305 0 : UniqueFreePtr<char[]> base64((char*)malloc(base64Len + 1));
306 0 : if (!base64) {
307 0 : return NS_ERROR_OUT_OF_MEMORY;
308 : }
309 :
310 0 : if (!PL_Base64Encode(aBinary, aBinaryLen, base64.get())) {
311 0 : return NS_ERROR_INVALID_ARG;
312 : }
313 :
314 : // PL_Base64Encode doesn't null terminate the buffer for us when we pass
315 : // the buffer in. Do that manually.
316 0 : base64[base64Len] = '\0';
317 :
318 0 : *aBase64 = base64.release();
319 0 : return NS_OK;
320 : }
321 :
322 : nsresult
323 1 : Base64Encode(const nsACString& aBinary, nsACString& aBase64)
324 : {
325 : // Check for overflow.
326 1 : if (aBinary.Length() > (UINT32_MAX / 4) * 3) {
327 0 : return NS_ERROR_FAILURE;
328 : }
329 :
330 : // Don't ask PR_Base64Encode to encode empty strings.
331 1 : if (aBinary.IsEmpty()) {
332 0 : aBase64.Truncate();
333 0 : return NS_OK;
334 : }
335 :
336 1 : uint32_t base64Len = ((aBinary.Length() + 2) / 3) * 4;
337 :
338 : // Add one byte for null termination.
339 1 : if (!aBase64.SetCapacity(base64Len + 1, fallible)) {
340 0 : return NS_ERROR_OUT_OF_MEMORY;
341 : }
342 :
343 1 : char* base64 = aBase64.BeginWriting();
344 1 : if (!PL_Base64Encode(aBinary.BeginReading(), aBinary.Length(), base64)) {
345 0 : aBase64.Truncate();
346 0 : return NS_ERROR_INVALID_ARG;
347 : }
348 :
349 : // PL_Base64Encode doesn't null terminate the buffer for us when we pass
350 : // the buffer in. Do that manually.
351 1 : base64[base64Len] = '\0';
352 :
353 1 : aBase64.SetLength(base64Len);
354 1 : return NS_OK;
355 : }
356 :
357 : nsresult
358 0 : Base64Encode(const nsAString& aBinary, nsAString& aBase64)
359 : {
360 0 : auto truncater = mozilla::MakeScopeExit([&]() { aBase64.Truncate(); });
361 :
362 : // XXX We should really consider decoding directly from the string, rather
363 : // than making a separate copy here.
364 0 : nsAutoCString binary;
365 0 : if (!binary.SetCapacity(aBinary.Length(), mozilla::fallible)) {
366 0 : return NS_ERROR_OUT_OF_MEMORY;
367 : }
368 0 : LossyCopyUTF16toASCII(aBinary, binary);
369 :
370 0 : nsAutoCString base64;
371 :
372 0 : nsresult rv = Base64Encode(binary, base64);
373 0 : NS_ENSURE_SUCCESS(rv, rv);
374 :
375 0 : if (!CopyASCIItoUTF16(base64, aBase64, mozilla::fallible)) {
376 0 : return NS_ERROR_OUT_OF_MEMORY;
377 : }
378 :
379 0 : truncater.release();
380 :
381 0 : return rv;
382 : }
383 :
384 : static nsresult
385 172 : Base64DecodeHelper(const char* aBase64, uint32_t aBase64Len, char* aBinary,
386 : uint32_t* aBinaryLen)
387 : {
388 172 : MOZ_ASSERT(aBinary);
389 172 : if (!PL_Base64Decode(aBase64, aBase64Len, aBinary)) {
390 0 : return NS_ERROR_INVALID_ARG;
391 : }
392 :
393 : // PL_Base64Decode doesn't null terminate the buffer for us when we pass
394 : // the buffer in. Do that manually, taking into account the number of '='
395 : // characters we were passed.
396 172 : if (aBase64Len != 0 && aBase64[aBase64Len - 1] == '=') {
397 139 : if (aBase64Len > 1 && aBase64[aBase64Len - 2] == '=') {
398 92 : *aBinaryLen -= 2;
399 : } else {
400 47 : *aBinaryLen -= 1;
401 : }
402 : }
403 172 : aBinary[*aBinaryLen] = '\0';
404 172 : return NS_OK;
405 : }
406 :
407 : nsresult
408 0 : Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary,
409 : uint32_t* aBinaryLen)
410 : {
411 : // Check for overflow.
412 0 : if (aBase64Len > UINT32_MAX / 3) {
413 0 : return NS_ERROR_FAILURE;
414 : }
415 :
416 : // Don't ask PR_Base64Decode to decode the empty string.
417 0 : if (aBase64Len == 0) {
418 0 : *aBinary = (char*)moz_xmalloc(1);
419 0 : (*aBinary)[0] = '\0';
420 0 : *aBinaryLen = 0;
421 0 : return NS_OK;
422 : }
423 :
424 0 : *aBinary = nullptr;
425 0 : *aBinaryLen = (aBase64Len * 3) / 4;
426 :
427 : // Add one byte for null termination.
428 0 : UniqueFreePtr<char[]> binary((char*)malloc(*aBinaryLen + 1));
429 0 : if (!binary) {
430 0 : return NS_ERROR_OUT_OF_MEMORY;
431 : }
432 :
433 : nsresult rv =
434 0 : Base64DecodeHelper(aBase64, aBase64Len, binary.get(), aBinaryLen);
435 0 : if (NS_FAILED(rv)) {
436 0 : return rv;
437 : }
438 :
439 0 : *aBinary = binary.release();
440 0 : return NS_OK;
441 : }
442 :
443 : nsresult
444 172 : Base64Decode(const nsACString& aBase64, nsACString& aBinary)
445 : {
446 : // Check for overflow.
447 172 : if (aBase64.Length() > UINT32_MAX / 3) {
448 0 : return NS_ERROR_FAILURE;
449 : }
450 :
451 : // Don't ask PR_Base64Decode to decode the empty string
452 172 : if (aBase64.IsEmpty()) {
453 0 : aBinary.Truncate();
454 0 : return NS_OK;
455 : }
456 :
457 172 : uint32_t binaryLen = ((aBase64.Length() * 3) / 4);
458 :
459 : // Add one byte for null termination.
460 172 : if (!aBinary.SetCapacity(binaryLen + 1, fallible)) {
461 0 : return NS_ERROR_OUT_OF_MEMORY;
462 : }
463 :
464 172 : char* binary = aBinary.BeginWriting();
465 172 : nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(),
466 172 : binary, &binaryLen);
467 172 : if (NS_FAILED(rv)) {
468 0 : aBinary.Truncate();
469 0 : return rv;
470 : }
471 :
472 172 : aBinary.SetLength(binaryLen);
473 172 : return NS_OK;
474 : }
475 :
476 : nsresult
477 0 : Base64Decode(const nsAString& aBase64, nsAString& aBinary)
478 : {
479 0 : auto truncater = mozilla::MakeScopeExit([&]() { aBinary.Truncate(); });
480 :
481 : // XXX We should really consider decoding directly from the string, rather
482 : // than making a separate copy here.
483 0 : nsAutoCString base64;
484 0 : if (!base64.SetCapacity(aBase64.Length(), mozilla::fallible)) {
485 0 : return NS_ERROR_OUT_OF_MEMORY;
486 : }
487 0 : LossyCopyUTF16toASCII(aBase64, base64);
488 :
489 0 : nsAutoCString binary;
490 :
491 0 : nsresult rv = Base64Decode(base64, binary);
492 0 : NS_ENSURE_SUCCESS(rv, rv);
493 :
494 0 : if (!CopyASCIItoUTF16(binary, aBinary, mozilla::fallible)) {
495 0 : return NS_ERROR_OUT_OF_MEMORY;
496 : }
497 :
498 0 : truncater.release();
499 :
500 0 : return rv;
501 : }
502 :
503 : nsresult
504 0 : Base64URLDecode(const nsACString& aBase64,
505 : Base64URLDecodePaddingPolicy aPaddingPolicy,
506 : FallibleTArray<uint8_t>& aBinary)
507 : {
508 : // Don't decode empty strings.
509 0 : if (aBase64.IsEmpty()) {
510 0 : aBinary.Clear();
511 0 : return NS_OK;
512 : }
513 :
514 : // Check for overflow.
515 0 : uint32_t base64Len = aBase64.Length();
516 0 : if (base64Len > UINT32_MAX / 3) {
517 0 : return NS_ERROR_FAILURE;
518 : }
519 0 : const char* base64 = aBase64.BeginReading();
520 :
521 : // The decoded length may be 1-2 bytes over, depending on the final quantum.
522 0 : uint32_t binaryLen = (base64Len * 3) / 4;
523 :
524 : // Determine whether to check for and ignore trailing padding.
525 0 : bool maybePadded = false;
526 0 : switch (aPaddingPolicy) {
527 : case Base64URLDecodePaddingPolicy::Require:
528 0 : if (base64Len % 4) {
529 : // Padded input length must be a multiple of 4.
530 0 : return NS_ERROR_INVALID_ARG;
531 : }
532 0 : maybePadded = true;
533 0 : break;
534 :
535 : case Base64URLDecodePaddingPolicy::Ignore:
536 : // Check for padding only if the length is a multiple of 4.
537 0 : maybePadded = !(base64Len % 4);
538 0 : break;
539 :
540 : // If we're expecting unpadded input, no need for additional checks.
541 : // `=` isn't in the decode table, so padded strings will fail to decode.
542 : default:
543 0 : MOZ_FALLTHROUGH_ASSERT("Invalid decode padding policy");
544 : case Base64URLDecodePaddingPolicy::Reject:
545 0 : break;
546 : }
547 0 : if (maybePadded && base64[base64Len - 1] == '=') {
548 0 : if (base64[base64Len - 2] == '=') {
549 0 : base64Len -= 2;
550 : } else {
551 0 : base64Len -= 1;
552 : }
553 : }
554 :
555 0 : if (NS_WARN_IF(!aBinary.SetCapacity(binaryLen, mozilla::fallible))) {
556 0 : return NS_ERROR_OUT_OF_MEMORY;
557 : }
558 0 : aBinary.SetLengthAndRetainStorage(binaryLen);
559 0 : uint8_t* binary = aBinary.Elements();
560 :
561 0 : for (; base64Len >= 4; base64Len -= 4) {
562 : uint8_t w, x, y, z;
563 0 : if (!Base64URLCharToValue(*base64++, &w) ||
564 0 : !Base64URLCharToValue(*base64++, &x) ||
565 0 : !Base64URLCharToValue(*base64++, &y) ||
566 0 : !Base64URLCharToValue(*base64++, &z)) {
567 0 : return NS_ERROR_INVALID_ARG;
568 : }
569 0 : *binary++ = w << 2 | x >> 4;
570 0 : *binary++ = x << 4 | y >> 2;
571 0 : *binary++ = y << 6 | z;
572 : }
573 :
574 0 : if (base64Len == 3) {
575 : uint8_t w, x, y;
576 0 : if (!Base64URLCharToValue(*base64++, &w) ||
577 0 : !Base64URLCharToValue(*base64++, &x) ||
578 0 : !Base64URLCharToValue(*base64++, &y)) {
579 0 : return NS_ERROR_INVALID_ARG;
580 : }
581 0 : *binary++ = w << 2 | x >> 4;
582 0 : *binary++ = x << 4 | y >> 2;
583 0 : } else if (base64Len == 2) {
584 : uint8_t w, x;
585 0 : if (!Base64URLCharToValue(*base64++, &w) ||
586 0 : !Base64URLCharToValue(*base64++, &x)) {
587 0 : return NS_ERROR_INVALID_ARG;
588 : }
589 0 : *binary++ = w << 2 | x >> 4;
590 0 : } else if (base64Len) {
591 0 : return NS_ERROR_INVALID_ARG;
592 : }
593 :
594 : // Set the length to the actual number of decoded bytes.
595 0 : aBinary.TruncateLength(binary - aBinary.Elements());
596 0 : return NS_OK;
597 : }
598 :
599 : nsresult
600 1 : Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary,
601 : Base64URLEncodePaddingPolicy aPaddingPolicy,
602 : nsACString& aBase64)
603 : {
604 : // Don't encode empty strings.
605 1 : if (aBinaryLen == 0) {
606 0 : aBase64.Truncate();
607 0 : return NS_OK;
608 : }
609 :
610 : // Check for overflow.
611 1 : if (aBinaryLen > (UINT32_MAX / 4) * 3) {
612 0 : return NS_ERROR_FAILURE;
613 : }
614 :
615 : // Allocate a buffer large enough to hold the encoded string with padding.
616 : // Add one byte for null termination.
617 1 : uint32_t base64Len = ((aBinaryLen + 2) / 3) * 4;
618 1 : if (NS_WARN_IF(!aBase64.SetCapacity(base64Len + 1, fallible))) {
619 0 : aBase64.Truncate();
620 0 : return NS_ERROR_FAILURE;
621 : }
622 :
623 1 : char* base64 = aBase64.BeginWriting();
624 :
625 1 : uint32_t index = 0;
626 7 : for (; index + 3 <= aBinaryLen; index += 3) {
627 3 : *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
628 9 : *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) |
629 6 : (aBinary[index + 1] >> 4)];
630 9 : *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2) |
631 6 : (aBinary[index + 2] >> 6)];
632 3 : *base64++ = kBase64URLAlphabet[aBinary[index + 2] & 0x3f];
633 : }
634 :
635 1 : uint32_t remaining = aBinaryLen - index;
636 1 : if (remaining == 1) {
637 0 : *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
638 0 : *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4)];
639 1 : } else if (remaining == 2) {
640 0 : *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
641 0 : *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) |
642 0 : (aBinary[index + 1] >> 4)];
643 0 : *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2)];
644 : }
645 :
646 1 : uint32_t length = base64 - aBase64.BeginWriting();
647 1 : if (aPaddingPolicy == Base64URLEncodePaddingPolicy::Include) {
648 0 : if (length % 4 == 2) {
649 0 : *base64++ = '=';
650 0 : *base64++ = '=';
651 0 : length += 2;
652 0 : } else if (length % 4 == 3) {
653 0 : *base64++ = '=';
654 0 : length += 1;
655 : }
656 : } else {
657 1 : MOZ_ASSERT(aPaddingPolicy == Base64URLEncodePaddingPolicy::Omit,
658 : "Invalid encode padding policy");
659 : }
660 :
661 : // Null terminate and truncate to the actual number of characters.
662 1 : *base64 = '\0';
663 1 : aBase64.SetLength(length);
664 :
665 1 : return NS_OK;
666 : }
667 :
668 : } // namespace mozilla
|