E-MailRelay
gbase64.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2023 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 = "=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"_sv ;
39 static constexpr string_view character_map( character_map_with_pad.data()+1 , character_map_with_pad.size()-1 ) ;
40 static constexpr char pad = '=' ;
41
42 static_assert( character_map_with_pad.size() == 1+26+26+10+2 , "" ) ;
43 static_assert( character_map.size() == 26+26+10+2 , "" ) ;
44
45 using iterator_in = string_view::const_iterator ;
46 using iterator_out = std::back_insert_iterator<std::string> ;
47
48 std::string encode( string_view , string_view eol ) ;
49 std::string decode( string_view , bool do_throw , bool strict ) ;
50 bool valid( string_view , bool strict ) ;
51
52 void encode_imp( iterator_out , string_view , string_view , std::size_t ) ;
53 void decode_imp( iterator_out , string_view s , bool & error ) ;
54 void generate_6( uint32_type & n , int & i , iterator_out & ) ;
55 void accumulate_8( uint32_type & n , iterator_in & , iterator_in , int & ) ;
56 void accumulate_6( g_uint32_t & n , iterator_in & , iterator_in , std::size_t & , bool & error ) ;
57 void generate_8( g_uint32_t & n , std::size_t & i , iterator_out & , bool & error ) ;
58 std::size_t index( char c , bool & error ) noexcept ;
59 bool strictlyValid( string_view ) noexcept ;
60
61 constexpr char to_char( g_uint32_t n ) noexcept
62 {
63 return static_cast<char>( static_cast<unsigned char>(n) ) ;
64 }
65 constexpr g_uint32_t numeric( char c ) noexcept
66 {
67 return static_cast<g_uint32_t>( static_cast<unsigned char>(c) ) ;
68 }
69 constexpr std::size_t hi_6( g_uint32_t n ) noexcept
70 {
71 return (n >> 18U) & 0x3FU ;
72 }
73 constexpr g_uint32_t hi_8( g_uint32_t n ) noexcept
74 {
75 return (n >> 16U) & 0xFFU ;
76 }
77 }
78}
79
80// ==
81
83{
84 return Base64Imp::encode( s , eol ) ;
85}
86
87std::string G::Base64::decode( string_view s , bool do_throw , bool strict )
88{
89 return Base64Imp::decode( s , do_throw , strict ) ;
90}
91
92bool G::Base64::valid( string_view s , bool strict )
93{
94 return Base64Imp::valid( s , strict ) ;
95}
96
97// ==
98
99std::string G::Base64Imp::encode( string_view input , string_view eol )
100{
101 std::string result ;
102 result.reserve( input.size() + input.size()/2U ) ;
103 encode_imp( std::back_inserter(result) , input , eol , 19U ) ;
104 return result ;
105}
106
107void G::Base64Imp::encode_imp( iterator_out result_p , string_view input , string_view eol , std::size_t blocks_per_line )
108{
109 std::size_t blocks = 0U ;
110 auto const end = input.end() ;
111 for( auto p = input.begin() ; p != end ; blocks++ )
112 {
113 if( !eol.empty() && blocks != 0U && (blocks % blocks_per_line) == 0U )
114 std::copy( eol.begin() , eol.end() , result_p ) ;
115
116 uint32_type n = 0UL ;
117 int i = 0 ;
118 accumulate_8( n , p , end , i ) ;
119 accumulate_8( n , p , end , i ) ;
120 accumulate_8( n , p , end , i ) ;
121 generate_6( n , i , result_p ) ;
122 generate_6( n , i , result_p ) ;
123 generate_6( n , i , result_p ) ;
124 generate_6( n , i , result_p ) ;
125 }
126}
127
128std::string G::Base64Imp::decode( string_view input , bool do_throw , bool strict )
129{
130 bool error = false ;
131 if( strict && !strictlyValid(input) )
132 error = true ;
133
134 std::string result ;
135 result.reserve( input.size() ) ;
136 decode_imp( std::back_inserter(result) , input , error ) ;
137
138 if( error )
139 result.clear() ;
140 if( error && do_throw )
141 throw Base64::Error() ;
142
143 return result ;
144}
145
146void G::Base64Imp::decode_imp( iterator_out result_p , string_view s , bool & error )
147{
148 auto const end = s.end() ;
149 for( auto p = s.begin() ; p != end ; )
150 {
151 if( *p == '\r' || *p == '\n' || *p == ' ' )
152 {
153 ++p ;
154 continue ;
155 }
156
157 // four input characters encode 4*6 bits, so three output bytes
158 g_uint32_t n = 0UL ; // up to 24 bits
159 std::size_t bits = 0U ;
160 accumulate_6( n , p , end , bits , error ) ;
161 accumulate_6( n , p , end , bits , error ) ;
162 accumulate_6( n , p , end , bits , error ) ;
163 accumulate_6( n , p , end , bits , error ) ;
164 if( bits < 8U ) error = true ; // 6 bits cannot make a byte
165 generate_8( n , bits , result_p , error ) ;
166 generate_8( n , bits , result_p , error ) ;
167 generate_8( n , bits , result_p , error ) ;
168 }
169}
170
171bool G::Base64Imp::valid( string_view input , bool strict )
172{
173 if( strict && !strictlyValid(input) )
174 return false ;
175
176 bool error = false ;
177 std::string result ;
178 result.reserve( input.size() ) ;
179 decode_imp( std::back_inserter(result) , input , error ) ;
180 return !error ;
181}
182
183bool G::Base64Imp::strictlyValid( string_view s ) noexcept
184{
185 if( s.empty() )
186 return true ;
187
188 if( s.size() == 1 )
189 return false ; // 6 bits cannot make a byte
190
191 if( std::string::npos == s.find_first_not_of(character_map) )
192 return true ;
193
194 if( std::string::npos != s.find_first_not_of(character_map_with_pad) )
195 return false ;
196
197 std::size_t pos = s.find( pad ) ;
198 if( (pos+1U) == s.size() && s[pos] == pad && (s.size()&3U) == 0U )
199 return true ;
200
201 if( (pos+2U) == s.size() && s[pos] == pad && s[pos+1U] == pad && (s.size()&3U) == 0U )
202 return true ;
203
204 return false ;
205}
206
207void G::Base64Imp::accumulate_8( uint32_type & n , iterator_in & p , iterator_in end , int & i )
208{
209 char c = p == end ? '\0' : *p ;
210 n <<= 8U ;
211 n |= numeric(c) ;
212 if( p != end )
213 {
214 ++p ;
215 ++i ;
216 }
217}
218
219void G::Base64Imp::generate_6( uint32_type & n , int & i , iterator_out & result )
220{
221 size_t index = hi_6( n ) ;
222 char c = i-- >= 0 ? character_map[index] : pad ;
223 *result++ = c ;
224 n <<= 6U ;
225}
226
227void G::Base64Imp::accumulate_6( g_uint32_t & n , iterator_in & p , iterator_in end ,
228 std::size_t & bits , bool & error )
229{
230 n <<= 6U ;
231 if( p == end )
232 {
233 }
234 else if( *p == pad )
235 {
236 ++p ;
237 }
238 else
239 {
240 n |= index( *p++ , error ) ;
241 bits += 6U ;
242 }
243}
244
245void G::Base64Imp::generate_8( g_uint32_t & n , std::size_t & bits , iterator_out & result , bool & error )
246{
247 if( bits >= 8U )
248 {
249 bits -= 8U ;
250 *result++ = to_char(hi_8(n)) ;
251 n <<= 8U ;
252 }
253 else if( hi_8(n) != 0U )
254 {
255 error = true ;
256 }
257}
258
259std::size_t G::Base64Imp::index( char c , bool & error ) noexcept
260{
261 std::size_t pos = character_map.find( c ) ;
262 error = error || (c=='\0') || pos == std::string::npos ;
263 return pos == std::string::npos ? std::size_t(0) : pos ;
264}
265
static std::string decode(string_view, bool throw_on_invalid=false, bool strict=true)
Decodes the given string.
Definition: gbase64.cpp:87
static bool valid(string_view, bool strict=true)
Returns true if the string is a valid base64 encoding, possibly allowing for embedded newlines,...
Definition: gbase64.cpp:92
static std::string encode(string_view, string_view line_break={})
Encodes the given string, optionally inserting line-breaks to limit the line length.
Definition: gbase64.cpp:82
A class like c++17's std::string_view.
Definition: gstringview.h:51
Low-level classes.
Definition: garg.h:30