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