E-MailRelay
glinebuffer.h
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 glinebuffer.h
19///
20
21#ifndef G_NET_LINE_BUFFER_H
22#define G_NET_LINE_BUFFER_H
23
24#include "gdef.h"
25#include "gexception.h"
26#include "glinestore.h"
27#include "gstringview.h"
28#include "gcall.h"
29#include <functional>
30#include <string>
31
32namespace GNet
33{
34 class LineBuffer ;
35 class LineBufferIterator ;
36 class LineBufferState ;
37}
38
39//| \class GNet::LineBuffer
40/// A class that does line buffering, supporting auto-detection of
41/// line endings and fixed-size block extraction. Raw data is
42/// added, and newline-delimited lines are extracted, optionally
43/// via an iterator.
44///
45/// Usage:
46/// \code
47/// {
48/// GNet::LineBuffer buffer( (GNet::LineBuffer::Config()) ) ;
49/// buffer.add("abc") ;
50/// buffer.add("def\nABC\nDE") ;
51/// buffer.add("F\n") ;
52///
53/// while( buffer.more() )
54/// cout << std::string(buffer.data(),buffer.size()) << endl ;
55/// }
56/// \endcode
57///
58/// A callback mechanism (apply()) can be used that combines
59/// adding and extracting. This has the benefit of less data
60/// copying, especially if the caller allows incomplete line
61/// fragments to be delivered.
62///
63/// \code
64/// {
65/// struct Callback { bool operator()( const char * , std::size_t size , std::size_t eolsize , std::size_t linesize , char c0 ) {...} } callback ;
66/// GNet::LineBuffer buffer( (GNet::LineBuffer::Config()) ) ;
67/// for( std::string s : std::vector<std::string> { "foo" , "bar\r" , "\n" } )
68/// buffer.apply( s.data() , s.size() , callback , true ) ;
69/// }
70/// \endcode
71///
72/// The expect() method allows for handling fixed-size blocks
73/// that are not line-structured (think http content-length).
74/// While the expect() value is in force the line buffer is in
75/// a transparent mode, delivering data() with a zero eolsize().
76///
77/// Note that a line buffer that is configured as 'transparent'
78/// at run-time is essentially zero cost when using apply()
79/// with the 'fragments' option: data passes directly from
80/// apply() to the callback.
81///
83{
84public:
85 G_EXCEPTION( ErrorOverflow , tx("line buffer overflow") ) ;
86 using SinkArgs = std::tuple<const char*,std::size_t,std::size_t,std::size_t,char,bool> ;
87 using SinkFn = std::function<bool(const SinkArgs&)> ;
88 using FragmentsFn = std::function<bool()> ;
89
90 struct Config /// A configuration structure for GNet::LineBuffer.
91 {
92 static constexpr std::size_t inf = ~(std::size_t(0)) ;
93 static_assert( (inf+1U) == 0U , "" ) ;
94 std::string m_eol {'\n'} ; // eol or autodetect if empty
95 std::size_t m_warn {0U} ; // line-length warning level
96 std::size_t m_fmin {0U} ; // minimum fragment size
97 std::size_t m_expect {0U} ; // expected size of binary chunk
98
99 const std::string & eol() const noexcept ;
100 std::size_t warn() const noexcept ;
101 std::size_t fmin() const noexcept ;
102 std::size_t expect() const noexcept ;
103
104 Config & set_eol( const std::string & ) ;
105 Config & set_warn( std::size_t ) noexcept ;
106 Config & set_fmin( std::size_t ) noexcept ;
107 Config & set_expect( std::size_t ) noexcept ;
108
109 static Config transparent() ;
110 static Config http() ;
111 static Config smtp() ;
112 static Config pop() ;
113 static Config crlf() ;
114 static Config newline() ;
115 static Config autodetect() ;
116 } ;
117
118 explicit LineBuffer( const Config & ) ;
119 ///< Constructor.
120
121 void clear() ;
122 ///< Clears the internal data.
123
124 void add( const std::string & data ) ;
125 ///< Adds a data segment.
126
127 void add( const char * data , std::size_t size ) ;
128 ///< Adds a data segment by copying. Does nothing if data is
129 ///< null or size is zero.
130 ///< See also apply().
131
132 void expect( std::size_t n ) ;
133 ///< Requests that the next 'n' bytes are extracted in one
134 ///< contiguous block, without regard to line endings. Once
135 ///< the expected number of bytes have been extracted the
136 ///< line buffering returns to normal.
137 ///<
138 ///< This method can be used during a data-transfer phase to
139 ///< obtain a chunk of data of known size, as in http with a
140 ///< known content-length.
141 ///<
142 ///< A parameter value of zero switches back to normal line
143 ///< buffering immediately.
144 ///<
145 ///< A parameter value of std::size_t(-1) can be used to represent
146 ///< an infinite expectation that is never fully satisfied.
147 ///< This is only sensible when extracting fragments with
148 ///< apply(), and it results in full transparency.
149
150 std::string eol() const ;
151 ///< Returns the end-of-line string as passed in to the
152 ///< constructor, or as auto-detected. Returns the empty
153 ///< string if auto-detection by iteration has not yet
154 ///< occurred.
155
156 template <typename Tfn>
157 void apply( const char * data , std::size_t data_size , Tfn sink_fn , bool fragments = false ) ;
158 ///< Adds the data and passes complete lines to the sink
159 ///< function with line-data, line-size, eol-size and
160 ///< c0 parameters. Stops if the sink function returns false.
161 ///< The data can be nullptr in order to flush any existing
162 ///< data to the sink function. This method is zero-copy if
163 ///< the supplied data contains complete lines or if allowing
164 ///< line fragments.
165 ///<
166 ///< \code
167 ///< void Foo::onData( const char * data , std::size_t size )
168 ///< {
169 ///< apply( data , size , onLine , false ) ;
170 ///< }
171 ///< bool onLine( const char * data , std::size_t size , std::size_t , std::size_t , char )
172 ///< {
173 ///< process( std::string(data,size) ) ;
174 ///< }
175 ///< \endcode
176
177 template <typename Tsink, typename Tmemfun>
178 void apply( Tsink sink_p , Tmemfun sink_memfun , const char * data , std::size_t data_size ,
179 bool fragments = false ) ;
180 ///< Overload that calls out to a member function.
181 ///<
182 ///< \code
183 ///< void Foo::onData( const char * data , std::size_t size )
184 ///< {
185 ///< apply( this , &Foo::onLine , data , size , false ) ;
186 ///< }
187 ///< bool Foo::onLine( const char * data , std::size_t size , std::size_t , std::size_t , char )
188 ///< {
189 ///< process( std::string(data,size) ) ;
190 ///< }
191 ///< \endcode
192
193 template <typename Tsink, typename Tmemfun, typename Tmemfun2>
194 void apply( Tsink sink_p , Tmemfun sink_memfun , const char * data , std::size_t data_size ,
195 Tmemfun2 fragments_memfun ) ;
196 ///< Overload where the 'fragments' flag comes from calling a member
197 ///< function on the sink object, allowing the flag to change
198 ///< dynamically as each line is delivered.
199
200 bool apply( SinkFn sink_fn , G::string_view data , FragmentsFn fragments_fn ) ;
201 ///< Overload for std::function.
202 ///<
203 ///< This overload provides extra parameter 'more' to the
204 ///< sink function that is true if there is another complete
205 ///< line buffered-up after the current one (and eolsize is
206 ///< non-zero).
207 ///<
208 ///< Returns false iff the sink function returned false.
209
210 template <typename Tfn>
211 void apply( const std::string & , Tfn sink_fn , bool fragments = false ) ;
212 ///< Overload taking a string as its data input, used in
213 ///< testing.
214
215 bool more( bool fragments = false ) ;
216 ///< Returns true if there is more data() to be had.
217 ///< This advances the implied iterator.
218 ///<
219 ///< If the fragments parameter is true then incomplete
220 ///< lines will be returned (with eolsize zero), but
221 ///< those fragments will explicitly exclude anything
222 ///< that might be part of the line ending.
223
224 const char * data() const ;
225 ///< Returns a pointer for the current line, expect()ed
226 ///< fixed-size block, or line fragment. This includes
227 ///< eolsize() bytes of line-ending.
228 ///< Precondition: more()
229
230 std::size_t size() const ;
231 ///< Returns the size of the current data(), excluding the
232 ///< line ending.
233 ///< Precondition: more()
234
235 std::size_t eolsize() const ;
236 ///< Returns the size of line-ending associated with the
237 ///< current data(). This will be zero for a fixed-size
238 ///< block or non-terminal line fragment. (It will never
239 ///< be zero as a result of auto-detection because the
240 ///< precondition means that auto-detection has already
241 ///< happened.)
242 ///< Precondition: more()
243
244 std::size_t linesize() const ;
245 ///< Returns the current size of all the line fragments
246 ///< making up the current line.
247 ///< Precondition: more()
248
249 char c0() const ;
250 ///< Returns the first character of the current line.
251 ///< This can be useful in the case of line fragments
252 ///< where treatment of the fragment depends on the
253 ///< first character of the complete line (as in SMTP
254 ///< data transfer).
255 ///< Precondition: linesize() != 0U
256
257 bool transparent() const ;
258 ///< Returns true if the current expect() value is
259 ///< infinite.
260
261 LineBufferState state() const ;
262 ///< Returns information about the current state of the
263 ///< line-buffer.
264
265 std::size_t buffersize() const ;
266 ///< Returns the total number of bytes buffered up.
267
268 bool peekmore() const ;
269 ///< Returns true if there is a line available after the
270 ///< current line or expect()ation.
271 ///< Precondition: more()
272
273public:
274 void extensionStart( const char * , std::size_t ) ;
275 ///< A pseudo-private method used by the implementation
276 ///< of the apply() method template.
277
278 void extensionEnd() ;
279 ///< A pseudo-private method used by the implementation
280 ///< of the apply() method template.
281
282private:
283 struct Output
284 {
285 bool m_first{true} ;
286 const char * m_data{nullptr} ;
287 std::size_t m_size{0U} ;
288 std::size_t m_eolsize{0U} ;
289 std::size_t m_linesize{0U} ;
290 char m_c0{'\0'} ;
291 Output() ;
292 std::size_t set( LineStore & , std::size_t pos , std::size_t size , std::size_t eolsize ) ;
293 } ;
294 struct Extension
295 {
296 Extension( LineBuffer * , const char * , std::size_t ) ;
297 ~Extension() ;
298 Extension( const Extension & ) = delete ;
299 Extension( Extension && ) = delete ;
300 Extension & operator=( const Extension & ) = delete ;
301 Extension & operator=( Extension && ) = delete ;
302 bool valid() const ;
303 LineBuffer * m_line_buffer ;
304 G::CallFrame m_call_frame ;
305 } ;
306
307public:
308 ~LineBuffer() = default ;
309 LineBuffer( const LineBuffer & ) = delete ;
310 LineBuffer( LineBuffer && ) = delete ;
311 LineBuffer & operator=( const LineBuffer & ) = delete ;
312 LineBuffer & operator=( LineBuffer && ) = delete ;
313
314private:
315 friend class LineBufferState ;
316 void output( std::size_t size , std::size_t eolsize , bool = false ) ;
317 bool detect() ;
318 bool trivial( std::size_t pos ) const ;
319 bool finite() const ;
320 std::string head() const ; // for LineBufferState
321
322private:
323 friend struct Extension ;
324 G::CallStack m_call_stack ;
325 bool m_auto ;
326 std::string m_eol ;
327 std::size_t m_warn_limit ;
328 std::size_t m_fmin ;
329 std::size_t m_expect ;
330 bool m_warned ;
331 LineStore m_in ;
332 Output m_out ;
333 std::size_t m_pos ;
334} ;
335
336//| \class GNet::LineBufferState
337/// Provides information about the state of a line buffer.
338///
340{
341public:
342 explicit LineBufferState( const LineBuffer & ) ;
343 ///< Constructor.
344
345 bool transparent() const ;
346 ///< Returns LineBuffer::transparent().
347
348 std::string eol() const ;
349 ///< Returns LineBuffer::eol().
350
351 std::size_t size() const ;
352 ///< Returns the number of bytes currently buffered up.
353
354 bool empty() const ;
355 ///< Returns true iff size() is zero.
356
357 std::string head() const ;
358 ///< Returns the first bytes of buffered data up to a limit
359 ///< of sixteen bytes.
360
361 bool peekmore() const ;
362 ///< Returns true if another complete line is available
363 ///< after the current line or current expect() block.
364
365private:
366 bool m_transparent ;
367 bool m_peekmore ;
368 std::string m_eol ;
369 std::size_t m_size ;
370 std::string m_head ;
371} ;
372
373// ==
374
375inline
376GNet::LineBuffer::Extension::Extension( LineBuffer * line_buffer , const char * data , std::size_t size ) :
377 m_line_buffer(line_buffer) ,
378 m_call_frame(line_buffer->m_call_stack)
379{
380 m_line_buffer->extensionStart( data , size ) ;
381}
382
383inline
384GNet::LineBuffer::Extension::~Extension()
385{
386 if( m_call_frame.valid() )
387 m_line_buffer->extensionEnd() ;
388}
389
390inline
391bool GNet::LineBuffer::Extension::valid() const
392{
393 return m_call_frame.valid() ;
394}
395
396// ==
397
398inline
399std::size_t GNet::LineBuffer::size() const
400{
401 return m_out.m_size ;
402}
403
404inline
406{
407 return m_in.size() ;
408}
409
410inline
411std::string GNet::LineBuffer::head() const
412{
413 return m_in.head( 16U ) ;
414}
415
416inline
417std::size_t GNet::LineBuffer::eolsize() const
418{
419 return m_out.m_eolsize ;
420}
421
422inline
423std::size_t GNet::LineBuffer::linesize() const
424{
425 return m_out.m_linesize ;
426}
427
428inline
430{
431 return m_out.m_c0 ;
432}
433
434template <typename Tfn>
435void GNet::LineBuffer::apply( const char * data_in , std::size_t size_in , Tfn sink_fn , bool with_fragments )
436{
437 Extension e( this , data_in , size_in ) ;
438 while( e.valid() && more(with_fragments) )
439 {
440 if( !sink_fn( data() , size() , eolsize() , linesize() , c0() ) )
441 break ;
442 }
443}
444
445template <typename Tsink, typename Tmemfun>
446void GNet::LineBuffer::apply( Tsink sink_p , Tmemfun memfun , const char * data_in , std::size_t size_in , bool with_fragments )
447{
448 Extension e( this , data_in , size_in ) ;
449 while( e.valid() && more(with_fragments) )
450 {
451 if( !(sink_p->*memfun)( data() , size() , eolsize() , linesize() , c0() ) )
452 break ;
453 }
454}
455
456template <typename Tsink, typename Tmemfun, typename Tmemfun2>
457void GNet::LineBuffer::apply( Tsink sink_p , Tmemfun memfun , const char * data_in , std::size_t size_in , Tmemfun2 fragments_memfun )
458{
459 Extension e( this , data_in , size_in ) ;
460 while( e.valid() && more( (sink_p->*fragments_memfun)() ) )
461 {
462 if( !(sink_p->*memfun)( data() , size() , eolsize() , linesize() , c0() ) )
463 break ;
464 }
465}
466
467inline
468bool GNet::LineBuffer::apply( SinkFn sink_fn , G::string_view data_in , FragmentsFn fragments_fn ) // NOLINT performance-unnecessary-value-param
469{
470 Extension e( this , data_in.data() , data_in.size() ) ;
471 while( e.valid() && more( fragments_fn() ) )
472 {
473 if( !sink_fn( SinkArgs(data(),size(),eolsize(),linesize(),c0(),peekmore()) ) )
474 return false ;
475 }
476 return true ;
477}
478
479template <typename T>
480inline
481void GNet::LineBuffer::apply( const std::string & data , T sink , bool with_fragments )
482{
483 return apply( data.data() , data.size() , sink , with_fragments ) ;
484}
485
486// ==
487
488inline const std::string & GNet::LineBuffer::Config::eol() const noexcept { return m_eol ; }
489inline std::size_t GNet::LineBuffer::Config::warn() const noexcept { return m_warn ; }
490inline std::size_t GNet::LineBuffer::Config::fmin() const noexcept { return m_fmin ; }
491inline std::size_t GNet::LineBuffer::Config::expect() const noexcept { return m_expect ; }
492inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_eol( const std::string & s ) { m_eol = s ; return *this ; }
493inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_warn( std::size_t n ) noexcept { m_warn = n ; return *this ; }
494inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_fmin( std::size_t n ) noexcept { m_fmin = n ; return *this ; }
495inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_expect( std::size_t n ) noexcept { m_expect = n ; return *this ; }
496namespace GNet
497{
498 inline bool operator==( const LineBuffer::Config & a , const LineBuffer::Config & b ) noexcept
499 {
500 return a.m_eol == b.m_eol && a.m_warn == b.m_warn && a.m_fmin == b.m_fmin && a.m_expect == b.m_expect ;
501 }
502}
503
504// ==
505
506inline
508 m_transparent(line_buffer.transparent()) ,
509 m_peekmore(line_buffer.peekmore()) ,
510 m_eol(line_buffer.eol()) ,
511 m_size(line_buffer.buffersize()) ,
512 m_head(line_buffer.head())
513{
514}
515
516inline
517std::string GNet::LineBufferState::eol() const
518{
519 return m_eol ;
520}
521
522inline
524{
525 return m_transparent ;
526}
527
528inline
530{
531 return m_size ;
532}
533
534inline
536{
537 return m_size == 0U ;
538}
539
540inline
542{
543 return m_head ;
544}
545
546inline
548{
549 return m_peekmore ;
550}
551
552#endif
Provides information about the state of a line buffer.
Definition: glinebuffer.h:340
bool peekmore() const
Returns true if another complete line is available after the current line or current expect() block.
Definition: glinebuffer.h:547
std::string head() const
Returns the first bytes of buffered data up to a limit of sixteen bytes.
Definition: glinebuffer.h:541
bool empty() const
Returns true iff size() is zero.
Definition: glinebuffer.h:535
bool transparent() const
Returns LineBuffer::transparent().
Definition: glinebuffer.h:523
std::size_t size() const
Returns the number of bytes currently buffered up.
Definition: glinebuffer.h:529
LineBufferState(const LineBuffer &)
Constructor.
Definition: glinebuffer.h:507
std::string eol() const
Returns LineBuffer::eol().
Definition: glinebuffer.h:517
A class that does line buffering, supporting auto-detection of line endings and fixed-size block extr...
Definition: glinebuffer.h:83
void expect(std::size_t n)
Requests that the next 'n' bytes are extracted in one contiguous block, without regard to line ending...
bool transparent() const
Returns true if the current expect() value is infinite.
std::size_t linesize() const
Returns the current size of all the line fragments making up the current line.
Definition: glinebuffer.h:423
void clear()
Clears the internal data.
Definition: glinebuffer.cpp:39
LineBuffer(const Config &)
Constructor.
Definition: glinebuffer.cpp:28
void extensionStart(const char *, std::size_t)
A pseudo-private method used by the implementation of the apply() method template.
Definition: glinebuffer.cpp:63
bool more(bool fragments=false)
Returns true if there is more data() to be had.
Definition: glinebuffer.cpp:75
std::size_t size() const
Returns the size of the current data(), excluding the line ending.
Definition: glinebuffer.h:399
std::size_t eolsize() const
Returns the size of line-ending associated with the current data().
Definition: glinebuffer.h:417
const char * data() const
Returns a pointer for the current line, expect()ed fixed-size block, or line fragment.
void apply(const char *data, std::size_t data_size, Tfn sink_fn, bool fragments=false)
Adds the data and passes complete lines to the sink function with line-data, line-size,...
Definition: glinebuffer.h:435
std::string eol() const
Returns the end-of-line string as passed in to the constructor, or as auto-detected.
std::size_t buffersize() const
Returns the total number of bytes buffered up.
Definition: glinebuffer.h:405
void extensionEnd()
A pseudo-private method used by the implementation of the apply() method template.
Definition: glinebuffer.cpp:69
void add(const std::string &data)
Adds a data segment.
Definition: glinebuffer.cpp:57
void apply(const std::string &, Tfn sink_fn, bool fragments=false)
Overload taking a string as its data input, used in testing.
char c0() const
Returns the first character of the current line.
Definition: glinebuffer.h:429
bool peekmore() const
Returns true if there is a line available after the current line or expect()ation.
LineBufferState state() const
Returns information about the current state of the line-buffer.
An object to represent a nested execution context.
Definition: gcall.h:88
A linked list of CallFrame pointers.
Definition: gcall.h:59
A class like c++17's std::string_view.
Definition: gstringview.h:51
Network classes.
Definition: gdef.h:1144
constexpr const char * tx(const char *p)
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
A configuration structure for GNet::LineBuffer.
Definition: glinebuffer.h:91