E-MailRelay
gstoredfile.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 gstoredfile.cpp
19///
20
21#include "gdef.h"
22#include "gfilestore.h"
23#include "gstoredfile.h"
24#include "gscope.h"
25#include "gfile.h"
26#include "gfbuf.h"
27#include "gstr.h"
28#include "glog.h"
29#include "gassert.h"
30#include <fstream>
31#include <type_traits>
32#include <limits>
33#include <utility>
34
35GStore::StoredFile::StoredFile( FileStore & store , const MessageId & id , State state ) :
36 m_store(store) ,
37 m_id(id) ,
38 m_state(state)
39{
40}
41
43{
44 try
45 {
46 if( m_unlock && m_state == State::Locked )
47 {
48 G_DEBUG( "GStore::StoredFile::dtor: unlocking envelope [" << epath(State::Locked).basename() << "]" ) ;
49 FileOp::rename( epath(State::Locked) , epath(State::Normal) ) ;
50 static_cast<MessageStore&>(m_store).updated() ;
51 }
52 }
53 catch(...) // dtor
54 {
55 }
56}
57
59{
60 m_unlock = false ;
61}
62
64{
65 return m_id ;
66}
67
68std::string GStore::StoredFile::location() const
69{
70 return cpath().str() ;
71}
72
73G::Path GStore::StoredFile::cpath() const
74{
75 return m_store.contentPath( m_id ) ;
76}
77
78G::Path GStore::StoredFile::epath( State state ) const
79{
80 return m_store.envelopePath( m_id , state ) ;
81}
82
83GStore::MessageStore::BodyType GStore::StoredFile::bodyType() const
84{
85 return m_env.body_type ;
86}
87
88void GStore::StoredFile::close()
89{
90 m_content.reset() ;
91}
92
93std::string GStore::StoredFile::reopen()
94{
95 std::string reason = "error" ;
96 if( !readEnvelope(reason) || !openContent(reason) )
97 return reason ;
98 else
99 return {} ;
100}
101
102bool GStore::StoredFile::readEnvelope( std::string & reason )
103{
104 try
105 {
106 m_env = FileStore::readEnvelope( epath(m_state) ) ;
107 return true ;
108 }
109 catch( std::exception & e ) // invalid file in store
110 {
111 reason = e.what() ;
112 return false ;
113 }
114}
115
116bool GStore::StoredFile::openContent( std::string & reason )
117{
118 try
119 {
120 G_DEBUG( "GStore::FileStore::openContent: reading content [" << cpath().basename() << "]" ) ;
121 auto stream = std::make_unique<Stream>( cpath() ) ;
122 if( !stream )
123 {
124 reason = "cannot open content file" ;
125 return false ;
126 }
127 stream->exceptions( std::ios_base::badbit ) ;
128 m_content = std::move( stream ) ;
129 return true ;
130 }
131 catch( std::exception & e ) // invalid file in store
132 {
133 G_DEBUG( "GStore::FileStore: exception: " << e.what() ) ;
134 reason = e.what() ;
135 return false ;
136 }
137}
138
139const std::string & GStore::StoredFile::eol() const
140{
141 static const std::string crlf( "\r\n" ) ;
142 static const std::string lf( "\n" ) ;
143 return m_env.crlf ? crlf : lf ;
144}
145
147{
148 G_DEBUG( "GStore::StoredFile::lock: locking envelope [" << epath(m_state).basename() << "]" ) ;
149 const G::Path src = epath( m_state ) ;
150 const G::Path dst = epath( State::Locked ) ;
151 bool ok = FileOp::rename( src , dst ) ;
152 if( ok )
153 {
154 m_state = State::Locked ;
155 m_unlock = true ;
156 }
157 else
158 {
159 G_DEBUG( "GStore::StoredFile::lock: failed to lock envelope "
160 "[" << src.basename() << "] (" << G::Process::strerror(FileOp::errno_()) << ")" ) ;
161 }
162 static_cast<MessageStore&>(m_store).updated() ;
163 return ok ;
164}
165
166void GStore::StoredFile::editRecipients( const G::StringArray & recipients )
167{
168 editEnvelope( [&recipients](Envelope &env_){env_.to_remote=recipients;} ) ;
169}
170
171void GStore::StoredFile::editEnvelope( std::function<void(Envelope&)> edit_fn , std::istream * headers_stream )
172{
173 // re-read the envelope (disregard m_env because we need the stream)
174 G::Path envelope_path = epath(m_state) ;
175 std::ifstream envelope_stream ;
176 Envelope envelope = FileStore::readEnvelope( envelope_path , &envelope_stream ) ;
177 envelope_stream.seekg( envelope.endpos ) ; // NOLINT narrowing
178
179 // edit the envelope as required
180 edit_fn( envelope ) ;
181
182 // write the envelope to a temporary file
183 const G::Path envelope_path_tmp = epath(m_state).str().append(".tmp") ;
184 G::ScopeExit file_cleanup( [envelope_path_tmp](){FileOp::remove(envelope_path_tmp);} ) ;
185 std::ofstream envelope_stream_tmp ;
186 envelope.endpos = writeEnvelopeImp( envelope , envelope_path_tmp , envelope_stream_tmp ) ;
187 envelope.crlf = true ;
188
189 // copy trailing headers (see StoredMessage::fail(), MessageDelivery::deliver(), etc)
190 GStore::Envelope::copyExtra( envelope_stream , envelope_stream_tmp ) ;
191
192 // add more trailing headers
193 if( headers_stream )
194 GStore::Envelope::copyExtra( *headers_stream , envelope_stream_tmp ) ;
195
196 // close
197 envelope_stream.close() ;
198 envelope_stream_tmp.close() ;
199 if( envelope_stream_tmp.fail() )
200 throw EditError( envelope_path.basename() ) ;
201
202 // commit
203 replaceEnvelope( envelope_path , envelope_path_tmp ) ;
204 file_cleanup.release() ;
205 m_env = envelope ;
206 static_cast<MessageStore&>(m_store).updated() ;
207}
208
209void GStore::StoredFile::replaceEnvelope( const G::Path & envelope_path , const G::Path & envelope_path_tmp )
210{
211 G_DEBUG( "GStore::StoredFile::replaceEnvelope: renaming envelope "
212 "[" << envelope_path.basename() << "] -> [" << envelope_path_tmp.basename() << "]" ) ;
213
214 if( !FileOp::renameOnto( envelope_path_tmp , envelope_path ) )
215 throw EditError( "renaming" , envelope_path.basename() , G::Process::strerror(FileOp::errno_()) ) ;
216}
217
218std::size_t GStore::StoredFile::writeEnvelopeImp( const Envelope & envelope , const G::Path & envelope_path , std::ofstream & stream )
219{
220 if( !FileOp::openOut( stream , envelope_path ) )
221 throw EditError( "creating" , envelope_path.basename() ) ;
222
223 std::size_t endpos = GStore::Envelope::write( stream , envelope ) ;
224 if( endpos == 0U )
225 throw EditError( envelope_path.basename() ) ;
226 return endpos ;
227}
228
229void GStore::StoredFile::fail( const std::string & reason , int reason_code )
230{
231 if( FileOp::exists( epath(m_state) ) ) // client-side preprocessing may have removed it
232 {
233 addReason( epath(m_state) , reason , reason_code ) ;
234
235 G::Path bad_path = epath( State::Bad ) ;
236 G_LOG_S( "GStore::StoredFile::fail: failing envelope [" << epath(m_state).basename() << "] "
237 << "-> [" << bad_path.basename() << "]" ) ;
238
239 FileOp::rename( epath(m_state) , bad_path ) ;
240 m_state = State::Bad ;
241 }
242 else
243 {
244 G_DEBUG( "GStore::StoredFile::fail: cannot fail envelope [" << epath(m_state).basename() << "]" ) ;
245 }
246 m_unlock = false ;
247 static_cast<MessageStore&>(m_store).updated() ;
248}
249
250void GStore::StoredFile::addReason( const G::Path & path , const std::string & reason , int reason_code ) const
251{
252 std::ofstream stream ;
253 if( !FileOp::openAppend( stream , path ) )
254 G_ERROR( "GStore::StoredFile::addReason: cannot re-open envelope file to append the failure reason: "
255 << "[" << path.basename() << "] (" << G::Process::strerror(FileOp::errno_()) << ")" ) ;
256
257 stream << FileStore::x() << "Reason: " << G::Str::toPrintableAscii(reason) << eol() ;
258 stream << FileStore::x() << "ReasonCode:" ; if( reason_code ) stream << " " << reason_code ; stream << eol() ;
259}
260
261void GStore::StoredFile::destroy()
262{
263 G_LOG( "GStore::StoredFile::destroy: deleting envelope [" << epath(m_state).basename() << "]" ) ;
264 if( !FileOp::remove( epath(m_state) ) )
265 G_WARNING( "GStore::StoredFile::destroy: failed to delete envelope file "
266 << "[" << epath(m_state).basename() << "] (" << G::Process::strerror(FileOp::errno_()) << ")" ) ;
267
268 G_LOG( "GStore::StoredFile::destroy: deleting content [" << cpath().basename() << "]" ) ;
269 m_content.reset() ; // close it before deleting
270 if( !FileOp::remove( cpath() ) )
271 G_WARNING( "GStore::StoredFile::destroy: failed to delete content file "
272 << "[" << cpath().basename() << "] (" << G::Process::strerror(FileOp::errno_()) << "]" ) ;
273
274 m_unlock = false ;
275 static_cast<MessageStore&>(m_store).updated() ;
276}
277
278std::string GStore::StoredFile::from() const
279{
280 return m_env.from ;
281}
282
283std::string GStore::StoredFile::to( std::size_t i ) const
284{
285 return i < m_env.to_remote.size() ? m_env.to_remote[i] : std::string() ;
286}
287
288std::size_t GStore::StoredFile::toCount() const
289{
290 return m_env.to_remote.size() ;
291}
292
293std::size_t GStore::StoredFile::contentSize() const
294{
295 std::streamoff size = m_content ? m_content->size() : 0U ;
296 if( static_cast<std::make_unsigned<std::streamoff>::type>(size) > std::numeric_limits<std::size_t>::max() )
297 throw SizeError( "too big" ) ;
298 return static_cast<std::size_t>(size) ;
299}
300
301std::istream & GStore::StoredFile::contentStream()
302{
303 if( m_content == nullptr )
304 m_content = std::make_unique<Stream>() ;
305 return *m_content ;
306}
307
308std::string GStore::StoredFile::authentication() const
309{
310 return m_env.authentication ;
311}
312
313std::string GStore::StoredFile::fromAuthIn() const
314{
315 return m_env.from_auth_in ;
316}
317
318std::string GStore::StoredFile::forwardTo() const
319{
320 return m_env.forward_to ;
321}
322
323std::string GStore::StoredFile::forwardToAddress() const
324{
325 return m_env.forward_to_address ;
326}
327
328std::string GStore::StoredFile::clientAccountSelector() const
329{
330 return m_env.client_account_selector ;
331}
332
333bool GStore::StoredFile::utf8Mailboxes() const
334{
335 return m_env.utf8_mailboxes ;
336}
337
338std::string GStore::StoredFile::fromAuthOut() const
339{
340 return m_env.from_auth_out ;
341}
342
343// ==
344
345GStore::StoredFile::Stream::Stream() :
346 StreamBuf(&G::File::read,&G::File::write,&G::File::close),
347 std::istream(static_cast<StreamBuf*>(this))
348{
349}
350
351GStore::StoredFile::Stream::Stream( const G::Path & path ) :
352 StreamBuf(&G::File::read,&G::File::write,&G::File::close),
353 std::istream(static_cast<StreamBuf*>(this))
354{
355 open( path ) ;
356}
357
358void GStore::StoredFile::Stream::open( const G::Path & path )
359{
360 // (because on windows we want _O_NOINHERIT and _SH_DENYNO)
361 int fd = FileOp::fdopen( path ) ;
362 if( fd >= 0 )
363 {
364 StreamBuf::open( fd ) ;
365 clear() ;
366 }
367 else
368 {
369 clear( std::ios_base::failbit ) ;
370 }
371}
372
373std::streamoff GStore::StoredFile::Stream::size() const
374{
375 // (G::fbuf is not seekable)
376 int fd = file() ;
377 auto old = G::File::seek( fd , 0 , G::File::Seek::Current ) ;
378 auto end_ = G::File::seek( fd , 0 , G::File::Seek::End ) ;
379 auto new_ = G::File::seek( fd , old , G::File::Seek::Start ) ;
380 if( old < 0 || old != new_ )
381 throw StoredFile::SizeError() ;
382 return end_ ;
383}
384
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
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:56
static std::string x()
Returns the prefix for envelope header lines.
Definition: gfilestore.cpp:129
static Envelope readEnvelope(const G::Path &, std::ifstream *=nullptr)
Used by FileStore sibling classes to read an envelope file.
Definition: gfilestore.cpp:308
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
A class which allows SMTP messages to be stored and retrieved.
Definition: gmessagestore.h:73
bool openContent(std::string &reason)
Opens the content file. Returns false on error.
bool readEnvelope(std::string &reason)
Reads the envelope.
StoredFile(FileStore &store, const MessageId &, State=State::Normal)
Constructor.
Definition: gstoredfile.cpp:35
bool lock()
Locks the file by renaming the envelope file.
void editEnvelope(std::function< void(Envelope &)>, std::istream *headers=nullptr)
Edits the envelope and updates it in the file store.
void noUnlock()
Disable unlocking in the destructor.
Definition: gstoredfile.cpp:58
~StoredFile() override
Destructor.
Definition: gstoredfile.cpp:42
MessageId id() const override
Override from GStore::StoredMessage.
Definition: gstoredfile.cpp:63
static std::streamoff seek(int fd, std::streamoff offset, Seek) noexcept
Does ::lseek() or equivalent.
Definition: gfile_unix.cpp:472
A Path object represents a file system path.
Definition: gpath.h:82
std::string basename() const
Returns the rightmost part of the path, ignoring "." parts.
Definition: gpath.cpp:339
std::string str() const
Returns the path string.
Definition: gpath.h:243
static std::string strerror(int errno_)
Translates an 'errno' value into a meaningful diagnostic string.
A class that calls an exit function at the end of its scope.
Definition: gscope.h:47
static std::string toPrintableAscii(const std::string &in, char escape='\\')
Returns a 7-bit printable representation of the given input string.
Definition: gstr.cpp:931
void open(T file)
Installs the given file descriptor.
Definition: gfbuf.h:153
Low-level classes.
Definition: garg.h:36
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
STL namespace.