Bug 1932613 - temporarily disable browser_ml_end_to_end.js for permanent failures...
[gecko.git] / xpcom / io / Base64.cpp
blobda4aeafb4a73f3f6e85121713bf8df2318077374
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/. */
7 #include "Base64.h"
9 #include "mozilla/ArrayUtils.h"
10 #include "mozilla/UniquePtrExtensions.h"
11 #include "nsIInputStream.h"
12 #include "nsString.h"
13 #include "nsTArray.h"
15 #include "plbase64.h"
17 namespace {
19 // BEGIN base64 encode code copied and modified from NSPR
20 const unsigned char* const base =
21 (unsigned char*)"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
22 "abcdefghijklmnopqrstuvwxyz"
23 "0123456789+/";
25 // The Base64 encoder assumes all characters are less than 256; for 16-bit
26 // strings, that means assuming that all characters are within range, and
27 // masking off high bits if necessary.
28 template <typename T>
29 uint8_t CharTo8Bit(T aChar) {
30 return uint8_t(aChar);
33 template <typename SrcT, typename DestT>
34 static void Encode3to4(const SrcT* aSrc, DestT* aDest) {
35 uint32_t b32 = (uint32_t)0;
36 int i, j = 18;
38 for (i = 0; i < 3; ++i) {
39 b32 <<= 8;
40 b32 |= CharTo8Bit(aSrc[i]);
43 for (i = 0; i < 4; ++i) {
44 aDest[i] = base[(uint32_t)((b32 >> j) & 0x3F)];
45 j -= 6;
49 template <typename SrcT, typename DestT>
50 static void Encode2to4(const SrcT* aSrc, DestT* aDest) {
51 uint8_t src0 = CharTo8Bit(aSrc[0]);
52 uint8_t src1 = CharTo8Bit(aSrc[1]);
53 aDest[0] = base[(uint32_t)((src0 >> 2) & 0x3F)];
54 aDest[1] = base[(uint32_t)(((src0 & 0x03) << 4) | ((src1 >> 4) & 0x0F))];
55 aDest[2] = base[(uint32_t)((src1 & 0x0F) << 2)];
56 aDest[3] = DestT('=');
59 template <typename SrcT, typename DestT>
60 static void Encode1to4(const SrcT* aSrc, DestT* aDest) {
61 uint8_t src0 = CharTo8Bit(aSrc[0]);
62 aDest[0] = base[(uint32_t)((src0 >> 2) & 0x3F)];
63 aDest[1] = base[(uint32_t)((src0 & 0x03) << 4)];
64 aDest[2] = DestT('=');
65 aDest[3] = DestT('=');
68 template <typename SrcT, typename DestT>
69 static void Encode(const SrcT* aSrc, uint32_t aSrcLen, DestT* aDest) {
70 while (aSrcLen >= 3) {
71 Encode3to4(aSrc, aDest);
72 aSrc += 3;
73 aDest += 4;
74 aSrcLen -= 3;
77 switch (aSrcLen) {
78 case 2:
79 Encode2to4(aSrc, aDest);
80 break;
81 case 1:
82 Encode1to4(aSrc, aDest);
83 break;
84 case 0:
85 break;
86 default:
87 MOZ_ASSERT_UNREACHABLE("coding error");
91 // END base64 encode code copied and modified from NSPR.
93 template <typename T>
94 struct EncodeInputStream_State {
95 unsigned char c[3];
96 uint8_t charsOnStack;
97 typename T::char_type* buffer;
100 template <typename T>
101 nsresult EncodeInputStream_Encoder(nsIInputStream* aStream, void* aClosure,
102 const char* aFromSegment, uint32_t aToOffset,
103 uint32_t aCount, uint32_t* aWriteCount) {
104 MOZ_ASSERT(aCount > 0, "Er, what?");
106 EncodeInputStream_State<T>* state =
107 static_cast<EncodeInputStream_State<T>*>(aClosure);
109 // We consume the whole data always.
110 *aWriteCount = aCount;
112 // If we have any data left from last time, encode it now.
113 uint32_t countRemaining = aCount;
114 const unsigned char* src = (const unsigned char*)aFromSegment;
115 if (state->charsOnStack) {
116 MOZ_ASSERT(state->charsOnStack == 1 || state->charsOnStack == 2);
118 // Not enough data to compose a triple.
119 if (state->charsOnStack == 1 && countRemaining == 1) {
120 state->charsOnStack = 2;
121 state->c[1] = src[0];
122 return NS_OK;
125 uint32_t consumed = 0;
126 unsigned char firstSet[4];
127 if (state->charsOnStack == 1) {
128 firstSet[0] = state->c[0];
129 firstSet[1] = src[0];
130 firstSet[2] = src[1];
131 firstSet[3] = '\0';
132 consumed = 2;
133 } else /* state->charsOnStack == 2 */ {
134 firstSet[0] = state->c[0];
135 firstSet[1] = state->c[1];
136 firstSet[2] = src[0];
137 firstSet[3] = '\0';
138 consumed = 1;
141 Encode(firstSet, 3, state->buffer);
142 state->buffer += 4;
143 countRemaining -= consumed;
144 src += consumed;
145 state->charsOnStack = 0;
147 // Nothing is left.
148 if (!countRemaining) {
149 return NS_OK;
153 // Encode as many full triplets as possible.
154 uint32_t encodeLength = countRemaining - countRemaining % 3;
155 MOZ_ASSERT(encodeLength % 3 == 0, "Should have an exact number of triplets!");
156 Encode(src, encodeLength, state->buffer);
157 state->buffer += (encodeLength / 3) * 4;
158 src += encodeLength;
159 countRemaining -= encodeLength;
161 if (countRemaining) {
162 // We should never have a full triplet left at this point.
163 MOZ_ASSERT(countRemaining < 3, "We should have encoded more!");
164 state->c[0] = src[0];
165 state->c[1] = (countRemaining == 2) ? src[1] : '\0';
166 state->charsOnStack = countRemaining;
169 return NS_OK;
172 mozilla::Result<uint32_t, nsresult> CalculateBase64EncodedLength(
173 const size_t aBinaryLen, const uint32_t aPrefixLen = 0) {
174 mozilla::CheckedUint32 res = aBinaryLen;
175 // base 64 encoded length is 4/3rds the length of the input data, rounded up
176 res += 2;
177 res /= 3;
178 res *= 4;
179 res += aPrefixLen;
180 if (!res.isValid()) {
181 return mozilla::Err(NS_ERROR_FAILURE);
183 return res.value();
186 template <typename T>
187 nsresult EncodeInputStream(nsIInputStream* aInputStream, T& aDest,
188 uint32_t aCount, uint32_t aOffset) {
189 nsresult rv;
190 uint64_t count64 = aCount;
192 if (!aCount) {
193 rv = aInputStream->Available(&count64);
194 if (NS_WARN_IF(NS_FAILED(rv))) {
195 return rv;
197 // if count64 is over 4GB, it will be failed at the below condition,
198 // then will return NS_ERROR_OUT_OF_MEMORY
199 aCount = (uint32_t)count64;
202 const auto base64LenOrErr = CalculateBase64EncodedLength(count64, aOffset);
203 if (base64LenOrErr.isErr()) {
204 // XXX For some reason, it was NS_ERROR_OUT_OF_MEMORY here instead of
205 // NS_ERROR_FAILURE, so we keep that.
206 return NS_ERROR_OUT_OF_MEMORY;
209 auto handleOrErr = aDest.BulkWrite(base64LenOrErr.inspect(), aOffset, false);
210 if (handleOrErr.isErr()) {
211 return handleOrErr.unwrapErr();
214 auto handle = handleOrErr.unwrap();
216 EncodeInputStream_State<T> state{
217 .c = {'\0', '\0', '\0'},
218 .charsOnStack = 0,
219 .buffer = handle.Elements() + aOffset,
222 while (aCount > 0) {
223 uint32_t read = 0;
225 rv = aInputStream->ReadSegments(&EncodeInputStream_Encoder<T>,
226 (void*)&state, aCount, &read);
227 if (NS_FAILED(rv)) {
228 if (rv == NS_BASE_STREAM_WOULD_BLOCK) {
229 MOZ_CRASH("Not implemented for async streams!");
231 if (rv == NS_ERROR_NOT_IMPLEMENTED) {
232 MOZ_CRASH("Requires a stream that implements ReadSegments!");
234 return rv;
237 if (!read) {
238 break;
241 aCount -= read;
244 // Finish encoding if anything is left
245 if (state.charsOnStack) {
246 Encode(state.c, state.charsOnStack, state.buffer);
247 state.buffer += 4;
250 // If we encountered EOF before reading aCount bytes, the resulting string
251 // could be shorter than predicted, so determine the length from the state.
252 size_t trueLength = state.buffer - handle.Elements();
253 handle.Finish(trueLength, false);
255 return NS_OK;
258 // Maps an encoded character to a value in the Base64 alphabet, per
259 // RFC 4648, Table 1. Invalid input characters map to UINT8_MAX.
261 static const uint8_t kBase64DecodeTable[] = {
262 // clang-format off
263 /* 0 */ 255, 255, 255, 255, 255, 255, 255, 255,
264 /* 8 */ 255, 255, 255, 255, 255, 255, 255, 255,
265 /* 16 */ 255, 255, 255, 255, 255, 255, 255, 255,
266 /* 24 */ 255, 255, 255, 255, 255, 255, 255, 255,
267 /* 32 */ 255, 255, 255, 255, 255, 255, 255, 255,
268 /* 40 */ 255, 255, 255,
269 62 /* + */,
270 255, 255, 255,
271 63 /* / */,
273 /* 48 */ /* 0 - 9 */ 52, 53, 54, 55, 56, 57, 58, 59,
274 /* 56 */ 60, 61, 255, 255, 255, 255, 255, 255,
276 /* 64 */ 255, /* A - Z */ 0, 1, 2, 3, 4, 5, 6,
277 /* 72 */ 7, 8, 9, 10, 11, 12, 13, 14,
278 /* 80 */ 15, 16, 17, 18, 19, 20, 21, 22,
279 /* 88 */ 23, 24, 25, 255, 255, 255, 255, 255,
280 /* 96 */ 255, /* a - z */ 26, 27, 28, 29, 30, 31, 32,
281 /* 104 */ 33, 34, 35, 36, 37, 38, 39, 40,
282 /* 112 */ 41, 42, 43, 44, 45, 46, 47, 48,
283 /* 120 */ 49, 50, 51, 255, 255, 255, 255, 255,
285 static_assert(std::size(kBase64DecodeTable) == 0x80);
286 // clang-format on
288 template <typename T>
289 [[nodiscard]] bool Base64CharToValue(T aChar, uint8_t* aValue) {
290 size_t index = static_cast<uint8_t>(aChar);
291 if (index >= std::size(kBase64DecodeTable)) {
292 *aValue = 255;
293 return false;
295 *aValue = kBase64DecodeTable[index];
296 return *aValue != 255;
299 static const char kBase64URLAlphabet[] =
300 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
301 static_assert(std::size(kBase64URLAlphabet) == 0x41);
303 // Maps an encoded character to a value in the Base64 URL alphabet, per
304 // RFC 4648, Table 2. Invalid input characters map to UINT8_MAX.
305 static const uint8_t kBase64URLDecodeTable[] = {
306 // clang-format off
307 255, 255, 255, 255, 255, 255, 255, 255,
308 255, 255, 255, 255, 255, 255, 255, 255,
309 255, 255, 255, 255, 255, 255, 255, 255,
310 255, 255, 255, 255, 255, 255, 255, 255,
311 255, 255, 255, 255, 255, 255, 255, 255,
312 255, 255, 255, 255, 255,
313 62 /* - */,
314 255, 255,
315 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, /* 0 - 9 */
316 255, 255, 255, 255, 255, 255, 255,
317 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
318 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, /* A - Z */
319 255, 255, 255, 255,
320 63 /* _ */,
321 255,
322 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,
323 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, /* a - z */
324 255, 255, 255, 255, 255,
326 static_assert(std::size(kBase64URLDecodeTable) == 0x80);
327 // clang-format on
329 bool Base64URLCharToValue(char aChar, uint8_t* aValue) {
330 uint8_t index = static_cast<uint8_t>(aChar);
331 if (index >= std::size(kBase64URLDecodeTable)) {
332 *aValue = 255;
333 return false;
335 *aValue = kBase64URLDecodeTable[index];
336 return *aValue != 255;
339 } // namespace
341 namespace mozilla {
343 nsresult Base64EncodeInputStream(nsIInputStream* aInputStream,
344 nsACString& aDest, uint32_t aCount,
345 uint32_t aOffset) {
346 return EncodeInputStream<nsACString>(aInputStream, aDest, aCount, aOffset);
349 nsresult Base64EncodeInputStream(nsIInputStream* aInputStream, nsAString& aDest,
350 uint32_t aCount, uint32_t aOffset) {
351 return EncodeInputStream<nsAString>(aInputStream, aDest, aCount, aOffset);
354 nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen,
355 char** aBase64) {
356 if (aBinaryLen == 0) {
357 *aBase64 = (char*)moz_xmalloc(1);
358 (*aBase64)[0] = '\0';
359 return NS_OK;
362 const auto base64LenOrErr = CalculateBase64EncodedLength(aBinaryLen);
363 if (base64LenOrErr.isErr()) {
364 return base64LenOrErr.inspectErr();
366 const uint32_t base64Len = base64LenOrErr.inspect();
368 *aBase64 = nullptr;
370 // Add one byte for null termination.
371 UniqueFreePtr<char[]> base64((char*)malloc(base64Len + 1));
372 if (!base64) {
373 return NS_ERROR_OUT_OF_MEMORY;
376 Encode(aBinary, aBinaryLen, base64.get());
377 base64[base64Len] = '\0';
379 *aBase64 = base64.release();
380 return NS_OK;
383 template <bool Append = false, typename T, typename U>
384 static nsresult Base64EncodeHelper(const T* const aBinary,
385 const size_t aBinaryLen, U& aBase64) {
386 if (aBinaryLen == 0) {
387 if (!Append) {
388 aBase64.Truncate();
390 return NS_OK;
393 const uint32_t prefixLen = Append ? aBase64.Length() : 0;
394 const auto base64LenOrErr =
395 CalculateBase64EncodedLength(aBinaryLen, prefixLen);
396 if (base64LenOrErr.isErr()) {
397 return base64LenOrErr.inspectErr();
399 const uint32_t base64Len = base64LenOrErr.inspect();
401 auto handleOrErr = aBase64.BulkWrite(base64Len, prefixLen, false);
402 if (handleOrErr.isErr()) {
403 return handleOrErr.unwrapErr();
406 auto handle = handleOrErr.unwrap();
408 Encode(aBinary, aBinaryLen, handle.Elements() + prefixLen);
409 handle.Finish(base64Len, false);
410 return NS_OK;
413 nsresult Base64EncodeAppend(const char* aBinary, uint32_t aBinaryLen,
414 nsAString& aBase64) {
415 return Base64EncodeHelper<true>(aBinary, aBinaryLen, aBase64);
418 nsresult Base64EncodeAppend(const char* aBinary, uint32_t aBinaryLen,
419 nsACString& aBase64) {
420 return Base64EncodeHelper<true>(aBinary, aBinaryLen, aBase64);
423 nsresult Base64EncodeAppend(const nsACString& aBinary, nsACString& aBase64) {
424 return Base64EncodeHelper<true>(aBinary.BeginReading(), aBinary.Length(),
425 aBase64);
428 nsresult Base64EncodeAppend(const nsACString& aBinary, nsAString& aBase64) {
429 return Base64EncodeHelper<true>(aBinary.BeginReading(), aBinary.Length(),
430 aBase64);
433 nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen,
434 nsACString& aBase64) {
435 return Base64EncodeHelper(aBinary, aBinaryLen, aBase64);
438 nsresult Base64Encode(const char* aBinary, uint32_t aBinaryLen,
439 nsAString& aBase64) {
440 return Base64EncodeHelper(aBinary, aBinaryLen, aBase64);
443 nsresult Base64Encode(const nsACString& aBinary, nsACString& aBase64) {
444 return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64);
447 nsresult Base64Encode(const nsACString& aBinary, nsAString& aBase64) {
448 return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64);
451 nsresult Base64Encode(const nsAString& aBinary, nsAString& aBase64) {
452 return Base64EncodeHelper(aBinary.BeginReading(), aBinary.Length(), aBase64);
455 template <typename T, typename U, typename Decoder>
456 static bool Decode4to3(const T* aSrc, U* aDest, Decoder aToVal) {
457 uint8_t w, x, y, z;
458 if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x) || !aToVal(aSrc[2], &y) ||
459 !aToVal(aSrc[3], &z)) {
460 return false;
462 aDest[0] = U(uint8_t(w << 2 | x >> 4));
463 aDest[1] = U(uint8_t(x << 4 | y >> 2));
464 aDest[2] = U(uint8_t(y << 6 | z));
465 return true;
468 template <typename T, typename U, typename Decoder>
469 static bool Decode3to2(const T* aSrc, U* aDest, Decoder aToVal) {
470 uint8_t w, x, y;
471 if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x) || !aToVal(aSrc[2], &y)) {
472 return false;
474 aDest[0] = U(uint8_t(w << 2 | x >> 4));
475 aDest[1] = U(uint8_t(x << 4 | y >> 2));
476 return true;
479 template <typename T, typename U, typename Decoder>
480 static bool Decode2to1(const T* aSrc, U* aDest, Decoder aToVal) {
481 uint8_t w, x;
482 if (!aToVal(aSrc[0], &w) || !aToVal(aSrc[1], &x)) {
483 return false;
485 aDest[0] = U(uint8_t(w << 2 | x >> 4));
486 return true;
489 template <typename SrcT, typename DestT>
490 static nsresult Base64DecodeHelper(const SrcT* aBase64, uint32_t aBase64Len,
491 DestT* aBinary, uint32_t* aBinaryLen) {
492 MOZ_ASSERT(aBinary);
494 const SrcT* input = aBase64;
495 uint32_t inputLength = aBase64Len;
496 DestT* binary = aBinary;
497 uint32_t binaryLength = 0;
499 // Handle trailing '=' characters.
500 if (inputLength && (inputLength % 4 == 0)) {
501 if (aBase64[inputLength - 1] == SrcT('=')) {
502 if (aBase64[inputLength - 2] == SrcT('=')) {
503 inputLength -= 2;
504 } else {
505 inputLength -= 1;
510 while (inputLength >= 4) {
511 if (!Decode4to3(input, binary, Base64CharToValue<SrcT>)) {
512 return NS_ERROR_INVALID_ARG;
515 input += 4;
516 inputLength -= 4;
517 binary += 3;
518 binaryLength += 3;
521 switch (inputLength) {
522 case 3:
523 if (!Decode3to2(input, binary, Base64CharToValue<SrcT>)) {
524 return NS_ERROR_INVALID_ARG;
526 binaryLength += 2;
527 break;
528 case 2:
529 if (!Decode2to1(input, binary, Base64CharToValue<SrcT>)) {
530 return NS_ERROR_INVALID_ARG;
532 binaryLength += 1;
533 break;
534 case 1:
535 return NS_ERROR_INVALID_ARG;
536 case 0:
537 break;
538 default:
539 MOZ_CRASH("Too many characters leftover");
542 aBinary[binaryLength] = DestT('\0');
543 *aBinaryLen = binaryLength;
545 return NS_OK;
548 nsresult Base64Decode(const char* aBase64, uint32_t aBase64Len, char** aBinary,
549 uint32_t* aBinaryLen) {
550 // Check for overflow.
551 if (aBase64Len > UINT32_MAX / 3) {
552 return NS_ERROR_FAILURE;
555 // Don't ask PR_Base64Decode to decode the empty string.
556 if (aBase64Len == 0) {
557 *aBinary = (char*)moz_xmalloc(1);
558 (*aBinary)[0] = '\0';
559 *aBinaryLen = 0;
560 return NS_OK;
563 *aBinary = nullptr;
564 *aBinaryLen = (aBase64Len * 3) / 4;
566 // Add one byte for null termination.
567 UniqueFreePtr<char[]> binary((char*)malloc(*aBinaryLen + 1));
568 if (!binary) {
569 return NS_ERROR_OUT_OF_MEMORY;
572 nsresult rv =
573 Base64DecodeHelper(aBase64, aBase64Len, binary.get(), aBinaryLen);
574 if (NS_FAILED(rv)) {
575 return rv;
578 *aBinary = binary.release();
579 return NS_OK;
582 template <typename T, typename U>
583 static nsresult Base64DecodeString(const T& aBase64, U& aBinary) {
584 aBinary.Truncate();
586 // Check for overflow.
587 if (aBase64.Length() > UINT32_MAX / 3) {
588 return NS_ERROR_FAILURE;
591 // Don't decode the empty string
592 if (aBase64.IsEmpty()) {
593 return NS_OK;
596 uint32_t binaryLen = ((aBase64.Length() * 3) / 4);
598 auto handleOrErr = aBinary.BulkWrite(binaryLen, 0, false);
599 if (handleOrErr.isErr()) {
600 // Must not touch the handle if failing here, but we
601 // already truncated the string at the top, so it's
602 // unchanged.
603 return handleOrErr.unwrapErr();
606 auto handle = handleOrErr.unwrap();
608 nsresult rv = Base64DecodeHelper(aBase64.BeginReading(), aBase64.Length(),
609 handle.Elements(), &binaryLen);
610 if (NS_FAILED(rv)) {
611 // Retruncate to match old semantics of this method.
612 handle.Finish(0, true);
613 return rv;
616 handle.Finish(binaryLen, true);
617 return NS_OK;
620 nsresult Base64Decode(const nsACString& aBase64, nsACString& aBinary) {
621 return Base64DecodeString(aBase64, aBinary);
624 nsresult Base64Decode(const nsAString& aBase64, nsAString& aBinary) {
625 return Base64DecodeString(aBase64, aBinary);
628 nsresult Base64Decode(const nsAString& aBase64, nsACString& aBinary) {
629 return Base64DecodeString(aBase64, aBinary);
632 nsresult Base64URLDecode(const nsACString& aBase64,
633 Base64URLDecodePaddingPolicy aPaddingPolicy,
634 FallibleTArray<uint8_t>& aBinary) {
635 // Don't decode empty strings.
636 if (aBase64.IsEmpty()) {
637 aBinary.Clear();
638 return NS_OK;
641 // Check for overflow.
642 uint32_t base64Len = aBase64.Length();
643 if (base64Len > UINT32_MAX / 3) {
644 return NS_ERROR_FAILURE;
646 const char* base64 = aBase64.BeginReading();
648 // The decoded length may be 1-2 bytes over, depending on the final quantum.
649 uint32_t binaryLen = (base64Len * 3) / 4;
651 // Determine whether to check for and ignore trailing padding.
652 bool maybePadded = false;
653 switch (aPaddingPolicy) {
654 case Base64URLDecodePaddingPolicy::Require:
655 if (base64Len % 4) {
656 // Padded input length must be a multiple of 4.
657 return NS_ERROR_INVALID_ARG;
659 maybePadded = true;
660 break;
662 case Base64URLDecodePaddingPolicy::Ignore:
663 // Check for padding only if the length is a multiple of 4.
664 maybePadded = !(base64Len % 4);
665 break;
667 // If we're expecting unpadded input, no need for additional checks.
668 // `=` isn't in the decode table, so padded strings will fail to decode.
669 default:
670 MOZ_FALLTHROUGH_ASSERT("Invalid decode padding policy");
671 case Base64URLDecodePaddingPolicy::Reject:
672 break;
674 if (maybePadded && base64[base64Len - 1] == '=') {
675 if (base64[base64Len - 2] == '=') {
676 base64Len -= 2;
677 } else {
678 base64Len -= 1;
682 if (NS_WARN_IF(!aBinary.SetCapacity(binaryLen, mozilla::fallible))) {
683 return NS_ERROR_OUT_OF_MEMORY;
685 aBinary.SetLengthAndRetainStorage(binaryLen);
686 uint8_t* binary = aBinary.Elements();
688 for (; base64Len >= 4; base64Len -= 4) {
689 if (!Decode4to3(base64, binary, Base64URLCharToValue)) {
690 return NS_ERROR_INVALID_ARG;
692 base64 += 4;
693 binary += 3;
696 if (base64Len == 3) {
697 if (!Decode3to2(base64, binary, Base64URLCharToValue)) {
698 return NS_ERROR_INVALID_ARG;
700 binary += 2;
701 } else if (base64Len == 2) {
702 if (!Decode2to1(base64, binary, Base64URLCharToValue)) {
703 return NS_ERROR_INVALID_ARG;
705 binary += 1;
706 } else if (base64Len) {
707 return NS_ERROR_INVALID_ARG;
710 // Set the length to the actual number of decoded bytes.
711 aBinary.TruncateLength(binary - aBinary.Elements());
712 return NS_OK;
715 nsresult Base64URLEncode(uint32_t aBinaryLen, const uint8_t* aBinary,
716 Base64URLEncodePaddingPolicy aPaddingPolicy,
717 nsACString& aBase64) {
718 aBase64.Truncate();
719 // Don't encode empty strings.
720 if (aBinaryLen == 0) {
721 return NS_OK;
724 // Allocate a buffer large enough to hold the encoded string with padding.
725 const auto base64LenOrErr = CalculateBase64EncodedLength(aBinaryLen);
726 if (base64LenOrErr.isErr()) {
727 return base64LenOrErr.inspectErr();
729 const uint32_t base64Len = base64LenOrErr.inspect();
731 auto handleOrErr = aBase64.BulkWrite(base64Len, 0, false);
732 if (handleOrErr.isErr()) {
733 return handleOrErr.unwrapErr();
736 auto handle = handleOrErr.unwrap();
738 char* base64 = handle.Elements();
740 uint32_t index = 0;
741 for (; index + 3 <= aBinaryLen; index += 3) {
742 *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
743 *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) |
744 (aBinary[index + 1] >> 4)];
745 *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2) |
746 (aBinary[index + 2] >> 6)];
747 *base64++ = kBase64URLAlphabet[aBinary[index + 2] & 0x3f];
750 uint32_t remaining = aBinaryLen - index;
751 if (remaining == 1) {
752 *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
753 *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4)];
754 } else if (remaining == 2) {
755 *base64++ = kBase64URLAlphabet[aBinary[index] >> 2];
756 *base64++ = kBase64URLAlphabet[((aBinary[index] & 0x3) << 4) |
757 (aBinary[index + 1] >> 4)];
758 *base64++ = kBase64URLAlphabet[((aBinary[index + 1] & 0xf) << 2)];
761 uint32_t length = base64 - handle.Elements();
762 if (aPaddingPolicy == Base64URLEncodePaddingPolicy::Include) {
763 if (length % 4 == 2) {
764 *base64++ = '=';
765 *base64++ = '=';
766 length += 2;
767 } else if (length % 4 == 3) {
768 *base64++ = '=';
769 length += 1;
771 } else {
772 MOZ_ASSERT(aPaddingPolicy == Base64URLEncodePaddingPolicy::Omit,
773 "Invalid encode padding policy");
776 handle.Finish(length, false);
777 return NS_OK;
780 } // namespace mozilla