E-MailRelay
gbase64.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 Graeme Walker <graeme_walker@users.sourceforge.net>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program. If not, see <http://www.gnu.org/licenses/>.
16// ===
17///
18/// \file gbase64.cpp
19///
20
21#include "gdef.h"
22#include "gbase64.h"
23#include "gstringview.h"
24#include "gstr.h"
25#include <algorithm>
26#include <iterator>
27
28namespace G
29{
30 namespace Base64Imp
31 {
32 #ifdef G_WINDOWS
33 using uint32_type = volatile g_uint32_t ; // volatile as workround for compiler bug: MSVC 2019 16.6.2 /02 /Ob2
34 #else
35 using uint32_type = g_uint32_t ;
36 #endif
37
38 static constexpr string_view character_map_with_pad( "=ABCDEFGHIJKLMNOPQRSTUVWXYZ"
39 "abcdefghijklmnopqrstuvwxyz" "0123456789+/" , nullptr ) ;
40 static constexpr string_view character_map( character_map_with_pad.data()+1 , character_map_with_pad.size()-1 ) ;
41 static constexpr char pad = '=' ;
42
43 static_assert( character_map_with_pad.size() == 1+26+26+10+2 , "" ) ;
44 static_assert( character_map.size() == 26+26+10+2 , "" ) ;
45
46 using string_in = string_view ;
47 using iterator_in = string_view::const_iterator ;
48 using iterator_out = std::back_insert_iterator<std::string> ;
49
50 std::string encode( string_in , string_in eol ) ;
51 std::string decode( string_in , bool do_throw , bool strict ) ;
52 bool valid( string_in , bool strict ) ;
53
54 void encode_imp( iterator_out , string_in , string_in , std::size_t ) ;
55 void decode_imp( iterator_out , string_in s , bool & error ) ;
56 void generate_6( uint32_type & n , int & i , iterator_out & ) ;
57 void accumulate_8( uint32_type & n , iterator_in & , iterator_in , int & ) ;
58 void accumulate_6( g_uint32_t & n , iterator_in & , iterator_in , std::size_t & , bool & error ) ;
59 void generate_8( g_uint32_t & n , std::size_t & i , iterator_out & , bool & error ) ;
60 std::size_t index( char c , bool & error ) noexcept ;
61 bool strictlyValid( string_view ) noexcept ;
62
63 constexpr char to_char( g_uint32_t n ) noexcept
64 {
65 return static_cast<char>( static_cast<unsigned char>(n) ) ;
66 }
67 constexpr g_uint32_t numeric( char c ) noexcept
68 {
69 return static_cast<g_uint32_t>( static_cast<unsigned char>(c) ) ;
70 }
71 constexpr std::size_t hi_6( g_uint32_t n ) noexcept
72 {
73 return (n >> 18U) & 0x3FU ;
74 }
75 constexpr g_uint32_t hi_8( g_uint32_t n ) noexcept
76 {
77 return (n >> 16U) & 0xFFU ;
78 }
79 }
80}
81
82// ==
83
84std::string G::Base64::encode( const std::string & s , const std::string & eol )
85{
86 return Base64Imp::encode( {s.data(),s.size()} , {eol.data(),eol.size()} ) ;
87}
88
89std::string G::Base64::decode( const std::string & s , bool do_throw , bool strict )
90{
91 return Base64Imp::decode( {s.data(),s.size()} , do_throw , strict ) ;
92}
93
94bool G::Base64::valid( const std::string & s , bool strict )
95{
96 return Base64Imp::valid( {s.data(),s.size()} , strict ) ;
97}
98
99// ==
100
101std::string G::Base64Imp::encode( string_in input , string_in eol )
102{
103 std::string result ;
104 result.reserve( input.size() + input.size()/2U ) ;
105 encode_imp( std::back_inserter(result) , input , eol , 19U ) ;
106 return result ;
107}
108
109void G::Base64Imp::encode_imp( iterator_out result_p , string_in input , string_in eol , std::size_t blocks_per_line )
110{
111 std::size_t blocks = 0U ;
112 auto const end = input.end() ;
113 for( auto p = input.begin() ; p != end ; blocks++ )
114 {
115 if( !eol.empty() && blocks != 0U && (blocks % blocks_per_line) == 0U )
116 std::copy( eol.begin() , eol.end() , result_p ) ;
117
118 uint32_type n = 0UL ;
119 int i = 0 ;
120 accumulate_8( n , p , end , i ) ;
121 accumulate_8( n , p , end , i ) ;
122 accumulate_8( n , p , end , i ) ;
123 generate_6( n , i , result_p ) ;
124 generate_6( n , i , result_p ) ;
125 generate_6( n , i , result_p ) ;
126 generate_6( n , i , result_p ) ;
127 }
128}
129
130std::string G::Base64Imp::decode( string_in input , bool do_throw , bool strict )
131{
132 bool error = false ;
133 if( strict && !strictlyValid(input) )
134 error = true ;
135
136 std::string result ;
137 result.reserve( input.size() ) ;
138 decode_imp( std::back_inserter(result) , input , error ) ;
139
140 if( error )
141 result.clear() ;
142 if( error && do_throw )
143 throw Base64::Error() ;
144
145 return result ;
146}
147
148void G::Base64Imp::decode_imp( iterator_out result_p , string_in s , bool & error )
149{
150 auto const end = s.end() ;
151 for( auto p = s.begin() ; p != end ; )
152 {
153 if( *p == '\r' || *p == '\n' || *p == ' ' )
154 {
155 ++p ;
156 continue ;
157 }
158
159 // four input characters encode 4*6 bits, so three output bytes
160 g_uint32_t n = 0UL ; // up to 24 bits
161 std::size_t bits = 0U ;
162 accumulate_6( n , p , end , bits , error ) ;
163 accumulate_6( n , p , end , bits , error ) ;
164 accumulate_6( n , p , end , bits , error ) ;
165 accumulate_6( n , p , end , bits , error ) ;
166 if( bits < 8U ) error = true ; // 6 bits cannot make a byte
167 generate_8( n , bits , result_p , error ) ;
168 generate_8( n , bits , result_p , error ) ;
169 generate_8( n , bits , result_p , error ) ;
170 }
171}
172
173bool G::Base64Imp::valid( string_in input , bool strict )
174{
175 if( strict && !strictlyValid(input) )
176 return false ;
177
178 bool error = false ;
179 std::string result ;
180 result.reserve( input.size() ) ;
181 decode_imp( std::back_inserter(result) , input , error ) ;
182 return !error ;
183}
184
185bool G::Base64Imp::strictlyValid( string_view s ) noexcept
186{
187 if( s.empty() )
188 return true ;
189
190 if( s.size() == 1 )
191 return false ; // 6 bits cannot make a byte
192
193 if( std::string::npos == s.find_first_not_of(character_map) )
194 return true ;
195
196 if( std::string::npos != s.find_first_not_of(character_map_with_pad) )
197 return false ;
198
199 std::size_t pos = s.find( pad ) ;
200 if( (pos+1U) == s.size() && s[pos] == pad && (s.size()&3U) == 0U )
201 return true ;
202
203 if( (pos+2U) == s.size() && s[pos] == pad && s[pos+1U] == pad && (s.size()&3U) == 0U )
204 return true ;
205
206 return false ;
207}
208
209void G::Base64Imp::accumulate_8( uint32_type & n , iterator_in & p , iterator_in end , int & i )
210{
211 char c = p == end ? '\0' : *p ;
212 n <<= 8U ;
213 n |= numeric(c) ;
214 if( p != end )
215 {
216 ++p ;
217 ++i ;
218 }
219}
220
221void G::Base64Imp::generate_6( uint32_type & n , int & i , iterator_out & result )
222{
223 size_t index = hi_6( n ) ;
224 char c = i-- >= 0 ? character_map[index] : pad ;
225 *result++ = c ;
226 n <<= 6U ;
227}
228
229void G::Base64Imp::accumulate_6( g_uint32_t & n , iterator_in & p , iterator_in end ,
230 std::size_t & bits , bool & error )
231{
232 n <<= 6U ;
233 if( p == end )
234 {
235 }
236 else if( *p == pad )
237 {
238 ++p ;
239 }
240 else
241 {
242 n |= index( *p++ , error ) ;
243 bits += 6U ;
244 }
245}
246
247void G::Base64Imp::generate_8( g_uint32_t & n , std::size_t & bits , iterator_out & result , bool & error )
248{
249 if( bits >= 8U )
250 {
251 bits -= 8U ;
252 *result++ = to_char(hi_8(n)) ;
253 n <<= 8U ;
254 }
255 else if( hi_8(n) != 0U )
256 {
257 error = true ;
258 }
259}
260
261std::size_t G::Base64Imp::index( char c , bool & error ) noexcept
262{
263 std::size_t pos = character_map.find( c ) ;
264 error = error || (c=='\0') || pos == std::string::npos ;
265 return pos == std::string::npos ? std::size_t(0) : pos ;
266}
267
static std::string decode(const std::string &, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
Definition: gbase64.cpp:89
static std::string encode(const std::string &s, const std::string &line_break=std::string())
Encodes the given string, optionally inserting line-breaks to limit the line length.
Definition: gbase64.cpp:84
static bool valid(const std::string &, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
Definition: gbase64.cpp:94
Low-level classes.
Definition: galign.h:28