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
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
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
48void 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
55void GNet::LineBuffer::add( const std::string & s )
56{
57 m_in.append( s ) ;
58}
59#endif
60
61void GNet::LineBuffer::extensionStart( const char * data , std::size_t size )
62{
63 if( data )
64 m_in.extend( data , size ) ;
65}
66
68{
69 m_in.discard( m_pos ) ;
70 m_pos = 0U ;
71}
72
73bool 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
144{
145 return !m_in.empty() && !m_eol.empty() && !transparent() && m_in.find(m_eol,m_pos) != std::string::npos ;
146}
147
148bool 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
154bool 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
172void GNet::LineBuffer::expect( std::size_t n )
173{
174 m_expect = n ;
175}
176
178{
179 return ( m_expect + 1U ) == 0U ;
180}
181
182std::string GNet::LineBuffer::eol() const
183{
184 return m_eol ;
185}
186
187void 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
203const char * GNet::LineBuffer::data() const
204{
205 G_ASSERT( m_out.m_data != nullptr ) ;
206 return m_out.m_data ;
207}
208
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
235GNet::LineBuffer::Config GNet::LineBuffer::Config::transparent()
236{
237 return Config().set_expect( inf ) ;
238}
239
240GNet::LineBuffer::Config GNet::LineBuffer::Config::newline()
241{
242 return {} ;
243}
244
245GNet::LineBuffer::Config GNet::LineBuffer::Config::autodetect()
246{
247 return Config().set_eol( {} ) ;
248}
249
250GNet::LineBuffer::Config GNet::LineBuffer::Config::crlf()
251{
252 return Config().set_eol( {"\r\n",2U} ) ;
253}
254
255GNet::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
261GNet::LineBuffer::Config GNet::LineBuffer::Config::pop()
262{
263 return crlf() ;
264}
265
266#ifndef G_LIB_SMALL
267GNet::LineBuffer::Config GNet::LineBuffer::Config::http()
268{
269 return crlf() ;
270}
271#endif
272
Provides information about the state of a line buffer.
Definition: glinebuffer.h:341
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.
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
const char * data() const
Returns a pointer for the current line, expect()ed fixed-size block, or line fragment.
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:67
void add(const std::string &data)
Adds a data segment.
Definition: glinebuffer.cpp:55
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.
A pair of character buffers, one kept by value and the other being an ephemeral extension.
Definition: glinestore.h:41
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
A configuration structure for GNet::LineBuffer.
Definition: glinebuffer.h:92