E-MailRelay

glinebuffer.cpp
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.cpp
19///
20
21#include "gdef.h"
22#include "glinebuffer.h"
23#include "gtest.h"
24#include "glog.h"
25#include "gassert.h"
26#include <algorithm>
27
28 GNet::LineBuffer::LineBuffer( const Config & config ) :
29 m_auto(config.eol().empty()) ,
30 m_eol(config.eol()) ,
31 m_warn_limit(config.warn()) ,
32 m_fmin(config.fmin()) ,
33 m_expect(config.expect())
34{
35}
36
37 void GNet::LineBuffer::clear()
38{
39 m_in.clear() ;
40 m_out = Output() ;
41 m_pos = 0U ;
42 if( !transparent() )
43 m_expect = 0U ;
44
45 G_ASSERT( m_in.empty() && state().empty() ) ;
46}
47
48 void GNet::LineBuffer::add( const char * data , std::size_t size )
49{
50 if( data != nullptr && size != 0U )
51 m_in.append( data , size ) ;
52}
53
54#ifndef G_LIB_SMALL
55 void GNet::LineBuffer::add( const std::string & s )
56{
57 m_in.append( s ) ;
58}
59#endif
60
61 void GNet::LineBuffer::extensionStart( const char * data , std::size_t size )
62{
63 if( data )
64 m_in.extend( data , size ) ;
65}
66
67 void GNet::LineBuffer::extensionEnd()
68{
69 m_in.discard( m_pos ) ;
70 m_pos = 0U ;
71}
72
73 bool GNet::LineBuffer::more( bool fragments )
74{
75 G_ASSERT( m_pos <= m_in.size() ) ;
76 const std::size_t npos = std::string::npos ;
77 std::size_t pos = 0U ;
78
79 if( m_pos == m_in.size() )
80 {
81 // finished iterating, no residue
82 //
83 m_in.clear() ;
84 m_pos = 0U ;
85 return false ;
86 }
87 else if( m_expect != 0U )
88 {
89 if( !transparent() && (m_pos+m_expect) <= m_in.size() )
90 {
91 // got all expected
92 //
93 output( m_expect , 0U , true ) ;
94 m_expect = 0U ;
95 return true ;
96 }
97 else if( fragments && !trivial(m_in.size()) )
98 {
99 // not all expected, return the available fragment
100 //
101 G_ASSERT( m_in.size() > m_pos ) ;
102 std::size_t n = m_in.size() - m_pos ;
103 output( n , 0U ) ;
104 if( !transparent() ) m_expect -= n ;
105 return true ;
106 }
107 else
108 {
109 // expecting more
110 //
111 return false ;
112 }
113 }
114 else if( !detect() )
115 {
116 // no eol-style determined yet
117 //
118 return false ;
119 }
120 else if( (pos=m_in.find(m_eol,m_pos)) != npos ) // NOLINT assignment
121 {
122 // complete line available
123 //
124 output( pos-m_pos , m_eol.size() ) ;
125 return true ;
126 }
127 else if( fragments && (pos=m_in.findSubStringAtEnd(m_eol,m_pos)) != m_pos && !trivial(pos) ) // NOLINT assignment
128 {
129 // finished iterating, return the residual fragment
130 //
131 pos = pos == npos ? m_in.size() : pos ;
132 output( pos-m_pos , 0U ) ;
133 return true ;
134 }
135 else
136 {
137 // finished iterating
138 //
139 return false ;
140 }
141}
142
143 bool GNet::LineBuffer::peekmore() const
144{
145 return !m_in.empty() && !m_eol.empty() && !transparent() && m_in.find(m_eol,m_pos) != std::string::npos ;
146}
147
148 bool GNet::LineBuffer::trivial( std::size_t pos ) const
149{
150 pos = pos == std::string::npos ? m_in.size() : pos ;
151 return ( pos - m_pos ) < m_fmin ;
152}
153
154 bool GNet::LineBuffer::detect()
155{
156 const std::size_t npos = std::string::npos ;
157 if( m_auto )
158 {
159 std::size_t pos = m_in.find( '\n' ) ;
160 if( pos != npos )
161 {
162 if( pos > 0U && m_in.at(pos-1U) == '\r' )
163 m_eol.assign( "\r\n" , 2U ) ;
164 else
165 m_eol.assign( 1U , '\n' ) ;
166 m_auto = false ;
167 }
168 }
169 return !m_eol.empty() ;
170}
171
172 void GNet::LineBuffer::expect( std::size_t n )
173{
174 m_expect = n ;
175}
176
177 bool GNet::LineBuffer::transparent() const
178{
179 return ( m_expect + 1U ) == 0U ;
180}
181
182std::string GNet::LineBuffer::eol() const
183{
184 return m_eol ;
185}
186
187 void GNet::LineBuffer::output( std::size_t size , std::size_t eolsize , bool force_next_is_start_of_line )
188{
189 G_ASSERT( (size+eolsize) != 0U ) ;
190
191 m_pos += m_out.set( m_in , m_pos , size , eolsize ) ;
192
193 if( force_next_is_start_of_line )
194 m_out.m_first = true ;
195
196 if( m_out.m_eolsize && m_out.m_size > m_warn_limit && !m_warned && m_warn_limit != 0U )
197 {
198 G_WARNING( "GNet::LineBuffer::output: very long line detected: " << m_out.m_size << " > " << m_warn_limit ) ;
199 m_warned = true ;
200 }
201}
202
203 const char * GNet::LineBuffer::data() const
204{
205 G_ASSERT( m_out.m_data != nullptr ) ;
206 return m_out.m_data ;
207}
208
209 GNet::LineBufferState GNet::LineBuffer::state() const
210{
211 return LineBufferState( *this ) ;
212}
213
214// ==
215
216GNet::LineBuffer::Output::Output()
217= default;
218
219std::size_t GNet::LineBuffer::Output::set( LineStore & in , std::size_t pos , std::size_t size , std::size_t eolsize )
220{
221 bool start = m_first || m_eolsize != 0U ; // ie. wrt previous line's eolsize
222 m_first = false ;
223
224 m_size = size ;
225 m_eolsize = eolsize ;
226 if( start ) m_linesize = 0U ;
227 m_linesize += size ;
228 m_data = in.data( pos , size+eolsize ) ;
229 if( start ) m_c0 = size == 0U ? '\0' : m_data[0] ;
230 return size + eolsize ;
231}
232
233// ==
234
235 GNet::LineBuffer::Config GNet::LineBuffer::Config::transparent()
236{
237 return Config().set_expect( inf ) ;
238}
239
240 GNet::LineBuffer::Config GNet::LineBuffer::Config::newline()
241{
242 return {} ;
243}
244
245 GNet::LineBuffer::Config GNet::LineBuffer::Config::autodetect()
246{
247 return Config().set_eol( {} ) ;
248}
249
250 GNet::LineBuffer::Config GNet::LineBuffer::Config::crlf()
251{
252 return Config().set_eol( {"\r\n",2U} ) ;
253}
254
255 GNet::LineBuffer::Config GNet::LineBuffer::Config::smtp()
256{
257 static constexpr unsigned int soft_limit = 998U ; // RFC-2822
258 return Config().set_eol( {"\r\n",2U} ).set_warn(soft_limit+2U).set_fmin(2U) ;
259}
260
261 GNet::LineBuffer::Config GNet::LineBuffer::Config::pop()
262{
263 return crlf() ;
264}
265
266#ifndef G_LIB_SMALL
267 GNet::LineBuffer::Config GNet::LineBuffer::Config::http()
268{
269 return crlf() ;
270}
271#endif
272
GNet::LineBufferState
Provides information about the state of a line buffer.
Definition: glinebuffer.h:341
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::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::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::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::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::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
GNet::LineStore
A pair of character buffers, one kept by value and the other being an ephemeral extension.
Definition: glinestore.h:41
GNet::LineStore::data
const char * data(std::size_t pos, std::size_t size) const
Returns a pointer for the data at the given position that is contiguous for the given size.
Definition: glinestore.cpp:351
gassert.h
glinebuffer.h
glog.h
gtest.h
GNet::LineBuffer::Config
A configuration structure for GNet::LineBuffer.
Definition: glinebuffer.h:92