Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
license.bot | bf09a50 | 2008-08-24 00:55:55 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 4 | |
[email protected] | 978df34 | 2009-11-24 06:21:53 | [diff] [blame] | 5 | #include "base/base64.h" |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 6 | |
avi | 9b6f4293 | 2015-12-26 22:15:14 | [diff] [blame] | 7 | #include <stddef.h> |
| 8 | |
Helmut Januschka | 0fc785b | 2024-04-17 21:13:36 | [diff] [blame] | 9 | #include <string_view> |
| 10 | |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 11 | #include "base/check.h" |
| 12 | #include "base/numerics/checked_math.h" |
Charlie Harrison | 7b633d9 | 2022-11-29 05:23:50 | [diff] [blame] | 13 | #include "base/strings/string_util.h" |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 14 | #include "third_party/modp_b64/modp_b64.h" |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 15 | |
[email protected] | 978df34 | 2009-11-24 06:21:53 | [diff] [blame] | 16 | namespace base { |
[email protected] | a9bb6f69 | 2008-07-30 16:40:10 | [diff] [blame] | 17 | |
Charlie Harrison | 7b633d9 | 2022-11-29 05:23:50 | [diff] [blame] | 18 | namespace { |
| 19 | |
| 20 | ModpDecodePolicy GetModpPolicy(Base64DecodePolicy policy) { |
| 21 | switch (policy) { |
| 22 | case Base64DecodePolicy::kStrict: |
| 23 | return ModpDecodePolicy::kStrict; |
| 24 | case Base64DecodePolicy::kForgiving: |
| 25 | return ModpDecodePolicy::kForgiving; |
| 26 | } |
| 27 | } |
| 28 | |
| 29 | } // namespace |
| 30 | |
Collin Baker | e21f723d | 2019-09-05 20:05:41 | [diff] [blame] | 31 | std::string Base64Encode(span<const uint8_t> input) { |
| 32 | std::string output; |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 33 | Base64EncodeAppend(input, &output); |
Collin Baker | e21f723d | 2019-09-05 20:05:41 | [diff] [blame] | 34 | return output; |
| 35 | } |
| 36 | |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 37 | void Base64EncodeAppend(span<const uint8_t> input, std::string* output) { |
Charlie Harrison | 39d53322 | 2022-11-22 23:49:39 | [diff] [blame] | 38 | // Ensure `modp_b64_encode_data_len` will not overflow. |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 39 | CHECK_LE(input.size(), MODP_B64_MAX_INPUT_LEN); |
Charlie Harrison | 39d53322 | 2022-11-22 23:49:39 | [diff] [blame] | 40 | size_t encode_data_len = modp_b64_encode_data_len(input.size()); |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 41 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 42 | const size_t after_size = |
| 43 | base::CheckAdd(encode_data_len, output->size()).ValueOrDie(); |
| 44 | output->resize(after_size); |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 45 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 46 | span<const char> read = base::as_chars(input); |
| 47 | span<char> write = base::span(*output).last(encode_data_len); |
| 48 | |
| 49 | const size_t written_size = modp_b64_encode_data( |
| 50 | write.data(), // This must point to `encode_data_len` many chars. |
| 51 | read.data(), read.size()); |
| 52 | // If this failed it would indicate we wrote OOB or left bytes uninitialized. |
| 53 | // It's possible for this to be elided by the compiler, since writing OOB is |
| 54 | // UB. |
| 55 | CHECK_EQ(written_size, write.size()); |
David Benjamin | 48808e2 | 2022-09-21 19:39:12 | [diff] [blame] | 56 | } |
| 57 | |
Helmut Januschka | 0fc785b | 2024-04-17 21:13:36 | [diff] [blame] | 58 | std::string Base64Encode(std::string_view input) { |
Tom Sepez | d325215 | 2023-11-15 21:38:45 | [diff] [blame] | 59 | return Base64Encode(base::as_byte_span(input)); |
wd l | 4a70b3ff | 2023-09-26 20:13:35 | [diff] [blame] | 60 | } |
| 61 | |
Helmut Januschka | 0fc785b | 2024-04-17 21:13:36 | [diff] [blame] | 62 | bool Base64Decode(std::string_view input, |
Charlie Harrison | 7b633d9 | 2022-11-29 05:23:50 | [diff] [blame] | 63 | std::string* output, |
| 64 | Base64DecodePolicy policy) { |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 65 | std::string decode_buf; |
| 66 | decode_buf.resize(modp_b64_decode_len(input.size())); |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 67 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 68 | // Does not NUL-terminate result since result is binary data! |
| 69 | size_t written_size = modp_b64_decode(decode_buf.data(), input.data(), |
| 70 | input.size(), GetModpPolicy(policy)); |
Charlie Harrison | 7b633d9 | 2022-11-29 05:23:50 | [diff] [blame] | 71 | |
| 72 | // Forgiving mode requires whitespace to be stripped prior to decoding. |
| 73 | // We don't do that in the above code to ensure that the "happy path" of |
| 74 | // input without whitespace is as fast as possible. Since whitespace in input |
| 75 | // will always cause `modp_b64_decode` to fail, just handle whitespace |
| 76 | // stripping on failure. This is not much slower than just scanning for |
| 77 | // whitespace first, even for input with whitespace. |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 78 | if (written_size == MODP_B64_ERROR && |
Charlie Harrison | 7b633d9 | 2022-11-29 05:23:50 | [diff] [blame] | 79 | policy == Base64DecodePolicy::kForgiving) { |
| 80 | // We could use `output` here to avoid an allocation when decoding is done |
| 81 | // in-place, but it violates the API contract that `output` is only modified |
| 82 | // on success. |
| 83 | std::string input_without_whitespace; |
Lei Zhang | a988705 | 2025-02-19 20:26:41 | [diff] [blame] | 84 | RemoveChars(input, |
| 85 | std::string_view(std::begin(kInfraAsciiWhitespace), |
| 86 | std::end(kInfraAsciiWhitespace)), |
| 87 | &input_without_whitespace); |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 88 | // This means that the required size to decode is at most what was needed |
| 89 | // above, which means `decode_buf` will fit the decoded bytes at its current |
| 90 | // size and we don't need to call `modp_b64_decode_len()` again. |
| 91 | CHECK_LE(input_without_whitespace.size(), input.size()); |
| 92 | written_size = |
| 93 | modp_b64_decode(decode_buf.data(), input_without_whitespace.data(), |
Charlie Harrison | 7b633d9 | 2022-11-29 05:23:50 | [diff] [blame] | 94 | input_without_whitespace.size(), GetModpPolicy(policy)); |
| 95 | } |
| 96 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 97 | if (written_size == MODP_B64_ERROR) { |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 98 | return false; |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 99 | } |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 100 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 101 | // If this failed it would indicate we wrote OOB. It's possible for this to be |
| 102 | // elided by the compiler, since writing OOB is UB. |
| 103 | CHECK_LE(written_size, decode_buf.size()); |
| 104 | |
| 105 | // Shrinks the buffer and makes it NUL-terminated. |
| 106 | decode_buf.resize(written_size); |
| 107 | *output = std::move(decode_buf); |
initial.commit | 586acc5fe | 2008-07-26 22:42:52 | [diff] [blame] | 108 | return true; |
| 109 | } |
[email protected] | a9bb6f69 | 2008-07-30 16:40:10 | [diff] [blame] | 110 | |
Helmut Januschka | 0fc785b | 2024-04-17 21:13:36 | [diff] [blame] | 111 | std::optional<std::vector<uint8_t>> Base64Decode(std::string_view input) { |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 112 | std::vector<uint8_t> write_buf(modp_b64_decode_len(input.size())); |
| 113 | span<char> write = base::as_writable_chars(base::span(write_buf)); |
David Benjamin | 47bb5ec | 2022-02-01 23:12:28 | [diff] [blame] | 114 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 115 | size_t written_size = |
| 116 | modp_b64_decode(write.data(), input.data(), input.size()); |
| 117 | if (written_size == MODP_B64_ERROR) { |
Arthur Sonzogni | e5fff99c | 2024-02-21 15:58:24 | [diff] [blame] | 118 | return std::nullopt; |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 119 | } |
David Benjamin | 47bb5ec | 2022-02-01 23:12:28 | [diff] [blame] | 120 | |
danakj | e891607 | 2024-07-23 21:08:11 | [diff] [blame] | 121 | // If this failed it would indicate we wrote OOB. It's possible for this to be |
| 122 | // elided by the compiler, since writing OOB is UB. |
| 123 | CHECK_LE(written_size, write.size()); |
| 124 | |
| 125 | write_buf.resize(written_size); |
| 126 | return write_buf; |
David Benjamin | 47bb5ec | 2022-02-01 23:12:28 | [diff] [blame] | 127 | } |
| 128 | |
[email protected] | 978df34 | 2009-11-24 06:21:53 | [diff] [blame] | 129 | } // namespace base |