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