gspamclient.cpp
Go to the documentation of this file.
1 //
2 // Copyright (C) 2001-2013 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 // gspamclient.cpp
19 //
20 
21 #include "gdef.h"
22 #include "gnet.h"
23 #include "gsmtp.h"
24 #include "gstr.h"
25 #include "gfile.h"
26 #include "gspamclient.h"
27 #include "gassert.h"
28 
30  unsigned int connect_timeout , unsigned int response_timeout ) :
31  GNet::Client(resolver_info,connect_timeout,response_timeout,0U,"\n") ,
32  m_in_size(0UL) ,
33  m_in_lines(0UL) ,
34  m_out_size(0UL) ,
35  m_out_lines(0UL) ,
36  m_header_out_index(0U) ,
37  m_timer(*this,&SpamClient::onTimeout,*this)
38 {
39  G_DEBUG( "GSmtp::SpamClient::ctor: " << resolver_info.displayString() << ": "
40  << connect_timeout << " " << response_timeout ) ;
41 }
42 
44 {
45 }
46 
48 {
49  return m_in.get() != NULL || m_out.get() != NULL ;
50 }
51 
52 void GSmtp::SpamClient::onDelete( const std::string & , bool )
53 {
54 }
55 
56 void GSmtp::SpamClient::onDeleteImp( const std::string & reason , bool b )
57 {
58  // we have to override onDeleteImp() rather than onDelete() so that we
59  // can get in early enough to guarantee that every request gets a response
60 
61  if( !reason.empty() )
62  G_WARNING( "GSmtp::SpamClient::onDeleteImp: error: " << reason ) ;
63 
64  if( busy() )
65  {
66  m_in <<= 0 ;
67  m_out <<= 0 ;
68  eventSignal().emit( "spam" , m_out_size >= headerBodyLength() ? headerResult() : reason ) ;
69  }
70  Base::onDeleteImp( reason , b ) ; // use typedef because of ms compiler bug
71 }
72 
73 void GSmtp::SpamClient::request( const std::string & path )
74 {
75  G_DEBUG( "GSmtp::SpamClient::request: \"" << path << "\"" ) ;
76  if( busy() )
77  throw ProtocolError() ;
78 
79  m_path = path ;
80  m_in <<= new std::ifstream( path.c_str() , std::ios_base::binary | std::ios_base::in ) ;
81  m_in_lines = 0UL ;
82  m_in_size = 0UL ;
83 
84  std::string username = "spam" ; // TODO -- configurable username in SPAMC protocol? environment variable?
85  m_header_out.push_back( std::string() + "PROCESS SPAMC/1.4" ) ;
86  m_header_out.push_back( std::string() + "User: " + username ) ;
87  m_header_out.push_back( std::string() + "Content-length: " + G::File::sizeString(m_path) ) ;
88  m_header_out.push_back( std::string() ) ;
89  m_header_out_index = 0U ;
90 
91  m_timer.startTimer( 0U ) ;
92  m_header_in.clear() ;
93 }
94 
96 {
97  G_DEBUG( "GSmtp::SpamClient::onConnect" ) ;
98  if( busy() )
99  sendContent() ;
100 }
101 
102 void GSmtp::SpamClient::onTimeout()
103 {
104  if( connected() )
105  sendContent() ;
106 }
107 
109 {
110  sendContent() ;
111 }
112 
113 void GSmtp::SpamClient::sendContent()
114 {
115  std::string line ;
116  while( nextContentLine(line) )
117  {
118  if( !send( line + "\r\n" ) )
119  break ;
120  }
121 }
122 
123 bool GSmtp::SpamClient::nextContentLine( std::string & line )
124 {
125  bool ok = false ;
126  if( m_in.get() != NULL )
127  {
128  if( m_header_out_index < m_header_out.size() )
129  {
130  line = m_header_out[m_header_out_index++] ;
131  G_LOG( "GSmtp::SpamClient::sendContent: spam>>: \"" << G::Str::printable(line) << "\"" ) ;
132  ok = true ;
133  }
134  else
135  {
136  std::istream & stream = *(m_in.get()) ;
137  if( stream.good() )
138  {
139  G::Str::readLineFrom( stream , "\r\n" , line ) ;
140  ok = !! stream ;
141  }
142  if( ok )
143  {
144  m_in_lines++ ;
145  m_in_size += ( line.length() + 2U ) ;
146  }
147  else
148  {
149  G_LOG( "GSmtp::SpamClient::addBody: spam>>: [" << m_in_lines
150  << " lines of body text, " << m_in_size << " bytes]" ) ;
151 
152  // stop sending, start receiving
153  turnRound() ;
154  }
155  }
156  }
157  return ok ;
158 }
159 
160 void GSmtp::SpamClient::turnRound()
161 {
162  // send eof
163  socket().shutdown() ;
164 
165  // close content file for reading, reopen for writing (keeping busy() true)
166  m_in <<= 0 ;
167  m_out <<= new std::ofstream( m_path.c_str() , std::ios_base::binary | std::ios_base::out | std::ios_base::trunc ) ;
168  m_out_size = 0UL ;
169  m_out_lines = 0UL ;
170 }
171 
172 void GSmtp::SpamClient::onSecure( const std::string & )
173 {
174 }
175 
176 bool GSmtp::SpamClient::onReceive( const std::string & line )
177 {
178  if( m_in.get() != NULL )
179  throw ProtocolError( G::Str::printable(line) ) ; // the spamd has sent a response too early
180 
181  if( ! haveCompleteHeader() )
182  addHeader( line ) ;
183  else
184  addBody( line ) ;
185 
186  return true ;
187 }
188 
189 bool GSmtp::SpamClient::haveCompleteHeader() const
190 {
191  return m_header_in.size() != 0U && m_header_in.back().empty() ;
192 }
193 
194 void GSmtp::SpamClient::addHeader( const std::string & line )
195 {
196  G_LOG( "GSmtp::SpamClient::onReceive: spam<<: \"" << G::Str::printable(G::Str::trimmed(line,"\r\n")) << "\"" ) ;
197  m_header_in.push_back( G::Str::trimmed(line,"\r\n") ) ;
198  m_n = 0UL ;
199 }
200 
201 void GSmtp::SpamClient::addBody( const std::string & line )
202 {
203  if( m_out.get() != NULL && m_out_size < headerBodyLength() )
204  {
205  std::ostream & out = *m_out.get() ;
206  out << line << "\n" ;
207  m_out_size += line.length() + 1U ;
208  m_out_lines++ ;
209 
210  if( m_out_size >= headerBodyLength() )
211  G_LOG( "GSmtp::SpamClient::addBody: spam<<: [" << m_out_lines
212  << " lines of body text, " << m_out_size << " bytes]" ) ;
213  }
214 }
215 
216 std::string GSmtp::SpamClient::headerResult() const
217 {
218  return G::Str::lower( part(headerLine("Spam:"),1U,"true") ) == "true" ? "marked as spam" : std::string() ;
219 }
220 
221 std::string GSmtp::SpamClient::part( const std::string & line , unsigned int i , const std::string & default_ ) const
222 {
223  StringArray part ;
224  G::Str::splitIntoTokens( line , part , "\t :" ) ;
225  return part.size() > i ? part[i] : default_ ;
226 }
227 
228 unsigned long GSmtp::SpamClient::headerBodyLength() const
229 {
230  if( m_n == 0UL )
231  (const_cast<SpamClient*>(this))->m_n = G::Str::toULong( part(headerLine("Content-length:"),1U,"0") ) ;
232  return m_n ;
233 }
234 
235 std::string GSmtp::SpamClient::headerLine( const std::string & key , const std::string & default_ ) const
236 {
237  for( StringArray::const_iterator p = m_header_in.begin() ; p != m_header_in.end() ; ++p )
238  {
239  if( (*p).find(key) == 0U )
240  return *p ;
241  }
242  return default_ ;
243 }
244 
bool busy() const
Returns true after request() and before the subsequent event signal.
Definition: gspamclient.cpp:47
static std::string printable(const std::string &in, char escape= '\\')
Returns a printable represention of the given input string.
Definition: gstr.cpp:507
Network classes.
virtual bool onReceive(const std::string &)
Final override from GNet::Client.
std::vector< std::string > StringArray
A std::vector of std::strings.
Definition: gstrings.h:44
A class that holds a host/service name pair and optionally the results of a name-to-address lookup...
Definition: gresolverinfo.h:48
static void splitIntoTokens(const std::string &in, Strings &out, const std::string &ws)
Splits the string into 'ws'-delimited tokens.
Definition: gstr.cpp:714
virtual void onDeleteImp(const std::string &, bool)
Final override from GNet::Client.
Definition: gspamclient.cpp:56
static std::string lower(const std::string &s)
Returns a copy of 's' in which all uppercase characters have been replaced by lowercase characters...
Definition: gstr.cpp:408
virtual void onSendComplete()
Final override from GNet::BufferedClient.
void request(const std::string &)
Issues a request.
Definition: gspamclient.cpp:73
virtual ~SpamClient()
Destructor.
Definition: gspamclient.cpp:43
#define G_LOG(expr)
Definition: glog.h:98
virtual void onConnect()
Final override from GNet::SimpleClient.
Definition: gspamclient.cpp:95
virtual void onDelete(const std::string &, bool)
Final override from GNet::HeapClient.
Definition: gspamclient.cpp:52
A client class that interacts with a remote process using a protocol somewhat similar to the spamassa...
Definition: gspamclient.h:47
virtual void onSecure(const std::string &)
Final override from GNet::SocketProtocol.
static std::string trimmed(const std::string &s, const std::string &ws)
Returns a trim()med version of s.
Definition: gstr.cpp:138
static std::string readLineFrom(std::istream &stream, const std::string &eol=std::string())
Reads a line from the stream using the given line terminator.
Definition: gstr.cpp:536
#define G_DEBUG(expr)
Definition: glog.h:95
A class which acts as an SMTP client, extracting messages from a message store and forwarding them to...
Definition: gsmtpclient.h:55
std::string displayString(bool simple=false) const
Returns a string representation for logging and debug.
static std::string sizeString(const Path &file)
Returns the file's size in string format.
Definition: gfile_unix.cpp:67
SpamClient(const GNet::ResolverInfo &host_and_service, unsigned int connect_timeout, unsigned int response_timeout)
Constructor.
Definition: gspamclient.cpp:29
static unsigned long toULong(const std::string &s, bool limited=false)
Converts string 's' to an unsigned long.
Definition: gstr.cpp:362
#define G_WARNING(expr)
Definition: glog.h:107