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 
15 
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
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
118 {
119  return ::restinio::utils::impl::bitops::n_bits_from< char, Shift, 6 >(bs);
120 }
121 
122 inline std::string
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 
179 {
181 };
182 
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 
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
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
270 {
271  auto result = try_decode( str );
272  if( !result )
274 
275  return std::move( *result );
276 }
277 
278 } /* namespace base64 */
279 
280 } /* namespace utils */
281 
282 } /* namespace restinio */
283 
restinio::exception_t
Exception class for all exceptions thrown by RESTinio.
Definition: exception.hpp:26
base64_lut.ipp
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::uint_type_t
std::uint_fast32_t uint_type_t
Definition: base64.hpp:34
nonstd::optional_lite::std11::move
T & move(T &t)
Definition: optional.hpp:421
restinio::string_view_t
nonstd::string_view string_view_t
Definition: string_view.hpp:19
bitops.hpp
restinio::utils::base64::encode
std::string encode(string_view_t str)
Definition: base64.hpp:123
restinio::utils::base64::sixbits_char
char sixbits_char(uint_type_t bs)
Definition: base64.hpp:117
include_fmtlib.hpp
A special wrapper around fmtlib include files.
restinio::utils::base64::is_base64_char
bool is_base64_char(char c) noexcept
Definition: base64.hpp:37
restinio::expected_t
nonstd::expected< T, E > expected_t
Definition: expected.hpp:22
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::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::utils::base64::is_valid_base64_string
bool is_valid_base64_string(string_view_t str) noexcept
Definition: base64.hpp:44
exception.hpp
expected.hpp
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::decode
std::string decode(string_view_t str)
Definition: base64.hpp:269
restinio::utils::base64::decoding_error_t
decoding_error_t
Description of base64 decode error.
Definition: base64.hpp:179