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
33namespace 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///
84{
85public:
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
274public:
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
283private:
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
308public:
309 ~LineBuffer() = default ;
310 LineBuffer( const LineBuffer & ) = delete ;
311 LineBuffer( LineBuffer && ) = delete ;
312 LineBuffer & operator=( const LineBuffer & ) = delete ;
313 LineBuffer & operator=( LineBuffer && ) = delete ;
314
315private:
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
323private:
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///
341{
342public:
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
366private:
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
376inline
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
384inline
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
399inline
400bool GNet::LineBuffer::Extension::valid() const
401{
402 return m_call_frame.valid() ;
403}
404
405// ==
406
407inline
408std::size_t GNet::LineBuffer::size() const
409{
410 return m_out.m_size ;
411}
412
413inline
415{
416 return m_in.size() ;
417}
418
419inline
420std::string GNet::LineBuffer::head() const
421{
422 return m_in.head( 16U ) ;
423}
424
425inline
426std::size_t GNet::LineBuffer::eolsize() const
427{
428 return m_out.m_eolsize ;
429}
430
431inline
432std::size_t GNet::LineBuffer::linesize() const
433{
434 return m_out.m_linesize ;
435}
436
437inline
439{
440 return m_out.m_c0 ;
441}
442
443template <typename Tfn>
444void 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
454template <typename Tsink, typename Tmemfun>
455void 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
465template <typename Tsink, typename Tmemfun, typename Tmemfun2>
466void 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
476inline
477bool 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
488template <typename T>
489inline
490void 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
497inline const std::string & GNet::LineBuffer::Config::eol() const noexcept { return m_eol ; }
498inline std::size_t GNet::LineBuffer::Config::warn() const noexcept { return m_warn ; }
499inline std::size_t GNet::LineBuffer::Config::fmin() const noexcept { return m_fmin ; }
500inline std::size_t GNet::LineBuffer::Config::expect() const noexcept { return m_expect ; }
501inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_eol( const std::string & s ) { m_eol = s ; return *this ; }
502inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_warn( std::size_t n ) noexcept { m_warn = n ; return *this ; }
503inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_fmin( std::size_t n ) noexcept { m_fmin = n ; return *this ; }
504inline GNet::LineBuffer::Config & GNet::LineBuffer::Config::set_expect( std::size_t n ) noexcept { m_expect = n ; return *this ; }
505namespace 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
515inline
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
525inline
526std::string GNet::LineBufferState::eol() const
527{
528 return m_eol ;
529}
530
531inline
533{
534 return m_transparent ;
535}
536
537inline
539{
540 return m_size ;
541}
542
543inline
545{
546 return m_size == 0U ;
547}
548
549inline
551{
552 return m_head ;
553}
554
555inline
557{
558 return m_peekmore ;
559}
560
561#endif
Provides information about the state of a line buffer.
Definition: glinebuffer.h:341
bool peekmore() const
Returns true if another complete line is available after the current line or current expect() block.
Definition: glinebuffer.h:556
std::string head() const
Returns the first bytes of buffered data up to a limit of sixteen bytes.
Definition: glinebuffer.h:550
bool empty() const
Returns true iff size() is zero.
Definition: glinebuffer.h:544
bool transparent() const
Returns LineBuffer::transparent().
Definition: glinebuffer.h:532
std::size_t size() const
Returns the number of bytes currently buffered up.
Definition: glinebuffer.h:538
LineBufferState(const LineBuffer &)
Constructor.
Definition: glinebuffer.h:516
std::string eol() const
Returns LineBuffer::eol().
Definition: glinebuffer.h:526
A class that does line buffering, supporting auto-detection of line endings and fixed-size block extr...
Definition: glinebuffer.h:84
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:432
void clear()
Clears the internal data.
Definition: glinebuffer.cpp:37
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:61
bool more(bool fragments=false)
Returns true if there is more data() to be had.
Definition: glinebuffer.cpp:73
std::size_t size() const
Returns the size of the current data(), excluding the line ending.
Definition: glinebuffer.h:408
std::size_t eolsize() const
Returns the size of line-ending associated with the current data().
Definition: glinebuffer.h:426
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:444
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:414
void extensionEnd()
A pseudo-private method used by the implementation of the apply() method template.
Definition: glinebuffer.cpp:67
void add(const std::string &data)
Adds a data segment.
Definition: glinebuffer.cpp:55
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:438
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:86
A linked list of CallFrame pointers.
Definition: gcall.h:59
Network classes.
Definition: gdef.h:1243
constexpr const char * tx(const char *p) noexcept
A briefer alternative to G::gettext_noop().
Definition: ggettext.h:84
A configuration structure for GNet::LineBuffer.
Definition: glinebuffer.h:92