E-MailRelay
gexecutablefilter.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 gexecutablefilter.cpp
19///
20
21#include "gdef.h"
22#include "gprocess.h"
23#include "gnewprocess.h"
24#include "gexecutablecommand.h"
25#include "gexecutablefilter.h"
26#include "gstr.h"
27#include "groot.h"
28#include "glog.h"
29#include "gassert.h"
30#include <tuple>
31
33 GStore::FileStore & file_store , Filter::Type filter_type ,
34 const Filter::Config & filter_config ,
35 const std::string & path ) :
36 m_file_store(file_store) ,
37 m_filter_type(filter_type) ,
38 m_exit(0,filter_type) ,
39 m_path(path) ,
40 m_timeout(filter_config.timeout) ,
41 m_timer(*this,&ExecutableFilter::onTimeout,es) ,
42 m_task(*this,es,"<<filter exec error: __strerror__>>",G::Root::nobody())
43{
44}
45
47= default;
48
49bool GFilters::ExecutableFilter::quiet() const
50{
51 return false ;
52}
53
54std::string GFilters::ExecutableFilter::id() const
55{
56 return m_path.basename() ; // was .str()
57}
58
59bool GFilters::ExecutableFilter::special() const
60{
61 return m_exit.special ;
62}
63
64GSmtp::Filter::Result GFilters::ExecutableFilter::result() const
65{
66 return m_exit.result ;
67}
68
69std::string GFilters::ExecutableFilter::response() const
70{
71 G_ASSERT( m_exit.ok() || m_exit.abandon() || !m_response.empty() ) ;
72 if( m_exit.ok() || m_exit.abandon() )
73 return {} ;
74 else
75 return m_response ;
76}
77
78int GFilters::ExecutableFilter::responseCode() const
79{
80 return m_response_code ;
81}
82
83std::string GFilters::ExecutableFilter::reason() const
84{
85 G_ASSERT( m_exit.ok() || m_exit.abandon() || !m_reason.empty() ) ;
86 if( m_exit.ok() || m_exit.abandon() )
87 return {} ;
88 else
89 return m_reason ;
90}
91
92void GFilters::ExecutableFilter::start( const GStore::MessageId & message_id )
93{
94 GStore::FileStore::State state = m_filter_type == Filter::Type::server ?
95 GStore::FileStore::State::New : GStore::FileStore::State::Locked ;
96 G::Path cpath = m_file_store.contentPath( message_id ) ;
97 G::Path epath = m_file_store.envelopePath( message_id , state ) ;
98
99 G::StringArray args ;
100 args.push_back( cpath.str() ) ;
101 args.push_back( epath.str() ) ;
102 G::ExecutableCommand commandline( m_path.str() , args ) ;
103 G_LOG( "GFilters::ExecutableFilter::start: " << prefix() << ": [" << message_id.str() << "]: running " << m_path ) ;
104 m_task.start( commandline ) ;
105
106 if( m_timeout )
107 m_timer.startTimer( m_timeout ) ;
108}
109
110void GFilters::ExecutableFilter::onTimeout()
111{
112 G_WARNING( "GFilters::ExecutableFilter::onTimeout: " << prefix() << " timed out after " << m_timeout << "s" ) ;
113 m_task.stop() ;
114 m_exit = Exit( 1 , m_filter_type ) ;
115 G_ASSERT( m_exit.fail() ) ;
116 m_response = "error" ;
117 m_response_code = 0 ;
118 m_reason = "timeout" ;
119 m_done_signal.emit( static_cast<int>(m_exit.result) ) ;
120}
121
122void GFilters::ExecutableFilter::onTaskDone( int exit_code , const std::string & output )
123{
124 m_timer.cancelTimer() ;
125
126 // search the output for diagnostics
127 std::tie(m_response,m_response_code,m_reason) = parseOutput( output , "rejected" ) ;
128 if( m_response.find("filter exec error:") == 0U ) // see ctor
129 {
130 m_reason = m_response ; // first
131 m_response = "rejected" ; // second
132 m_response_code = 0 ;
133 }
134
135 m_exit = Exit( exit_code , m_filter_type ) ;
136 if( !m_exit.ok() )
137 {
138 G_WARNING( "GFilters::ExecutableFilter::onTaskDone: " << prefix() << " failed: "
139 << "exit code " << exit_code << ": [" << m_response << "]"
140 << (m_response_code?("("+G::Str::fromInt(m_response_code)+")"):"") ) ;
141 }
142
143 // callback
144 m_done_signal.emit( static_cast<int>(m_exit.result) ) ;
145}
146
147std::tuple<std::string,int,std::string> GFilters::ExecutableFilter::parseOutput( std::string s ,
148 const std::string & default_ )
149{
150 G_DEBUG( "GFilters::ExecutableFilter::parseOutput: in: \"" << G::Str::printable(s) << "\"" ) ;
151
152 static constexpr auto start_1 = "<<"_sv ;
153 static constexpr auto end_1 = ">>"_sv ;
154 static constexpr auto start_2 = "[["_sv ;
155 static constexpr auto end_2 = "]]"_sv ;
156
157 G::StringArray lines ;
158 while( G::Str::replaceAll( s , "\r\n" , "\n" ) ) {;}
159 G::Str::replaceAll( s , "\r" , "\n" ) ;
160 G::Str::splitIntoFields( s , lines , '\n' ) ;
161
162 for( auto p = lines.begin() ; p != lines.end() ; )
163 {
164 const std::string & line = *p ;
165 std::size_t pos_start = line.find( start_1.data() , 0U , start_1.size() ) ;
166 std::size_t pos_end = line.find( end_1.data() , 0U , end_1.size() ) ;
167 if( pos_start != 0U )
168 {
169 pos_start = line.find( start_2.data() , 0U , start_2.size() ) ;
170 pos_end = line.find( end_2.data() , 0U , end_2.size() ) ;
171 }
172 if( pos_start == 0U && pos_end != std::string::npos )
173 {
174 *p++ = G::Str::printable( line.substr(2U,pos_end-2U) ) ;
175 }
176 else
177 {
178 p = lines.erase( p ) ;
179 }
180 }
181
182 G_DEBUG( "GFilters::ExecutableFilter::parseOutput: out: [" << G::Str::join("|",lines) << "]" ) ;
183
184 std::string response = ( !lines.empty() && !lines.at(0U).empty() ) ? lines.at(0U) : default_ ;
185 int response_code = 0 ;
186 if( response.size() >= 3U &&
187 ( response[0] == '4' || response[0] == '5' ) &&
188 ( response[1] >= '0' && response[1] <= '9' ) &&
189 ( response[2] >= '0' && response[2] <= '9' ) &&
190 ( response.size() == 3U || response[3] == ' ' || response[3] == '\t' ) )
191 {
192 response_code = G::Str::toInt( response.substr(0U,3U) ) ;
193 response.erase( 0U , response.size() == 3U ? 3U : 4U ) ;
194 }
195 std::string reason = ( lines.size() > 1U && !lines.at(1U).empty() ) ? lines.at(1U) : response ;
196 return { response , response_code , reason } ;
197}
198
199G::Slot::Signal<int> & GFilters::ExecutableFilter::doneSignal() noexcept
200{
201 return m_done_signal ;
202}
203
204void GFilters::ExecutableFilter::cancel()
205{
206 m_task.stop() ;
207 m_timer.cancelTimer() ;
208}
209
210std::string GFilters::ExecutableFilter::prefix() const
211{
212 return G::sv_to_string(strtype(m_filter_type)).append(" [").append(id()).append(1U,']') ;
213}
214
A Filter class that runs an external helper program.
ExecutableFilter(GNet::EventState, GStore::FileStore &, Filter::Type, const Filter::Config &, const std::string &path)
Constructor.
~ExecutableFilter() override
Destructor.
A lightweight object containing an ExceptionHandler pointer, optional ExceptionSource pointer and opt...
Definition: geventstate.h:131
A concrete implementation of the MessageStore interface dealing in paired flat files.
Definition: gfilestore.h:56
A somewhat opaque identifer for a GStore::MessageStore message id.
Definition: gmessagestore.h:43
std::string str() const
Returns the id string.
A structure representing an external program, holding a path and a set of arguments.
A Path object represents a file system path.
Definition: gpath.h:82
std::string str() const
Returns the path string.
Definition: gpath.h:243
static int toInt(std::string_view s)
Converts string 's' to an int.
Definition: gstr.cpp:538
static std::string join(std::string_view sep, const StringArray &strings)
Concatenates an array of strings with separators.
Definition: gstr.cpp:1221
static void splitIntoFields(std::string_view in, StringArray &out, char sep, char escape='\0', bool remove_escapes=true)
Splits the string into fields.
Definition: gstr.cpp:1191
static std::string fromInt(int i)
Converts int 'i' to a string.
Definition: gstr.h:594
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
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
Low-level classes.
Definition: garg.h:36
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstringarray.h:30