E-MailRelay
glinebuffer.h
Go to the documentation of this file.
1//
2// Copyright (C) 2001-2021 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 "gcall.h"
28#include <string>
29
30namespace GNet
31{
32 class LineBuffer ;
33 class LineBufferConfig ;
34 class LineBufferIterator ;
35 class LineBufferState ;
36}
37
38//| \class GNet::LineBuffer
39/// A class that does line buffering, supporting auto-detection of
40/// line endings and fixed-size block extraction. Raw data is
41/// added, and newline-delimited lines are extracted, optionally
42/// via an iterator.
43///
44/// Usage:
45/// \code
46/// {
47/// GNet::LineBuffer buffer( (GNet::LineBufferConfig()) ) ;
48/// buffer.add("abc") ;
49/// buffer.add("def\nABC\nDE") ;
50/// buffer.add("F\n") ;
51///
52/// GNet::LineBufferIterator iter( buffer ) ;
53/// while( iter.more() )
54/// cout << iter.line() << 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::LineBufferConfig()) ) ;
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 , "line buffer overflow" ) ;
86
87 explicit LineBuffer( const LineBufferConfig & ) ;
88 ///< Constructor.
89
90 void clear() ;
91 ///< Clears the internal data.
92
93 void add( const std::string & data ) ;
94 ///< Adds a data segment.
95
96 void add( const char * data , std::size_t size ) ;
97 ///< Adds a data segment by copying.
98 ///< See also apply().
99
100 void expect( std::size_t n ) ;
101 ///< Requests that the next 'n' bytes extracted be extracted
102 ///< in one contiguous block, without regard to line endings.
103 ///< Once the expected number of bytes have been extracted
104 ///< the line buffering returns to normal.
105 ///<
106 ///< This method can be used during a data-transfer phase to
107 ///< obtain a chunk of data of known size, as in http with a
108 ///< known content-length.
109 ///<
110 ///< A parameter value of zero switches back to normal line
111 ///< buffering immediately.
112 ///<
113 ///< A parameter value of std::size_t(-1) can be used to represent
114 ///< an infinite expectation that is never fully satisfied.
115 ///< This is only sensible when extracting fragments and
116 ///< results in full transparency.
117
118 std::string eol() const ;
119 ///< Returns the end-of-line string as passed in to the
120 ///< constructor, or as auto-detected. Returns the empty
121 ///< string if auto-detection by iteration has not yet
122 ///< occurred.
123
124 template <typename Tfn>
125 void apply( const char * data , std::size_t data_size , Tfn sink_fn , bool fragments = false ) ;
126 ///< Adds the data and passes complete lines to the sink
127 ///< function with line-data, line-size, eol-size and
128 ///< c0 parameters. Stops if the sink function returns false.
129 ///< The data can be nullptr in order to flush any existing
130 ///< data to the sink function. This method is zero-copy if
131 ///< the supplied data contains complete lines or if allowing
132 ///< line fragments.
133 ///<
134 ///< \code
135 ///< void Foo::onData( const char * data , std::size_t size )
136 ///< {
137 ///< apply( data , size , onLine , false ) ;
138 ///< }
139 ///< bool onLine( const char * data , std::size_t size , std::size_t , std::size_t , char )
140 ///< {
141 ///< process( std::string(data,size) ) ;
142 ///< }
143 ///< \endcode
144
145 template <typename Tsink, typename Tmemfun>
146 void apply( Tsink sink_p , Tmemfun sink_memfun , const char * data , std::size_t data_size , bool fragments = false ) ;
147 ///< Overload that calls out to a member function.
148 ///<
149 ///< \code
150 ///< void Foo::onData( const char * data , std::size_t size )
151 ///< {
152 ///< apply( this , &Foo::onLine , data , size , false ) ;
153 ///< }
154 ///< bool Foo::onLine( const char * data , std::size_t size , std::size_t , std::size_t , char )
155 ///< {
156 ///< process( std::string(data,size) ) ;
157 ///< }
158 ///< \endcode
159
160 template <typename Tsink, typename Tmemfun, typename Tmemfun2>
161 void apply( Tsink sink_p , Tmemfun sink_memfun , const char * data , std::size_t data_size , Tmemfun2 fragments_memfun ) ;
162 ///< Overload where the 'fragments' flag comes from calling a member
163 ///< function on the sink object, allowing the flag to change
164 ///< dynamically as each line is delivered.
165
166 template <typename Tfn>
167 void apply( const std::string & , Tfn sink_fn , bool fragments = false ) ;
168 ///< Overload taking a string as its data input, used in
169 ///< testing.
170
171 bool more( bool fragments = false ) ;
172 ///< Returns true if there is more data() to be had.
173 ///< This advances the implied iterator.
174 ///<
175 ///< If the fragments parameter is true then incomplete
176 ///< lines will be returned (with eolsize zero), but
177 ///< those fragments will explicitly exclude anything
178 ///< that might be part of the line ending.
179
180 const char * data() const ;
181 ///< Returns a pointer for the current line, expect()ed
182 ///< fixed-size block, or line fragment. This includes
183 ///< eolsize() bytes of line-ending.
184 ///< Precondition: more()
185
186 std::size_t size() const ;
187 ///< Returns the size of the current data(), excluding the
188 ///< line ending.
189 ///< Precondition: more()
190
191 std::size_t eolsize() const ;
192 ///< Returns the size of line-ending associated with the
193 ///< current data(). This will be zero for a fixed-size
194 ///< block or non-terminal line fragment. (It will never
195 ///< be zero as a result of auto-detection because the
196 ///< precondition means that auto-detection has already
197 ///< happened.)
198 ///< Precondition: more()
199
200 std::size_t linesize() const ;
201 ///< Returns the current size of all the line fragments
202 ///< making up the current line.
203 ///< Precondition: more()
204
205 char c0() const ;
206 ///< Returns the first character of the current line.
207 ///< This can be useful in the case of line fragments
208 ///< where treatment of the fragment depends on the
209 ///< first character of the complete line (as in SMTP
210 ///< data transfer).
211 ///< Precondition: linesize() != 0U
212
213 bool transparent() const ;
214 ///< Returns true if the current expect() value is
215 ///< infinite.
216
217 LineBufferState state() const ;
218 ///< Returns information about the current state of the
219 ///< line-buffer.
220
221public:
222 void extensionStart( const char * , std::size_t ) ;
223 ///< A pseudo-private method used by the implementation
224 ///< of the apply() method template.
225
226 void extensionEnd() ;
227 ///< A pseudo-private method used by the implementation
228 ///< of the apply() method template.
229
230private:
231 struct Output
232 {
233 bool m_first{true} ;
234 const char * m_data{nullptr} ;
235 std::size_t m_size{0U} ;
236 std::size_t m_eolsize{0U} ;
237 std::size_t m_linesize{0U} ;
238 char m_c0{'\0'} ;
239 Output() ;
240 std::size_t set( LineStore & , std::size_t pos , std::size_t size , std::size_t eolsize ) ;
241 } ;
242 struct Extension
243 {
244 Extension( LineBuffer * , const char * , std::size_t ) ;
245 ~Extension() ;
246 Extension( const Extension & ) = delete ;
247 Extension( Extension && ) = delete ;
248 void operator=( const Extension & ) = delete ;
249 void operator=( Extension && ) = delete ;
250 bool valid() const ;
251 LineBuffer * m_line_buffer ;
252 G::CallFrame m_call_frame ;
253 } ;
254
255public:
256 ~LineBuffer() = default ;
257 LineBuffer( const LineBuffer & ) = delete ;
258 LineBuffer( LineBuffer && ) = delete ;
259 void operator=( const LineBuffer & ) = delete ;
260 void operator=( LineBuffer && ) = delete ;
261
262private:
263 friend class LineBufferState ;
264 void check( const Output & ) ;
265 void output( std::size_t size , std::size_t eolsize , bool = false ) ;
266 bool detect() ;
267 bool trivial( std::size_t pos ) const ;
268 bool finite() const ;
269 std::size_t insize() const ; // for LineBufferState
270 std::string head() const ; // for LineBufferState
271
272private:
273 friend struct Extension ;
274 G::CallStack m_call_stack ;
275 bool m_auto ;
276 std::string m_eol ;
277 std::size_t m_warn_limit ;
278 std::size_t m_fmin ;
279 std::size_t m_expect ;
280 bool m_warned ;
281 LineStore m_in ;
282 Output m_out ;
283 std::size_t m_pos ;
284} ;
285
286//| \class GNet::LineBufferIterator
287/// Syntactic sugar for calling GNet::LineBuffer iteration methods.
288///
290{
291public:
292 explicit LineBufferIterator( LineBuffer & buffer ) ;
293 ///< Constructor.
294
295 bool more() ;
296 ///< See LineBuffer::more().
297
298 const char * data() const ;
299 ///< See LineBuffer::data().
300
301 std::size_t size() const ;
302 ///< See LineBuffer::size().
303
304 std::size_t eolsize() const ;
305 ///< See LineBuffer::eolsize().
306
307 std::string line() const ;
308 ///< Returns the current line (of length size()).
309
310public:
311 ~LineBufferIterator() = default ;
312 LineBufferIterator( const LineBufferIterator & ) = delete ;
314 void operator=( const LineBufferIterator & ) = delete ;
315 void operator=( LineBufferIterator && ) = delete ;
316
317private:
318 LineBuffer & m_line_buffer ;
319} ;
320
321//| \class GNet::LineBufferConfig
322/// A configuration structure for GNet::LineBuffer.
323///
325{
326public:
327 explicit LineBufferConfig( const std::string & eol = std::string(1U,'\n') ,
328 std::size_t warn = 0U , std::size_t fmin = 0U , std::size_t initial_expect = 0U ) ;
329 ///< Constructor. An empty end-of-line string detects either
330 ///< LF or CR-LF. The default end-of-line string is newline.
331 ///< A non-zero warn-limit generates a one-shot warning when
332 ///< breached. The fmin value can be used to prevent trivially
333 ///< small line fragments from being returned. This is useful
334 ///< for SMTP where a fragment containing a single dot character
335 ///< and no end-of-line can cause confusion with respect to
336 ///< the end-of-text marker. The initial-expect parameter
337 ///< is useful for defining transparent operation.
338
339 const std::string & eol() const ;
340 ///< Returns the end-of-line string as passed to the constructor.
341
342 std::size_t warn() const ;
343 ///< Returns the warn-limit, as passed to the constructor.
344
345 std::size_t fmin() const ;
346 ///< Returns the minimum fragment size, as passed to the constructor.
347
348 std::size_t expect() const ;
349 ///< Returns the initial expect value, as passed to the constructor.
350
352 ///< Convenience factory function.
353
354 static LineBufferConfig http() ;
355 ///< Convenience factory function.
356
357 static LineBufferConfig smtp() ;
358 ///< Convenience factory function.
359
360 static LineBufferConfig pop() ;
361 ///< Convenience factory function.
362
363 static LineBufferConfig crlf() ;
364 ///< Convenience factory function.
365
366 static LineBufferConfig newline() ;
367 ///< Convenience factory function.
368
370 ///< Convenience factory function.
371
372private:
373 std::string m_eol ;
374 std::size_t m_warn ;
375 std::size_t m_fmin ;
376 std::size_t m_expect ;
377} ;
378
379//| \class GNet::LineBufferState
380/// Provides information abount the state of a line buffer.
381///
383{
384public:
385 explicit LineBufferState( const LineBuffer & ) ;
386 ///< Constructor.
387
388 bool transparent() const ;
389 ///< Returns LineBuffer::transparent().
390
391 std::string eol() const ;
392 ///< Returns LineBuffer::eol().
393
394 std::size_t size() const ;
395 ///< Returns the number of bytes currently buffered up.
396
397 bool empty() const ;
398 ///< Returns true iff size() is zero.
399
400 std::string head() const ;
401 ///< Returns the first bytes of buffered data up to a limit
402 ///< of sixteen bytes.
403
404private:
405 bool m_transparent ;
406 std::string m_eol ;
407 std::size_t m_size ;
408 std::string m_head ;
409} ;
410
411// ==
412
413inline
414GNet::LineBuffer::Extension::Extension( LineBuffer * line_buffer , const char * data , std::size_t size ) :
415 m_line_buffer(line_buffer) ,
416 m_call_frame(line_buffer->m_call_stack)
417{
418 m_line_buffer->extensionStart( data , size ) ;
419}
420
421inline
422GNet::LineBuffer::Extension::~Extension()
423{
424 if( m_call_frame.valid() )
425 m_line_buffer->extensionEnd() ;
426}
427
428inline
429bool GNet::LineBuffer::Extension::valid() const
430{
431 return m_call_frame.valid() ;
432}
433
434// ==
435
436inline
437std::size_t GNet::LineBuffer::size() const
438{
439 return m_out.m_size ;
440}
441
442inline
443std::size_t GNet::LineBuffer::insize() const
444{
445 return m_in.size() ;
446}
447
448inline
449std::string GNet::LineBuffer::head() const
450{
451 return m_in.head( 16U ) ;
452}
453
454inline
455std::size_t GNet::LineBuffer::eolsize() const
456{
457 return m_out.m_eolsize ;
458}
459
460inline
461std::size_t GNet::LineBuffer::linesize() const
462{
463 return m_out.m_linesize ;
464}
465
466inline
468{
469 return m_out.m_c0 ;
470}
471
472template <typename Tfn>
473void GNet::LineBuffer::apply( const char * data_in , std::size_t size_in , Tfn sink_fn , bool with_fragments )
474{
475 Extension e( this , data_in , size_in ) ;
476 while( e.valid() && more(with_fragments) )
477 {
478 if( !sink_fn( data() , size() , eolsize() , linesize() , c0() ) )
479 break ;
480 }
481}
482
483template <typename Tsink, typename Tmemfun>
484void GNet::LineBuffer::apply( Tsink sink_p , Tmemfun memfun , const char * data_in , std::size_t size_in , bool with_fragments )
485{
486 Extension e( this , data_in , size_in ) ;
487 while( e.valid() && more(with_fragments) )
488 {
489 if( !(sink_p->*memfun)( data() , size() , eolsize() , linesize() , c0() ) )
490 break ;
491 }
492}
493
494template <typename Tsink, typename Tmemfun, typename Tmemfun2>
495void GNet::LineBuffer::apply( Tsink sink_p , Tmemfun memfun , const char * data_in , std::size_t size_in , Tmemfun2 fragments_memfun )
496{
497 Extension e( this , data_in , size_in ) ;
498 while( e.valid() && more( (sink_p->*fragments_memfun)() ) )
499 {
500 if( !(sink_p->*memfun)( data() , size() , eolsize() , linesize() , c0() ) )
501 break ;
502 }
503}
504
505template <typename T>
506inline
507void GNet::LineBuffer::apply( const std::string & data , T sink , bool with_fragments )
508{
509 return apply( data.data() , data.size() , sink , with_fragments ) ;
510}
511
512// ==
513
514inline
515const std::string & GNet::LineBufferConfig::eol() const
516{
517 return m_eol ;
518}
519
520inline
522{
523 return m_warn ;
524}
525
526inline
528{
529 return m_fmin ;
530}
531
532inline
534{
535 return m_expect ;
536}
537
538// ==
539
540inline
542 m_line_buffer(line_buffer)
543{
544}
545
546inline
548{
549 return m_line_buffer.more() ;
550}
551
552inline
554{
555 return std::string( m_line_buffer.data() , m_line_buffer.size() ) ;
556}
557
558inline
560{
561 return m_line_buffer.data() ;
562}
563
564inline
566{
567 return m_line_buffer.size() ;
568}
569
570inline
572{
573 return m_line_buffer.eolsize() ;
574}
575
576// ==
577
578inline
580 m_transparent(line_buffer.transparent()) ,
581 m_eol(line_buffer.eol()) ,
582 m_size(line_buffer.insize()) ,
583 m_head(line_buffer.head())
584{
585}
586
587inline
588std::string GNet::LineBufferState::eol() const
589{
590 return m_eol ;
591}
592
593inline
595{
596 return m_transparent ;
597}
598
599inline
601{
602 return m_size ;
603}
604
605inline
607{
608 return m_size == 0U ;
609}
610
611inline
613{
614 return m_head ;
615}
616
617#endif
A configuration structure for GNet::LineBuffer.
Definition: glinebuffer.h:325
static LineBufferConfig crlf()
Convenience factory function.
std::size_t warn() const
Returns the warn-limit, as passed to the constructor.
Definition: glinebuffer.h:521
std::size_t expect() const
Returns the initial expect value, as passed to the constructor.
Definition: glinebuffer.h:533
std::size_t fmin() const
Returns the minimum fragment size, as passed to the constructor.
Definition: glinebuffer.h:527
static LineBufferConfig transparent()
Convenience factory function.
static LineBufferConfig http()
Convenience factory function.
const std::string & eol() const
Returns the end-of-line string as passed to the constructor.
Definition: glinebuffer.h:515
static LineBufferConfig autodetect()
Convenience factory function.
static LineBufferConfig newline()
Convenience factory function.
static LineBufferConfig pop()
Convenience factory function.
LineBufferConfig(const std::string &eol=std::string(1U,'\n'), std::size_t warn=0U, std::size_t fmin=0U, std::size_t initial_expect=0U)
Constructor.
static LineBufferConfig smtp()
Convenience factory function.
Syntactic sugar for calling GNet::LineBuffer iteration methods.
Definition: glinebuffer.h:290
LineBufferIterator(LineBuffer &buffer)
Constructor.
Definition: glinebuffer.h:541
std::string line() const
Returns the current line (of length size()).
Definition: glinebuffer.h:553
const char * data() const
See LineBuffer::data().
Definition: glinebuffer.h:559
std::size_t eolsize() const
See LineBuffer::eolsize().
Definition: glinebuffer.h:571
bool more()
See LineBuffer::more().
Definition: glinebuffer.h:547
std::size_t size() const
See LineBuffer::size().
Definition: glinebuffer.h:565
Provides information abount the state of a line buffer.
Definition: glinebuffer.h:383
std::string head() const
Returns the first bytes of buffered data up to a limit of sixteen bytes.
Definition: glinebuffer.h:612
bool empty() const
Returns true iff size() is zero.
Definition: glinebuffer.h:606
bool transparent() const
Returns LineBuffer::transparent().
Definition: glinebuffer.h:594
std::size_t size() const
Returns the number of bytes currently buffered up.
Definition: glinebuffer.h:600
LineBufferState(const LineBuffer &)
Constructor.
Definition: glinebuffer.h:579
std::string eol() const
Returns LineBuffer::eol().
Definition: glinebuffer.h:588
A class that does line buffering, supporting auto-detection of line endings and fixed-size block extr...
Definition: glinebuffer.h:83
LineBuffer(const LineBufferConfig &)
Constructor.
Definition: glinebuffer.cpp:28
void expect(std::size_t n)
Requests that the next 'n' bytes extracted be extracted in one contiguous block, without regard to li...
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:461
void clear()
Clears the internal data.
Definition: glinebuffer.cpp:39
void extensionStart(const char *, std::size_t)
A pseudo-private method used by the implementation of the apply() method template.
Definition: glinebuffer.cpp:60
bool more(bool fragments=false)
Returns true if there is more data() to be had.
Definition: glinebuffer.cpp:72
std::size_t size() const
Returns the size of the current data(), excluding the line ending.
Definition: glinebuffer.h:437
std::size_t eolsize() const
Returns the size of line-ending associated with the current data().
Definition: glinebuffer.h:455
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:473
std::string eol() const
Returns the end-of-line string as passed in to the constructor, or as auto-detected.
void extensionEnd()
A pseudo-private method used by the implementation of the apply() method template.
Definition: glinebuffer.cpp:66
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:467
LineBufferState state() const
Returns information about the current state of the line-buffer.
An object to represent a nested execution context.
Definition: gcall.h:87
A linked list of CallFrame pointers.
Definition: gcall.h:58
Network classes.
Definition: gdef.h:1115