1 // Copyright 2015 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "base/base64url.h"
6
7 #include <stddef.h>
8
9 #include <string_view>
10
11 #include "base/base64.h"
12 #include "base/numerics/safe_math.h"
13 #include "base/strings/string_util.h"
14 #include "third_party/modp_b64/modp_b64.h"
15
16 namespace base {
17
18 namespace {
19
20 const char kPaddingChar = '=';
21
22 // Base64url maps {+, /} to {-, _} in order for the encoded content to be safe
23 // to use in a URL. These characters will be translated by this implementation.
24 const char kBase64Chars[] = "+/";
25 const char kBase64UrlSafeChars[] = "-_";
26
27 class StringViewOrString {
28 public:
StringViewOrString(std::string_view piece)29 explicit StringViewOrString(std::string_view piece) : piece_(piece) {}
StringViewOrString(std::string str)30 explicit StringViewOrString(std::string str) : str_(std::move(str)) {}
31
get() const32 std::string_view get() const {
33 if (str_) {
34 return *str_;
35 }
36 return piece_;
37 }
38
39 private:
40 const std::optional<std::string> str_;
41 const std::string_view piece_;
42 };
43
44 // Converts the base64url `input` into a plain base64 string.
Base64UrlToBase64(std::string_view input,Base64UrlDecodePolicy policy)45 std::optional<StringViewOrString> Base64UrlToBase64(
46 std::string_view input,
47 Base64UrlDecodePolicy policy) {
48 // Characters outside of the base64url alphabet are disallowed, which includes
49 // the {+, /} characters found in the conventional base64 alphabet.
50 if (input.find_first_of(kBase64Chars) != std::string::npos)
51 return std::nullopt;
52
53 const size_t required_padding_characters = input.size() % 4;
54 const bool needs_replacement =
55 input.find_first_of(kBase64UrlSafeChars) != std::string::npos;
56
57 switch (policy) {
58 case Base64UrlDecodePolicy::REQUIRE_PADDING:
59 // Fail if the required padding is not included in |input|.
60 if (required_padding_characters > 0)
61 return std::nullopt;
62 break;
63 case Base64UrlDecodePolicy::IGNORE_PADDING:
64 // Missing padding will be silently appended.
65 break;
66 case Base64UrlDecodePolicy::DISALLOW_PADDING:
67 // Fail if padding characters are included in |input|.
68 if (input.find_first_of(kPaddingChar) != std::string::npos)
69 return std::nullopt;
70 break;
71 }
72
73 if (required_padding_characters == 0 && !needs_replacement) {
74 return StringViewOrString(input);
75 }
76
77 // If the string either needs replacement of URL-safe characters to normal
78 // base64 ones, or additional padding, a copy of |input| needs to be made in
79 // order to make these adjustments without side effects.
80 CheckedNumeric<size_t> out_size = input.size();
81 if (required_padding_characters > 0) {
82 out_size += 4 - required_padding_characters;
83 }
84
85 std::string base64_input;
86 base64_input.reserve(out_size.ValueOrDie());
87 base64_input.append(input);
88
89 // Substitute the base64url URL-safe characters to their base64 equivalents.
90 ReplaceChars(base64_input, "-", "+", &base64_input);
91 ReplaceChars(base64_input, "_", "/", &base64_input);
92
93 // Append the necessary padding characters.
94 base64_input.resize(out_size.ValueOrDie(), '=');
95
96 return StringViewOrString(std::move(base64_input));
97 }
98
99 } // namespace
100
Base64UrlEncode(span<const uint8_t> input,Base64UrlEncodePolicy policy,std::string * output)101 void Base64UrlEncode(span<const uint8_t> input,
102 Base64UrlEncodePolicy policy,
103 std::string* output) {
104 *output = Base64Encode(input);
105
106 ReplaceChars(*output, "+", "-", output);
107 ReplaceChars(*output, "/", "_", output);
108
109 switch (policy) {
110 case Base64UrlEncodePolicy::INCLUDE_PADDING:
111 // The padding included in |*output| will not be amended.
112 break;
113 case Base64UrlEncodePolicy::OMIT_PADDING:
114 // The padding included in |*output| will be removed.
115 const size_t last_non_padding_pos =
116 output->find_last_not_of(kPaddingChar);
117 if (last_non_padding_pos != std::string::npos) {
118 output->resize(last_non_padding_pos + 1);
119 }
120
121 break;
122 }
123 }
124
Base64UrlEncode(std::string_view input,Base64UrlEncodePolicy policy,std::string * output)125 void Base64UrlEncode(std::string_view input,
126 Base64UrlEncodePolicy policy,
127 std::string* output) {
128 Base64UrlEncode(base::as_byte_span(input), policy, output);
129 }
130
Base64UrlDecode(std::string_view input,Base64UrlDecodePolicy policy,std::string * output)131 bool Base64UrlDecode(std::string_view input,
132 Base64UrlDecodePolicy policy,
133 std::string* output) {
134 std::optional<StringViewOrString> base64_input =
135 Base64UrlToBase64(input, policy);
136 if (!base64_input) {
137 return false;
138 }
139 return Base64Decode(base64_input->get(), output);
140 }
141
Base64UrlDecode(std::string_view input,Base64UrlDecodePolicy policy)142 std::optional<std::vector<uint8_t>> Base64UrlDecode(
143 std::string_view input,
144 Base64UrlDecodePolicy policy) {
145 std::optional<StringViewOrString> base64_input =
146 Base64UrlToBase64(input, policy);
147 if (!base64_input) {
148 return std::nullopt;
149 }
150 return Base64Decode(base64_input->get());
151 }
152
153 } // namespace base
154