E-MailRelay

gfilterfactory.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 gfilterfactory.cpp
19///
20
21#include "gdef.h"
22#include "gfilterfactory.h"
23#include "gstringtoken.h"
24#include "gfilterchain.h"
25#include "gfilestore.h"
26#include "gnullfilter.h"
27#include "gfile.h"
28#include "gnetworkfilter.h"
29#include "gexecutablefilter.h"
30#include "gspamfilter.h"
31#include "gdeliveryfilter.h"
32#include "gmessageidfilter.h"
33#include "gcopyfilter.h"
34#include "gmxfilter.h"
35#include "gsplitfilter.h"
36#include "gstr.h"
37#include "grange.h"
38#include "gexception.h"
39#include <limits>
40
41 GFilters::FilterFactory::FilterFactory( GStore::FileStore & file_store ) :
42 m_file_store(file_store)
43{
44}
45
46 GFilters::FilterFactory::Spec GFilters::FilterFactory::parse( std::string_view spec_in ,
47 const G::Path & base_dir , const G::Path & app_dir , G::StringArray * warnings_p )
48{
49 std::string_view tail = G::Str::tailView( spec_in , ":" ) ;
50 Spec result ;
51 if( spec_in.empty() )
52 {
53 result = Spec( "exit" , "0" ) ;
54 }
55 else if( spec_in.find(',') != std::string::npos )
56 {
57 result = Spec( "chain" , "" ) ;
58 for( G::StringTokenView t( spec_in , "," ) ; t ; ++t )
59 result += parse( t() , base_dir , app_dir , warnings_p ) ; // one level of recursion
60 }
61 else if( G::Str::headMatch( spec_in ,"exit:" ) )
62 {
63 result = Spec( "exit" , tail ) ;
64 checkNumber( result ) ;
65 }
66 else if( G::Str::headMatch( spec_in , "sleepms:" ) )
67 {
68 result = Spec( "sleep" , tail ) ;
69 checkNumber( result ) ;
70 }
71 else if( G::Str::headMatch( spec_in , "net:" ) )
72 {
73 result = Spec( "net" , tail ) ;
74 checkNet( result ) ;
75 }
76 else if( G::Str::headMatch( spec_in , "spam:" ) )
77 {
78 result = Spec( "spam" , tail ) ;
79 checkNet( result ) ;
80 }
81 else if( G::Str::headMatch( spec_in , "spam-edit:" ) )
82 {
83 result = Spec( "spam-edit" , tail ) ;
84 checkNet( result ) ;
85 }
86 else if( G::Str::headMatch( spec_in , "deliver:" ) )
87 {
88 result = Spec( "deliver" , tail ) ;
89 checkRange( result ) ;
90 }
91 else if( G::Str::headMatch( spec_in , "copy:" ) )
92 {
93 result = Spec( "copy" , tail ) ;
94 }
95 else if( G::Str::headMatch( spec_in , "split:" ) )
96 {
97 result = Spec( "split" , tail ) ;
98 }
99 else if( G::Str::headMatch( spec_in , "mx:" ) )
100 {
101 result = Spec( "mx" , tail ) ;
102 }
103 else if( G::Str::headMatch( spec_in , "msgid:" ) )
104 {
105 result = Spec( "msgid" , tail ) ;
106 }
107 else if( G::Str::headMatch( spec_in , "file:" ) )
108 {
109 result = Spec( "file" , tail ) ;
110 fixFile( result , base_dir , app_dir ) ;
111 checkFile( result , warnings_p ) ;
112 }
113 else
114 {
115 result = Spec( "file" , spec_in ) ;
116 fixFile( result , base_dir , app_dir ) ;
117 checkFile( result , warnings_p ) ;
118 }
119 return result ;
120}
121
122std::unique_ptr<GSmtp::Filter> GFilters::FilterFactory::newFilter( GNet::EventState es ,
123 GSmtp::Filter::Type filter_type , const GSmtp::Filter::Config & filter_config ,
124 const FilterFactory::Spec & spec )
125{
126 if( spec.first == "chain" )
127 {
128 // (one level of recursion -- FilterChain::ctor calls newFilter())
129 return std::make_unique<FilterChain>( es , *this , filter_type , filter_config , spec ) ;
130 }
131 else if( spec.first == "spam" )
132 {
133 // "spam:" is read-only, not-always-pass
134 return std::make_unique<SpamFilter>( es , m_file_store , filter_type , filter_config , spec.second , true , false ) ;
135 }
136 else if( spec.first == "spam-edit" )
137 {
138 // "spam-edit:" is read-write, always-pass
139 return std::make_unique<SpamFilter>( es , m_file_store , filter_type , filter_config , spec.second , false , true ) ;
140 }
141 else if( spec.first == "net" )
142 {
143 return std::make_unique<NetworkFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
144 }
145 else if( spec.first == "exit" )
146 {
147 return std::make_unique<NullFilter>( es , m_file_store , filter_type , filter_config , G::Str::toUInt(spec.second) ) ;
148 }
149 else if( spec.first == "sleep" )
150 {
151 return std::make_unique<NullFilter>( es , m_file_store , filter_type , filter_config , G::TimeInterval(0U,G::Str::toUInt(spec.second)) ) ;
152 }
153 else if( spec.first == "file" )
154 {
155 return std::make_unique<ExecutableFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
156 }
157 else if( spec.first == "deliver" )
158 {
159 return std::make_unique<DeliveryFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
160 }
161 else if( spec.first == "copy" )
162 {
163 return std::make_unique<CopyFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
164 }
165 else if( spec.first == "split" )
166 {
167 return std::make_unique<SplitFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
168 }
169 else if( spec.first == "mx" )
170 {
171 return std::make_unique<MxFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
172 }
173 else if( spec.first == "msgid" )
174 {
175 return std::make_unique<MessageIdFilter>( es , m_file_store , filter_type , filter_config , spec.second ) ;
176 }
177 else
178 {
179 throw G::Exception( "invalid filter" , spec.second ) ;
180 }
181}
182
183 void GFilters::FilterFactory::checkNumber( Spec & result )
184{
185 if( result.second.empty() )
186 {
187 result.first.clear() ;
188 result.second = "numeric value missing" ;
189 }
190 else if( !G::Str::isUInt(result.second) )
191 {
192 result.first.clear() ;
193 result.second = "invalid numeric value: " + G::Str::printable(result.second) ;
194 }
195 else if( G::Str::toUInt(result.second) >= (std::numeric_limits<unsigned>::max()/1000U) )
196 {
197 result.first.clear() ;
198 result.second = "numeric value too big: " + G::Str::printable(result.second) ;
199 }
200}
201
202 void GFilters::FilterFactory::checkNet( Spec & result )
203{
204 try
205 {
206 GNet::Location::nosocks( result.second ) ;
207 }
208 catch( std::exception & e )
209 {
210 result.first.clear() ;
211 result.second = e.what() ;
212 }
213}
214
215 void GFilters::FilterFactory::checkRange( Spec & result )
216{
217 try
218 {
219 G::Range::check( result.second ) ;
220 }
221 catch( std::exception & e )
222 {
223 result.first.clear() ;
224 result.second = e.what() ;
225 }
226}
227
228 void GFilters::FilterFactory::fixFile( Spec & result ,
229 const G::Path & base_dir , const G::Path & app_dir )
230{
231 if( result.second.find("@app") == 0U && !app_dir.empty() )
232 {
233 G::Str::replace( result.second , "@app" , app_dir.str() ) ;
234 }
235 else if( G::Path(result.second).isRelative() && !base_dir.empty() )
236 {
237 result.second = (base_dir/result.second).str() ;
238 }
239}
240
241 void GFilters::FilterFactory::checkFile( Spec & result , G::StringArray * warnings_p )
242{
243 if( result.second.empty() )
244 {
245 result.first.clear() ;
246 result.second = "empty file path" ;
247 }
248 else if( warnings_p && !G::File::exists(result.second) )
249 {
250 warnings_p->push_back( std::string("filter program does not exist: ").append(result.second) ) ;
251 }
252 else if( warnings_p && G::File::isDirectory(result.second,std::nothrow) )
253 {
254 warnings_p->push_back( std::string("invalid program: ").append(result.second) ) ;
255 }
256}
257
GFilters::FilterFactory::newFilter
std::unique_ptr< GSmtp::Filter > newFilter(GNet::EventState, GSmtp::Filter::Type, const GSmtp::Filter::Config &, const Spec &) override
Returns a Filter on the heap.
Definition: gfilterfactory.cpp:122
GFilters::FilterFactory::parse
static Spec parse(std::string_view spec, const G::Path &base_dir={}, const G::Path &app_dir={}, G::StringArray *warnings_p=nullptr)
Parses and validates the filter specification string returning the type and value in a Spec tuple,...
Definition: gfilterfactory.cpp:46
GFilters::FilterFactory::FilterFactory
FilterFactory(GStore::FileStore &)
Constructor.
Definition: gfilterfactory.cpp:41
GNet::EventState
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
GNet::Location::nosocks
static Location nosocks(const std::string &spec, int family=AF_UNSPEC)
Factory function for a remote location but not allowing the extended syntax for socks.
Definition: glocation.cpp:77
GStore::FileStore
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:56
G::Exception
A general-purpose exception class derived from std::exception and containing an error message.
Definition: gexception.h:64
G::File::isDirectory
static bool isDirectory(const Path &path, std::nothrow_t)
Returns true if the path exists() and is a directory.
Definition: gfile.cpp:179
G::File::exists
static bool exists(const Path &file)
Returns true if the file (directory, device etc.) exists.
Definition: gfile.cpp:136
G::Path
A Path object represents a file system path.
Definition: gpath.h:82
G::Path::isRelative
bool isRelative() const noexcept
Returns true if the path is a relative path or empty().
Definition: gpath.cpp:334
G::Path::str
std::string str() const
Returns the path string.
Definition: gpath.h:243
G::Path::empty
bool empty() const noexcept
Returns true if the path is empty.
Definition: gpath.h:237
G::Str::isUInt
static bool isUInt(std::string_view s) noexcept
Returns true if the string can be converted into an unsigned integer without throwing an exception.
Definition: gstr.cpp:446
G::Str::headMatch
static bool headMatch(std::string_view in, std::string_view head) noexcept
Returns true if the string has the given start (or head is empty).
Definition: gstr.cpp:1359
G::Str::printable
static std::string printable(const std::string &in, char escape='\\')
Returns a printable representation of the given input string, using chacter code ranges 0x20 to 0x7e ...
Definition: gstr.cpp:913
G::Str::tailView
static std::string_view tailView(std::string_view in, std::size_t pos, std::string_view default_={}) noexcept
Like tail() but returning a view into the input string.
Definition: gstr.cpp:1337
G::Str::toUInt
static unsigned int toUInt(std::string_view s)
Converts string 's' to an unsigned int.
Definition: gstr.cpp:648
G::Str::replace
static bool replace(std::string &s, std::string_view from, std::string_view to, std::size_t *pos_p=nullptr)
A std::string_view overload.
Definition: gstr.cpp:226
G::StringTokenT
A zero-copy string token iterator where the token separators are runs of whitespace characters,...
Definition: gstringtoken.h:54
G::TimeInterval
An interval between two G::SystemTime values or two G::TimerTime values.
Definition: gdatetime.h:305
gcopyfilter.h
gdeliveryfilter.h
gexception.h
gexecutablefilter.h
gfile.h
gfilestore.h
gfilterchain.h
gfilterfactory.h
gmessageidfilter.h
gmxfilter.h
gnetworkfilter.h
gnullfilter.h
grange.h
gspamfilter.h
gsplitfilter.h
gstr.h
gstringtoken.h
G::StringArray
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30
GSmtp::FilterFactoryBase::Spec
Filter specification tuple for GSmtp::FilterFactoryBase::newFilter().
Definition: gfilterfactorybase.h:46
GSmtp::Filter::Config
Configuration passed to filter constructors.
Definition: gfilter.h:66