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