Skip to content

Commit 8872746

Browse files
jonsimantova-maurice
authored andcommitted
Add Base64 encode and decode functions.
Originally we wanted to call curl_base64_encode and curl_base64_decode, but since those functions are internal to curl and not accessible by outside code, I decided to implement this by hand instead. :D PiperOrigin-RevId: 248577402
1 parent d0c961c commit 8872746

File tree

3 files changed

+301
-0
lines changed

3 files changed

+301
-0
lines changed

app/src/base64.cc

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include "app/src/base64.h"
18+
19+
#include <cstdint>
20+
21+
#include "app/src/assert.h"
22+
#include "app/src/log.h"
23+
24+
namespace firebase {
25+
namespace internal {
26+
27+
// Maps 6-bit index to base64 encoded character.
28+
static const char kBase64Table[] =
29+
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
30+
31+
// Reverse lookup for kBase64Table above; -1 signifies an invalid character.
32+
// Maps ASCII value to 6-bit index.
33+
// clang-format off
34+
static const int8_t kBase64TableReverse[256] = {
35+
/* 0 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
36+
/* 16 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
37+
/* 32 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
38+
/* 48 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
39+
/* 64 */ -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
40+
/* 80 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
41+
/* 96 */ -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
42+
/* 112 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1,
43+
/* 128 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
44+
/* 144 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
45+
/* 160 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
46+
/* 176 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
47+
/* 192 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
48+
/* 208 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
49+
/* 224 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
50+
/* 240 */ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
51+
};
52+
// clang-format on
53+
54+
// If the last character is this, the decoded length has 1 subtracted from it.
55+
// If the last 2 characters are both this, the decoded length has 2 subtracted
56+
// from it. (See GetBase64DecodedSize for implementation detail.)
57+
static const char kBase64NullEnding = '=';
58+
59+
// Base64 encode a string (binary allowed). Returns true if successful.
60+
static bool Base64EncodeInternal(const std::string& input, std::string* output,
61+
bool pad_to_32_bits) {
62+
if (!output) {
63+
return false;
64+
}
65+
66+
// Workaround for if input and output are the same string.
67+
bool inplace = (output == &input);
68+
std::string inplace_buffer;
69+
std::string* output_ptr = inplace ? &inplace_buffer : output;
70+
71+
// The base64 algorithm is pretty simple: take 3 bytes = 24 bits of data at a
72+
// time, and encode them in four 6-bit chunks.
73+
output_ptr->resize(GetBase64EncodedSize(input));
74+
for (int i = 0, o = 0; i < input.size(); i += 3, o += 4) {
75+
uint32_t b0 = static_cast<uint8_t>(input[i]);
76+
uint32_t b1 =
77+
(i + 1 < input.size()) ? static_cast<uint8_t>(input[i + 1]) : 0;
78+
uint32_t b2 =
79+
(i + 2 < input.size()) ? static_cast<uint8_t>(input[i + 2]) : 0;
80+
81+
uint32_t stream = (b2 & 0xFF) | ((b1 & 0xFF) << 8) | ((b0 & 0xFF) << 16);
82+
83+
(*output_ptr)[o + 0] = kBase64Table[(stream >> 18) & 0x3F];
84+
(*output_ptr)[o + 1] = kBase64Table[(stream >> 12) & 0x3F];
85+
(*output_ptr)[o + 2] = (i + 1 < input.size())
86+
? kBase64Table[(stream >> 6) & 0x3F]
87+
: kBase64NullEnding;
88+
(*output_ptr)[o + 3] = (i + 2 < input.size())
89+
? kBase64Table[(stream >> 0) & 0x3F]
90+
: kBase64NullEnding;
91+
}
92+
if (!pad_to_32_bits) {
93+
// If the last 1 or 2 characters are '=', trim them.
94+
if (!output_ptr->empty() &&
95+
(*output_ptr)[output_ptr->size() - 1] == kBase64NullEnding) {
96+
if (output_ptr->size() > 1 &&
97+
(*output_ptr)[output_ptr->size() - 2] == kBase64NullEnding) {
98+
output_ptr->resize(output_ptr->size() - 2);
99+
} else {
100+
output_ptr->resize(output_ptr->size() - 1);
101+
}
102+
}
103+
}
104+
if (inplace) {
105+
*output = inplace_buffer;
106+
}
107+
return true;
108+
}
109+
110+
bool Base64Encode(const std::string& input, std::string* output) {
111+
return Base64EncodeInternal(input, output, false);
112+
}
113+
114+
bool Base64EncodeWithPadding(const std::string& input, std::string* output) {
115+
return Base64EncodeInternal(input, output, true);
116+
}
117+
118+
// Get the size that a given string would take up if encoded to Base64.
119+
size_t GetBase64EncodedSize(const std::string& input) {
120+
return ((input.length() + 2) / 3) * 4;
121+
}
122+
123+
// Base64 decode a string (may output binary). Returns true if successful. If
124+
// the string is not a multiple of 4 bytes in size, = or == are implied at the
125+
// end.
126+
bool Base64Decode(const std::string& input, std::string* output) {
127+
if (!output) {
128+
return false;
129+
}
130+
// All Base64 strings must be padded to 4 bytes, with optionally 1 or 2 of the
131+
// ending bytes removed.
132+
if (input.size() % 4 == 1) {
133+
return false;
134+
}
135+
// Workaround for if input and output are the same string.
136+
bool inplace = (output == &input);
137+
std::string inplace_buffer;
138+
std::string* output_ptr = inplace ? &inplace_buffer : output;
139+
output_ptr->resize(GetBase64DecodedSize(input));
140+
141+
for (int i = 0, o = 0; i < input.size(); i += 4, o += 3) {
142+
char input0 = input[i + 0];
143+
char input1 = input[i + 1];
144+
// At the end of the string, missing bytes 2 and 3 are considered '='.
145+
char input2 = i + 2 < input.size() ? input[i + 2] : kBase64NullEnding;
146+
char input3 = i + 3 < input.size() ? input[i + 3] : kBase64NullEnding;
147+
// If any unknown characters appear, it's an error.
148+
if (kBase64TableReverse[input0] < 0 || kBase64TableReverse[input1] < 0 ||
149+
kBase64TableReverse[input2] < 0 || kBase64TableReverse[input3] < 0) {
150+
return false;
151+
}
152+
// If kBase64NullEnding appears anywhere but the last 1 or 2 characters,
153+
// or if it appears but input.size() % 4 != 0, it's an error.
154+
bool at_end = (i + 4 >= input.size());
155+
if ((input0 == kBase64NullEnding) || (input1 == kBase64NullEnding) ||
156+
(input2 == kBase64NullEnding && !at_end) ||
157+
(input2 == kBase64NullEnding && input3 != kBase64NullEnding) ||
158+
(input3 == kBase64NullEnding && !at_end)) {
159+
return false;
160+
}
161+
uint32_t b0 = kBase64TableReverse[input0] & 0x3f;
162+
uint32_t b1 = kBase64TableReverse[input1] & 0x3f;
163+
uint32_t b2 = kBase64TableReverse[input2] & 0x3f;
164+
uint32_t b3 = kBase64TableReverse[input3] & 0x3f;
165+
166+
uint32_t stream = (b0 << 18) | (b1 << 12) | (b2 << 6) | b3;
167+
(*output_ptr)[o + 0] = (stream >> 16) & 0xFF;
168+
if (input2 != kBase64NullEnding) {
169+
(*output_ptr)[o + 1] = (stream >> 8) & 0xFF;
170+
} else if (((stream >> 8) & 0xFF) != 0) {
171+
// If there are any stale bits in this from input1, the text is malformed.
172+
return false;
173+
}
174+
if (input3 != kBase64NullEnding) {
175+
(*output_ptr)[o + 2] = (stream >> 0) & 0xFF;
176+
} else if (((stream >> 0) & 0xFF) != 0) {
177+
// If there are any stale bits in this from input2, the text is malformed.
178+
return false;
179+
}
180+
}
181+
if (inplace) {
182+
*output = inplace_buffer;
183+
}
184+
return true;
185+
}
186+
187+
// Get the size that a given string would take up if decoded from base64.
188+
size_t GetBase64DecodedSize(const std::string& input) {
189+
int mod = input.size() % 4;
190+
if (input.empty() || mod == 1) {
191+
// Special-cased so we don't have to check input.size() > 1 below.
192+
return 0;
193+
}
194+
size_t padded_size = ((input.size() + 3) / 4) * 3;
195+
if (mod >= 2 || (mod == 0 && input[input.size() - 1] == kBase64NullEnding)) {
196+
// If the last byte is '=', or the input size % 4 is 2 or 3 (thus there
197+
// are implied '='s), then the actual size is 1-2 bytes smaller.
198+
if (mod == 2 ||
199+
(mod == 0 && input[input.size() - 2] == kBase64NullEnding)) {
200+
// If the second-to-last byte is also '=', or the input size % 4 is 2
201+
// (implying a second '='), then the actual size is 2 bytes smaller.
202+
return padded_size - 2;
203+
} else {
204+
// Otherwise it's just the last character and the actual size is 1 byte
205+
// smaller.
206+
return padded_size - 1;
207+
}
208+
}
209+
return padded_size;
210+
}
211+
212+
} // namespace internal
213+
} // namespace firebase

app/src/base64.h

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#ifndef FIREBASE_APP_CLIENT_CPP_SRC_BASE64_H_
18+
#define FIREBASE_APP_CLIENT_CPP_SRC_BASE64_H_
19+
20+
#include <string>
21+
22+
namespace firebase {
23+
namespace internal {
24+
25+
// Base64 encode a string (binary allowed). Returns true if successful.
26+
bool Base64Encode(const std::string& input, std::string* output);
27+
28+
// Base64 encode a string (binary allowed). Returns true if successful.
29+
// Pads output to 32 bits by adding = or == to the end, as is tradition.
30+
bool Base64EncodeWithPadding(const std::string& input, std::string* output);
31+
32+
// Get the size that a given string would take up if encoded to Base64, rounded
33+
// up to the next 4 bytes.
34+
size_t GetBase64EncodedSize(const std::string& input);
35+
36+
// Base64 decode a string (may output binary). Returns true if successful, or
37+
// false if there is an error (in which case the output string is undefined).
38+
bool Base64Decode(const std::string& input, std::string* output);
39+
40+
// Base64 decode a string (may output binary). Returns true if successful, or
41+
// false if there is an error (in which case the output string is undefined).
42+
bool Base64Decode(const std::string& input, std::string* output);
43+
44+
// Get the size that a given string would take up if decoded from Base64.
45+
// Returns 0 if the given string is not a valid Base64 size (or, of course, if
46+
// you passed in the empty string.).
47+
size_t GetBase64DecodedSize(const std::string& input);
48+
49+
} // namespace internal
50+
} // namespace firebase
51+
52+
#endif // FIREBASE_APP_CLIENT_CPP_SRC_BASE64_H_

app/src/base64_fuzzer.cc

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2019 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
#include <cassert>
18+
#include <string>
19+
20+
#include "app/src/base64.h"
21+
22+
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
23+
// Test encoding and decoding this string.
24+
std::string orig(reinterpret_cast<const char *>(data), size);
25+
std::string encoded, decoded;
26+
bool success = firebase::internal::Base64Encode(orig, &encoded);
27+
assert(success);
28+
success = firebase::internal::Base64Decode(encoded, &decoded);
29+
assert(success);
30+
assert(orig == decoded);
31+
32+
// Test passing in this string to the decoder, and make sure it doesn't crash.
33+
std::string unused;
34+
firebase::internal::Base64Decode(orig, &unused);
35+
return 0;
36+
}

0 commit comments

Comments
 (0)