E-MailRelay

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