RESTinio

base64.hpp
Go to the documentation of this file.
1/*
2 restinio
3*/
4
9#pragma once
10
11#include <restinio/exception.hpp>
12#include <restinio/expected.hpp>
13
14#include <restinio/utils/impl/bitops.hpp>
15
16#include <restinio/impl/include_fmtlib.hpp>
17
18#include <string>
19#include <array>
20#include <exception>
21#include <iostream> // std::cout, debug
22
23 namespace restinio
24{
25
26 namespace utils
27{
28
29 namespace base64
30{
31
32#include "base64_lut.ipp"
33
34 using uint_type_t = std::uint_fast32_t;
35
36 inline bool
37 is_base64_char( char c ) noexcept
38{
39 return 1 == is_base64_char_lut< unsigned char >()[
40 static_cast<unsigned char>(c) ];
41}
42
43 inline bool
44 is_valid_base64_string( string_view_t str ) noexcept
45{
46 enum class expected_type { b64ch, b64ch_or_padding, padding };
47
48 if( str.size() < 4u )
49 return false;
50
51 expected_type expects = expected_type::b64ch;
52 std::uint_fast8_t b64chars_found = 0u; // Can't be greater than 2.
53 std::uint_fast8_t paddings_found = 0u; // Can't be greater than 3.
54 for( const auto ch : str )
55 {
56 switch( expects )
57 {
58 case expected_type::b64ch:
59 {
60 // Because '=' is a part of base64_chars, it should be checked
61 // individually.
62 if( '=' == ch )
63 return false;
64 else if( is_base64_char( ch ) )
65 {
66 ++b64chars_found;
67 if( b64chars_found >= 2u )
68 expects = expected_type::b64ch_or_padding;
69 }
70 else
71 return false;
72 break;
73 }
74 case expected_type::b64ch_or_padding:
75 {
76 if( '=' == ch )
77 {
78 expects = expected_type::padding;
79 ++paddings_found;
80 }
81 else if( is_base64_char( ch ) )
82 {
83 /* Nothing to do */
84 }
85 else
86 return false;
87
88 break;
89 }
90 case expected_type::padding:
91 {
92 if( '=' == ch )
93 {
94 ++paddings_found;
95 if( paddings_found > 2u )
96 return false;
97 }
98 else
99 return false;
100
101 break;
102 }
103 }
104 }
105
106 return true;
107}
108
109 inline uint_type_t
110 uch( char ch )
111{
112 return static_cast<uint_type_t>(static_cast<unsigned char>(ch));
113}
114
115 template<unsigned int Shift>
116 char
117 sixbits_char( uint_type_t bs )
118{
119 return ::restinio::utils::impl::bitops::n_bits_from< char, Shift, 6 >(bs);
120}
121
122 inline std::string
123 encode( string_view_t str )
124{
125 std::string result;
126
127 const auto at = [&str](auto index) { return uch(str[index]); };
128
129 const auto alphabet_char = [](auto ch) {
130 return static_cast<char>(
131 base64_alphabet< unsigned char >()[
132 static_cast<unsigned char>(ch) ]);
133 };
134
135 constexpr std::size_t group_size = 3u;
136 const auto remaining = str.size() % group_size;
137
138 result.reserve( (str.size()/group_size + (remaining ? 1:0)) * 4 );
139
140 std::size_t i = 0;
141 for(; i < str.size() - remaining; i += group_size )
142 {
143 uint_type_t bs = (at(i) << 16) | (at(i+1) << 8) | at(i+2);
144
145 result.push_back( alphabet_char( sixbits_char<18>(bs) ) );
146 result.push_back( alphabet_char( sixbits_char<12>(bs) ) );
147 result.push_back( alphabet_char( sixbits_char<6>(bs) ) );
148 result.push_back( alphabet_char( sixbits_char<0>(bs) ) );
149 }
150
151 if( remaining )
152 {
153 // Some code duplication to avoid additional IFs.
154 if( 1u == remaining )
155 {
156 uint_type_t bs = (at(i) << 16);
157 result.push_back( alphabet_char( sixbits_char<18>(bs) ) );
158 result.push_back( alphabet_char( sixbits_char<12>(bs) ) );
159
160 result.push_back('=');
161 }
162 else
163 {
164 uint_type_t bs = (at(i) << 16) | (at(i+1) << 8);
165
166 result.push_back( alphabet_char( sixbits_char<18>(bs) ) );
167 result.push_back( alphabet_char( sixbits_char<12>(bs) ) );
168 result.push_back( alphabet_char( sixbits_char<6>(bs) ) );
169 }
170
171 result.push_back('=');
172 }
173
174 return result;
175}
176
178 enum class decoding_error_t
179{
180 invalid_base64_sequence
181};
182
183 inline expected_t< std::string, decoding_error_t >
184 try_decode( string_view_t str )
185{
186 if( !is_valid_base64_string( str ) )
187 return make_unexpected( decoding_error_t::invalid_base64_sequence );
188
189 constexpr std::size_t group_size = 4;
190
191 std::string result;
192 result.reserve( (str.size() / group_size) * 3 );
193
194 const unsigned char * const decode_table = base64_decode_lut< unsigned char >();
195
196 const auto at = [&str](auto index) {
197 return static_cast<unsigned char>(str[index]);
198 };
199
200 for( size_t i = 0 ; i < str.size(); i += group_size)
201 {
202
203 uint_type_t bs{};
204 int paddings_found = 0u;
205
206 bs |= decode_table[ at(i) ];
207
208 bs <<= 6;
209 bs |= decode_table[ at(i+1) ];
210
211 bs <<= 6;
212 if( '=' == str[i+2] )
213 {
214 ++paddings_found;
215 }
216 else
217 {
218 bs |= decode_table[ at(i+2) ];
219 }
220
221 bs <<= 6;
222 if( '=' == str[i+3] )
223 {
224 ++paddings_found;
225 }
226 else
227 {
228 bs |= decode_table[ at(i+3) ];
229 }
230
231 using ::restinio::utils::impl::bitops::n_bits_from;
232
233 result.push_back( n_bits_from< char, 16 >(bs) );
234 if( paddings_found < 2 )
235 {
236 result.push_back( n_bits_from< char, 8 >(bs) );
237 }
238 if( paddings_found < 1 )
239 {
240 result.push_back( n_bits_from< char, 0 >(bs) );
241 }
242 }
243
244 return result;
245}
246
247 namespace impl
248{
249
250 inline void
251 throw_exception_on_invalid_base64_string( string_view_t str )
252{
253 constexpr size_t max_allowed_len = 32u;
254 // If str is too long only a part of it will be included
255 // in the error message.
256 if( str.size() > max_allowed_len )
257 throw exception_t{
258 fmt::format( "invalid base64 string that starts with '{}'",
259 str.substr( 0u, max_allowed_len ) )
260 };
261 else
262 throw exception_t{
263 fmt::format( "invalid base64 string '{}'", str ) };
264}
265
266} /* namespace impl */
267
268 inline std::string
269 decode( string_view_t str )
270{
271 auto result = try_decode( str );
272 if( !result )
273 impl::throw_exception_on_invalid_base64_string( str );
274
275 return std::move( *result );
276}
277
278} /* namespace base64 */
279
280} /* namespace utils */
281
282} /* namespace restinio */
283
base64_lut.ipp
bitops.hpp
restinio::exception_t
Exception class for all exceptions thrown by RESTinio.
Definition: exception.hpp:26
exception.hpp
expected.hpp
include_fmtlib.hpp
A special wrapper around fmtlib include files.
nonstd::optional_lite::std11::move
T & move(T &t)
Definition: optional.hpp:465
restinio::utils::base64::impl::throw_exception_on_invalid_base64_string
void throw_exception_on_invalid_base64_string(string_view_t str)
Definition: base64.hpp:251
restinio::utils::base64::sixbits_char
char sixbits_char(uint_type_t bs)
Definition: base64.hpp:117
restinio::utils::base64::uint_type_t
std::uint_fast32_t uint_type_t
Definition: base64.hpp:34
restinio::utils::base64::is_base64_char
bool is_base64_char(char c) noexcept
Definition: base64.hpp:37
restinio::utils::base64::decode
std::string decode(string_view_t str)
Definition: base64.hpp:269
restinio::utils::base64::is_valid_base64_string
bool is_valid_base64_string(string_view_t str) noexcept
Definition: base64.hpp:44
restinio::utils::base64::try_decode
expected_t< std::string, decoding_error_t > try_decode(string_view_t str)
Definition: base64.hpp:184
restinio::utils::base64::decoding_error_t
decoding_error_t
Description of base64 decode error.
Definition: base64.hpp:179
restinio::utils::base64::decoding_error_t::invalid_base64_sequence
@ invalid_base64_sequence
restinio::utils::base64::uch
uint_type_t uch(char ch)
Definition: base64.hpp:110
restinio::utils::base64::encode
std::string encode(string_view_t str)
Definition: base64.hpp:123
restinio::utils::impl::bitops::n_bits_from
T n_bits_from(F value)
Extract N bits from a bigger integer value.
Definition: bitops.hpp:86
restinio
Definition: asio_include.hpp:21
restinio::string_view_t
nonstd::string_view string_view_t
Definition: string_view.hpp:19
restinio::expected_t
nonstd::expected< T, E > expected_t
Definition: expected.hpp:22