E-MailRelay
genvelope.cpp
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2024 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 genvelope.cpp
19///
20
21#include "gdef.h"
22#include "genvelope.h"
23#include "gfilestore.h"
24#include "gfile.h"
25#include "gstr.h"
26#include "gstringview.h"
27#include "gxtext.h"
28
29namespace GStore
30{
31 namespace EnvelopeImp
32 {
33 std::string folded( const std::string & ) ;
34 std::string xnormalise( const std::string & ) ;
35 std::string readLine( std::istream & , bool * = nullptr ) ;
36 std::string readValue( std::istream & , const std::string & , bool * = nullptr ) ;
37 std::string value( const std::string & ) ;
38 std::string readFormat( std::istream & stream , bool * ) ;
39 void readUtf8Mailboxes( std::istream & , Envelope & ) ;
40 void readBodyType( std::istream & , Envelope & ) ;
41 void readFrom( std::istream & , Envelope & ) ;
42 void readFromAuthIn( std::istream & , Envelope & ) ;
43 void readFromAuthOut( std::istream & , Envelope & ) ;
44 void readForwardTo( std::istream & , Envelope & ) ;
45 void readForwardToAddress( std::istream & , Envelope & ) ;
46 void readClientAccountSelector( std::istream & , Envelope & ) ;
47 void readToList( std::istream & , Envelope & ) ;
48 void readAuthentication( std::istream & , Envelope & ) ;
49 void readClientSocketAddress( std::istream & , Envelope & ) ;
50 void readClientSocketName( std::istream & , Envelope & ) ;
51 void readClientCertificate( std::istream & , Envelope & ) ;
52 void readEnd( std::istream & , Envelope & ) ;
53 std::string_view bodyTypeName( MessageStore::BodyType ) ;
54 MessageStore::BodyType parseSmtpBodyType( std::string_view , MessageStore::BodyType ) ;
55 std::string_view smtpBodyType( MessageStore::BodyType ) ;
56 }
57}
58
59std::size_t GStore::Envelope::write( std::ostream & stream , const GStore::Envelope & e )
60{
61 namespace imp = GStore::EnvelopeImp ;
62 const std::string x = GStore::FileStore::x() ;
63 std::string_view crlf = "\r\n"_sv ;
64
65 std::streampos pos = stream.tellp() ;
66 if( pos < 0 )
67 stream.setstate( std::ios_base::failbit ) ;
68 if( stream.fail() )
69 return 0U ;
70
71 stream << x << "Format: " << GStore::FileStore::format() << crlf ;
72 stream << x << "Content: " << imp::bodyTypeName(e.body_type) << crlf ;
73 stream << x << "From: " << e.from << crlf ;
74 stream << x << "ToCount: " << (e.to_local.size()+e.to_remote.size()) << crlf ;
75 {
76 auto to_p = e.to_local.begin() ;
77 for( ; to_p != e.to_local.end() ; ++to_p )
78 stream << x << "To-Local: " << *to_p << crlf ;
79 }
80 {
81 auto to_p = e.to_remote.begin() ;
82 for( ; to_p != e.to_remote.end() ; ++to_p )
83 stream << x << "To-Remote: " << *to_p << crlf ;
84 }
85 stream << x << "Authentication: " << G::Xtext::encode(e.authentication) << crlf ;
86 stream << x << "Client: " << e.client_socket_address << crlf ;
87 stream << x << "ClientCertificate: " << imp::folded(e.client_certificate) << crlf ;
88 stream << x << "MailFromAuthIn: " << imp::xnormalise(e.from_auth_in) << crlf ;
89 stream << x << "MailFromAuthOut: " << imp::xnormalise(e.from_auth_out) << crlf ;
90 stream << x << "ForwardTo: " << imp::xnormalise(e.forward_to) << crlf ;
91 stream << x << "ForwardToAddress: " << e.forward_to_address << crlf ;
92 stream << x << "ClientAccountSelector: " << e.client_account_selector << crlf ;
93 stream << x << "Utf8MailboxNames: " << (e.utf8_mailboxes?"1":"0") << crlf ;
94 stream << x << "End: 1" << crlf ;
95 stream.flush() ;
96 return stream.fail() ? std::size_t(0U) : static_cast<std::size_t>( stream.tellp() - pos ) ;
97}
98
99void GStore::Envelope::copyExtra( std::istream & in , std::ostream & out )
100{
101 std::string line ;
102 while( G::Str::readLine( in , line ) )
103 {
104 G::Str::trimRight( line , {"\r",1U} ) ;
105 G::Str::trimRight( line , G::Str::ws() ) ;
106 out << line << "\r\n" ;
107 }
108 if( in.bad() || (in.fail()&&!in.eof()) )
109 throw ReadError() ;
110 in.clear( std::ios_base::eofbit ) ; // clear failbit
111}
112
113void GStore::Envelope::read( std::istream & stream , GStore::Envelope & e )
114{
115 namespace imp = GStore::EnvelopeImp ;
116 std::streampos oldpos = stream.tellg() ;
117 std::string format = imp::readFormat( stream , &e.crlf ) ;
118 imp::readBodyType( stream , e ) ;
119 imp::readFrom( stream , e ) ;
120 imp::readToList( stream , e ) ;
121 imp::readAuthentication( stream , e ) ;
122 imp::readClientSocketAddress( stream , e ) ;
123 if( format == GStore::FileStore::format() )
124 {
125 imp::readClientCertificate( stream , e ) ;
126 imp::readFromAuthIn( stream , e ) ;
127 imp::readFromAuthOut( stream , e ) ;
128 imp::readForwardTo( stream , e ) ; // 2.4
129 imp::readForwardToAddress( stream , e ) ; // 2.4
130 imp::readClientAccountSelector( stream , e ) ; // 2.5
131 imp::readUtf8Mailboxes( stream , e ) ; // 2.5rc
132 }
133 else if( format == GStore::FileStore::format(-1) )
134 {
135 imp::readClientCertificate( stream , e ) ;
136 imp::readFromAuthIn( stream , e ) ;
137 imp::readFromAuthOut( stream , e ) ;
138 imp::readForwardTo( stream , e ) ; // 2.4
139 imp::readForwardToAddress( stream , e ) ; // 2.4
140 imp::readUtf8Mailboxes( stream , e ) ; // 2.5rc
141 }
142 else if( format == GStore::FileStore::format(-2) )
143 {
144 imp::readClientCertificate( stream , e ) ;
145 imp::readFromAuthIn( stream , e ) ;
146 imp::readFromAuthOut( stream , e ) ;
147 imp::readForwardTo( stream , e ) ; // 2.4
148 imp::readForwardToAddress( stream , e ) ; // 2.4
149 }
150 else if( format == GStore::FileStore::format(-3) )
151 {
152 imp::readClientCertificate( stream , e ) ;
153 imp::readFromAuthIn( stream , e ) ;
154 imp::readFromAuthOut( stream , e ) ;
155 }
156 else if( format == GStore::FileStore::format(-4) )
157 {
158 imp::readClientSocketName( stream , e ) ;
159 imp::readClientCertificate( stream , e ) ;
160 }
161 imp::readEnd( stream , e ) ;
162
163 if( stream.bad() )
164 throw ReadError() ;
165 else if( stream.fail() && stream.eof() )
166 stream.clear( std::ios_base::eofbit ) ; // clear failbit -- see tellg()
167
168 std::streampos newpos = stream.tellg() ;
169 if( newpos <= 0 || newpos < oldpos )
170 throw ReadError() ; // never gets here
171
172 e.endpos = static_cast<std::size_t>(newpos-oldpos) ;
173}
174
175GStore::MessageStore::BodyType GStore::Envelope::parseSmtpBodyType( const std::string & s , MessageStore::BodyType default_ )
176{
177 namespace imp = GStore::EnvelopeImp ;
178 return imp::parseSmtpBodyType( {s.data(),s.size()} , default_ ) ;
179}
180
181#ifndef G_LIB_SMALL
182std::string GStore::Envelope::smtpBodyType( MessageStore::BodyType type )
183{
184 namespace imp = GStore::EnvelopeImp ;
185 return G::sv_to_string( imp::smtpBodyType( type ) ) ;
186}
187#endif
188
189// ==
190
191std::string GStore::EnvelopeImp::folded( const std::string & s_in )
192{
193 std::string s = s_in ;
194 G::Str::trim( s , G::Str::ws() ) ;
195 G::Str::replaceAll( s , "\r" , "" ) ;
196 G::Str::replaceAll( s , "\n" , "\r\n " ) ; // RFC-2822 folding
197 return s ;
198}
199
200std::string GStore::EnvelopeImp::xnormalise( const std::string & s )
201{
203}
204
205std::string GStore::EnvelopeImp::readFormat( std::istream & stream , bool * crlf )
206{
207 std::string format = readValue( stream , "Format" , crlf ) ;
208 if( ! FileStore::knownFormat(format) )
209 throw Envelope::ReadError( "unknown format id" , format ) ;
210 return format ;
211}
212
213void GStore::EnvelopeImp::readUtf8Mailboxes( std::istream & stream , Envelope & e )
214{
215 e.utf8_mailboxes = readValue(stream,"Utf8MailboxNames") == "1" ;
216}
217
218void GStore::EnvelopeImp::readBodyType( std::istream & stream , Envelope & e )
219{
220 std::string body_type = readValue( stream , "Content" ) ;
221 if( body_type == bodyTypeName(MessageStore::BodyType::SevenBit) )
222 e.body_type = MessageStore::BodyType::SevenBit ;
223 else if( body_type == bodyTypeName(MessageStore::BodyType::EightBitMime) )
224 e.body_type = MessageStore::BodyType::EightBitMime ;
225 else if( body_type == bodyTypeName(MessageStore::BodyType::BinaryMime) )
226 e.body_type = MessageStore::BodyType::BinaryMime ;
227 else
228 e.body_type = MessageStore::BodyType::Unknown ;
229}
230
231void GStore::EnvelopeImp::readFrom( std::istream & stream , Envelope & e )
232{
233 e.from = readValue( stream , "From" ) ;
234}
235
236void GStore::EnvelopeImp::readFromAuthIn( std::istream & stream , Envelope & e )
237{
238 e.from_auth_in = readValue( stream , "MailFromAuthIn" ) ;
239 if( !e.from_auth_in.empty() && e.from_auth_in != "+" && !G::Xtext::valid(e.from_auth_in) )
240 throw Envelope::ReadError( "invalid mail-from-auth-in encoding" ) ;
241}
242
243void GStore::EnvelopeImp::readFromAuthOut( std::istream & stream , Envelope & e )
244{
245 e.from_auth_out = readValue( stream , "MailFromAuthOut" ) ;
246 if( !e.from_auth_out.empty() && e.from_auth_out != "+" && !G::Xtext::valid(e.from_auth_out) )
247 throw Envelope::ReadError( "invalid mail-from-auth-out encoding" ) ;
248}
249
250void GStore::EnvelopeImp::readForwardTo( std::istream & stream , Envelope & e )
251{
252 e.forward_to = readValue( stream , "ForwardTo" ) ;
253}
254
255void GStore::EnvelopeImp::readForwardToAddress( std::istream & stream , Envelope & e )
256{
257 e.forward_to_address = readValue( stream , "ForwardToAddress" ) ;
258}
259
260void GStore::EnvelopeImp::readToList( std::istream & stream , Envelope & e )
261{
262 e.to_local.clear() ;
263 e.to_remote.clear() ;
264
265 unsigned int to_count = G::Str::toUInt( readValue(stream,"ToCount") ) ;
266
267 for( unsigned int i = 0U ; i < to_count ; i++ )
268 {
269 std::string to_line = readLine( stream ) ;
270 bool is_local = to_line.find(FileStore::x().append("To-Local: ")) == 0U ;
271 bool is_remote = to_line.find(FileStore::x().append("To-Remote: ")) == 0U ;
272 if( ! is_local && ! is_remote )
273 throw Envelope::ReadError( "bad 'to' line" ) ;
274
275 if( is_local )
276 e.to_local.push_back( value(to_line) ) ;
277 else
278 e.to_remote.push_back( value(to_line) ) ;
279 }
280}
281
282void GStore::EnvelopeImp::readAuthentication( std::istream & stream , Envelope & e )
283{
284 e.authentication = G::Xtext::decode( readValue(stream,"Authentication") ) ;
285}
286
287void GStore::EnvelopeImp::readClientAccountSelector( std::istream & stream , Envelope & e )
288{
289 e.client_account_selector = readValue( stream , "ClientAccountSelector" ) ;
290}
291
292void GStore::EnvelopeImp::readClientSocketAddress( std::istream & stream , Envelope & e )
293{
294 e.client_socket_address = readValue( stream , "Client" ) ;
295}
296
297void GStore::EnvelopeImp::readClientSocketName( std::istream & stream , Envelope & )
298{
299 G::Xtext::decode( readValue(stream,"ClientName") ) ;
300}
301
302void GStore::EnvelopeImp::readClientCertificate( std::istream & stream , Envelope & e )
303{
304 e.client_certificate = readValue( stream , "ClientCertificate" ) ;
305}
306
307void GStore::EnvelopeImp::readEnd( std::istream & stream , Envelope & )
308{
309 std::string end = readLine( stream ) ;
310 if( end.find(FileStore::x()+"End") != 0U )
311 throw Envelope::ReadError( "no end line" ) ;
312}
313
314std::string GStore::EnvelopeImp::readValue( std::istream & stream , const std::string & expected_key , bool * crlf )
315{
316 std::string line = readLine( stream , crlf ) ;
317
318 std::string prefix = FileStore::x().append(expected_key).append(1U,':') ;
319 if( line == prefix )
320 return {} ;
321
322 prefix.append( 1U , ' ' ) ;
323 std::size_t pos = line.find( prefix ) ;
324 if( pos != 0U )
325 throw Envelope::ReadError( std::string("expected \"").append(FileStore::x()).append(expected_key).append(":\"") ) ;
326
327 // RFC-2822 unfolding
328 for(;;)
329 {
330 int c = stream.peek() ;
331 if( c == ' ' || c == '\t' )
332 {
333 std::string next_line = readLine( stream ) ;
334 if( next_line.empty() || (next_line[0]!=' '&&next_line[0]!='\t') ) // just in case
335 throw Envelope::ReadError() ;
336 next_line[0] = '\n' ;
337 line.append( next_line ) ;
338 }
339 else
340 break ;
341 }
342
343 return value( line ) ;
344}
345
346std::string GStore::EnvelopeImp::readLine( std::istream & stream , bool * crlf )
347{
348 std::string line = G::Str::readLineFrom( stream ) ;
349
350 if( crlf && !line.empty() )
351 *crlf = line.at(line.size()-1U) == '\r' ;
352
353 G::Str::trimRight( line , {"\r",1U} ) ;
354 return line ;
355}
356
357std::string GStore::EnvelopeImp::value( const std::string & line )
358{
359 return G::Str::trimmed( G::Str::tail( line , line.find(':') , std::string() ) , G::Str::ws() ) ;
360}
361
362std::string_view GStore::EnvelopeImp::bodyTypeName( MessageStore::BodyType type )
363{
364 if( type == MessageStore::BodyType::EightBitMime )
365 return "8bit"_sv ;
366 else if( type == MessageStore::BodyType::SevenBit )
367 return "7bit"_sv ;
368 else if( type == MessageStore::BodyType::BinaryMime )
369 return "binarymime"_sv ;
370 else
371 return "unknown"_sv ;
372}
373
374GStore::MessageStore::BodyType GStore::EnvelopeImp::parseSmtpBodyType( std::string_view s , MessageStore::BodyType default_ )
375{
376 if( s.empty() )
377 return default_ ;
378 else if( G::Str::imatch( "7BIT"_sv , s ) )
379 return MessageStore::BodyType::SevenBit ;
380 else if( G::Str::imatch( "8BITMIME"_sv , s ) )
381 return MessageStore::BodyType::EightBitMime ;
382 else if( G::Str::imatch( "BINARYMIME"_sv , s ) )
383 return MessageStore::BodyType::BinaryMime ;
384 else
385 return MessageStore::BodyType::Unknown ;
386}
387
388std::string_view GStore::EnvelopeImp::smtpBodyType( MessageStore::BodyType type )
389{
390 if( type == MessageStore::BodyType::EightBitMime )
391 return "8BITMIME"_sv ;
392 else if( type == MessageStore::BodyType::SevenBit )
393 return "7BIT"_sv ;
394 else if( type == MessageStore::BodyType::BinaryMime )
395 return "BINARYMIME"_sv ;
396 else
397 return {} ;
398}
399
A structure containing the contents of an envelope file, with support for file reading,...
Definition: genvelope.h:41
static void copyExtra(std::istream &, std::ostream &)
A convenience function to copy extra envelope lines from an envelope input stream to an output stream...
Definition: genvelope.cpp:99
static std::size_t write(std::ostream &, const Envelope &)
Writes an envelope to a seekable stream.
Definition: genvelope.cpp:59
static std::string smtpBodyType(MessageStore::BodyType)
Converts a body type enum into the corresponding SMTP keyword.
Definition: genvelope.cpp:182
static void read(std::istream &, Envelope &)
Reads an envelope from a stream.
Definition: genvelope.cpp:113
static MessageStore::BodyType parseSmtpBodyType(const std::string &, MessageStore::BodyType default_=MessageStore::BodyType::Unknown)
Parses an SMTP MAIL-FROM BODY= parameter.
Definition: genvelope.cpp:175
static std::string x()
Returns the prefix for envelope header lines.
Definition: gfilestore.cpp:129
static std::string format(int generation=0)
Returns an identifier for the storage format implemented by this class, or some older generation of i...
Definition: gfilestore.cpp:134
static bool knownFormat(const std::string &format)
Returns true if the storage format string is recognised and supported for reading.
Definition: gfilestore.cpp:151
static std::string & trimRight(std::string &s, std::string_view ws, std::size_t limit=0U)
Trims the rhs of s, taking off up to 'limit' of the 'ws' characters.
Definition: gstr.cpp:313
static bool imatch(char, char) noexcept
Returns true if the two characters are the same, ignoring seven-bit case.
Definition: gstr.cpp:1415
static std::istream & readLine(std::istream &stream, std::string &result, std::string_view eol={}, bool pre_erase_result=true, std::size_t limit=0U)
Reads a line from the stream using the given line terminator, which may be multi-character.
Definition: gstr.cpp:958
static std::string & trim(std::string &s, std::string_view ws)
Trims both ends of s, taking off any of the 'ws' characters.
Definition: gstr.cpp:338
static std::string tail(std::string_view in, std::size_t pos, std::string_view default_={})
Returns the last part of the string after the given position.
Definition: gstr.cpp:1322
static std::string readLineFrom(std::istream &stream, std::string_view eol={})
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:951
static unsigned int toUInt(std::string_view s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:648
static unsigned int replaceAll(std::string &s, std::string_view from, std::string_view to)
Does a global replace on string 's', replacing all occurrences of sub-string 'from' with 'to'.
Definition: gstr.cpp:247
static std::string_view ws() noexcept
Returns a string of standard whitespace characters.
Definition: gstr.cpp:1265
static std::string trimmed(const std::string &s, std::string_view ws)
Returns a trim()med version of s.
Definition: gstr.cpp:343
static std::string encode(std::string_view)
Encodes the given string.
Definition: gxtext.cpp:97
static std::string decode(std::string_view)
Decodes the given string.
Definition: gxtext.cpp:119
static bool valid(std::string_view, bool strict=false)
Returns true if a valid encoding, or empty.
Definition: gxtext.cpp:77
Message store classes.
Definition: genvelope.cpp:30