Line data Source code
1 : // Copyright (c) 2006-2022 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/advgetopt
4 : // contact@m2osw.com
5 : //
6 : // This program is free software; you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation; either version 2 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License along
17 : // with this program; if not, write to the Free Software Foundation, Inc.,
18 : // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 :
20 : /** \file
21 : * \brief Implementation of the integer validator.
22 : *
23 : * This validator is used to verify that a parameter represents a valid
24 : * integer.
25 : *
26 : * Note that the validator supports 64 bits integers by default. You can
27 : * reduce the size by defining your parameter with a range as required
28 : * by your application.
29 : *
30 : * The value is checked for overflows on a signed 64 bits value.
31 : */
32 :
33 : // self
34 : //
35 : #include "advgetopt/validator_integer.h"
36 :
37 :
38 : // cppthread
39 : //
40 : #include <cppthread/log.h>
41 :
42 :
43 : // snapdev
44 : //
45 : #include <snapdev/not_used.h>
46 :
47 :
48 : // boost
49 : //
50 : #include <boost/algorithm/string/trim.hpp>
51 :
52 :
53 : // last include
54 : //
55 : #include <snapdev/poison.h>
56 :
57 :
58 :
59 :
60 : namespace advgetopt
61 : {
62 :
63 :
64 :
65 : namespace
66 : {
67 :
68 :
69 :
70 2 : class validator_integer_factory
71 : : public validator_factory
72 : {
73 : public:
74 2 : validator_integer_factory()
75 2 : {
76 2 : validator::register_validator(*this);
77 2 : }
78 :
79 4 : virtual std::string get_name() const override
80 : {
81 4 : return std::string("integer");
82 : }
83 :
84 52 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
85 : {
86 52 : snapdev::NOT_USED(data); // ignore `data`
87 52 : return std::make_shared<validator_integer>(data);
88 : }
89 : };
90 :
91 2 : validator_integer_factory g_validator_integer_factory;
92 :
93 :
94 :
95 : } // no name namespace
96 :
97 :
98 :
99 :
100 :
101 : /** \brief Initialize the integer validator.
102 : *
103 : * The constructor accepts a string with values and ranges which are
104 : * used to limit the values that can be used with this parameter.
105 : *
106 : * Remember that the default does not have to be included in these
107 : * values. It will still be viewed as \em valid.
108 : *
109 : * The string uses the following format:
110 : *
111 : * \code
112 : * start: range
113 : * | start ',' range
114 : *
115 : * range: number
116 : * | number '...' number
117 : *
118 : * number: [-+]?[0-9]+
119 : * \endcode
120 : *
121 : * Note that a single number is considered to be a range and is managed
122 : * the exact same way. A value which matches any of the ranges is considered
123 : * valid.
124 : *
125 : * Example:
126 : *
127 : * \code
128 : * "-100...100,-1000"
129 : * \endcode
130 : *
131 : * This example allows all values between -100 and +100 inclusive and also
132 : * allows the value -1000.
133 : *
134 : * \param[in] ranges The ranges used to limit the integer.
135 : */
136 52 : validator_integer::validator_integer(string_list_t const & range_list)
137 : {
138 52 : range_t range;
139 270 : for(auto r : range_list)
140 : {
141 222 : std::string::size_type const pos(r.find("..."));
142 222 : if(pos == std::string::npos)
143 : {
144 198 : if(!convert_string(r, range.f_minimum))
145 : {
146 2 : cppthread::log << cppthread::log_level_t::error
147 1 : << r
148 : << " is not a valid standalone value for your ranges;"
149 : " it must only be digits, optionally preceeded by a sign (+ or -)"
150 1 : " and not overflow an int64_t value."
151 2 : << cppthread::end;
152 1 : continue;
153 : }
154 196 : range.f_maximum = range.f_minimum;
155 : }
156 : else
157 : {
158 47 : std::string min_value(r.substr(0, pos));
159 25 : boost::trim(min_value);
160 26 : if(!convert_string(min_value, range.f_minimum))
161 : {
162 2 : cppthread::log << cppthread::log_level_t::error
163 1 : << min_value
164 : << " is not a valid value for your range's start;"
165 : " it must only be digits, optionally preceeded by a sign (+ or -)"
166 1 : " and not overflow an int64_t value."
167 2 : << cppthread::end;
168 1 : continue;
169 : }
170 :
171 46 : std::string max_value(r.substr(pos + 3));
172 24 : boost::trim(max_value);
173 25 : if(!convert_string(max_value, range.f_maximum))
174 : {
175 2 : cppthread::log << cppthread::log_level_t::error
176 1 : << max_value
177 : << " is not a valid value for your range's end;"
178 : " it must only be digits, optionally preceeded by a sign (+ or -)"
179 1 : " and not overflow an int64_t value."
180 2 : << cppthread::end;
181 1 : continue;
182 : }
183 :
184 24 : if(range.f_minimum > range.f_maximum)
185 : {
186 2 : cppthread::log << cppthread::log_level_t::error
187 1 : << min_value
188 1 : << " has to be smaller or equal to "
189 1 : << max_value
190 1 : << "; you have an invalid range."
191 2 : << cppthread::end;
192 1 : continue;
193 : }
194 : }
195 218 : f_allowed_values.push_back(range);
196 : }
197 52 : }
198 :
199 :
200 : /** \brief Return the name of this validator.
201 : *
202 : * This function returns "integer".
203 : *
204 : * \return "integer".
205 : */
206 43 : std::string validator_integer::name() const
207 : {
208 43 : return std::string("integer");
209 : }
210 :
211 :
212 : /** \brief Determine whether value is an integer.
213 : *
214 : * This function verifies that the specified value is a valid integer.
215 : *
216 : * It makes sures that the value is only composed of digits (`[0-9]+`).
217 : * It may also start with a sign (`[-+]?`).
218 : *
219 : * The function also makes sure that the value fits in an `int64_t` value.
220 : *
221 : * If ranges were defined, then the function also verifies that the
222 : * value is within at least one of the ranges.
223 : *
224 : * \todo
225 : * Add support for binary, octal, hexadecimal.
226 : *
227 : * \param[in] value The value to validate.
228 : *
229 : * \return true if the value validates.
230 : */
231 135452 : bool validator_integer::validate(std::string const & value) const
232 : {
233 135452 : std::int64_t result(0);
234 135452 : if(convert_string(value, result))
235 : {
236 51442 : if(f_allowed_values.empty())
237 : {
238 1521 : return true;
239 : }
240 :
241 252657 : for(auto f : f_allowed_values)
242 : {
243 211667 : if(result >= f.f_minimum
244 117506 : && result <= f.f_maximum)
245 : {
246 8931 : return true;
247 : }
248 : }
249 40990 : return false;
250 : }
251 :
252 84010 : return false;
253 : }
254 :
255 :
256 : /** \brief Convert a string to an std::int64_t value.
257 : *
258 : * This function is used to convert a string to an integer with full
259 : * boundary verification.
260 : *
261 : * \warning
262 : * There is no range checks in this function since it does not have access
263 : * to the ranges (i.e. it is static).
264 : *
265 : * \param[in] value The value to be converted to an integer.
266 : * \param[out] result The resulting integer.
267 : *
268 : * \return true if the conversion succeeded.
269 : */
270 135844 : bool validator_integer::convert_string(std::string const & value, std::int64_t & result)
271 : {
272 135844 : std::uint64_t integer(0);
273 135844 : char const * s(value.c_str());
274 :
275 135844 : char sign('\0');
276 135844 : if(*s == '-' || *s == '+')
277 : {
278 52907 : sign = *s;
279 52907 : ++s;
280 : }
281 :
282 135844 : if(*s == '\0')
283 : {
284 : // empty string, not considered valid
285 : //
286 8 : return false;
287 : }
288 :
289 : for(;;)
290 : {
291 1902997 : char const c(*s++);
292 1902997 : if(c == '\0')
293 : {
294 : // valid
295 : //
296 51822 : if(sign == '-')
297 : {
298 21018 : if(integer > 0x8000000000000000ULL)
299 : {
300 2 : return false;
301 : }
302 21016 : result = -integer;
303 : }
304 : else
305 : {
306 30804 : if(integer > 0x7FFFFFFFFFFFFFFFULL)
307 : {
308 3 : return false;
309 : }
310 30801 : result = integer;
311 : }
312 51817 : return true;
313 : }
314 1851175 : if(c < '0' || c > '9')
315 : {
316 : // invalid digit
317 : //
318 84010 : return false;
319 : }
320 :
321 1767165 : std::uint64_t const old(integer);
322 1767165 : integer = integer * 10 + c - '0';
323 1767165 : if(integer < old)
324 : {
325 : // overflow
326 : //
327 4 : return false;
328 : }
329 1767161 : }
330 : }
331 :
332 :
333 :
334 6 : } // namespace advgetopt
335 : // vim: ts=4 sw=4 et
|