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 : // boost
44 : //
45 : #include <boost/algorithm/string/trim.hpp>
46 :
47 :
48 : // last include
49 : //
50 : #include <snapdev/poison.h>
51 :
52 :
53 :
54 :
55 : namespace advgetopt
56 : {
57 :
58 :
59 :
60 : namespace
61 : {
62 :
63 :
64 :
65 2 : class validator_integer_factory
66 : : public validator_factory
67 : {
68 : public:
69 2 : validator_integer_factory()
70 2 : {
71 2 : validator::register_validator(*this);
72 2 : }
73 :
74 4 : virtual std::string get_name() const override
75 : {
76 4 : return std::string("integer");
77 : }
78 :
79 52 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
80 : {
81 52 : return std::make_shared<validator_integer>(data);
82 : }
83 : };
84 :
85 2 : validator_integer_factory g_validator_integer_factory;
86 :
87 :
88 :
89 : } // no name namespace
90 :
91 :
92 :
93 :
94 :
95 : /** \brief Initialize the integer validator.
96 : *
97 : * The constructor accepts a string with values and ranges which are
98 : * used to limit the values that can be used with this parameter.
99 : *
100 : * Remember that the default does not have to be included in these
101 : * values. It will still be viewed as \em valid.
102 : *
103 : * The string uses the following format:
104 : *
105 : * \code
106 : * start: range
107 : * | start ',' range
108 : *
109 : * range: number
110 : * | number '...' number
111 : *
112 : * number: [-+]?[0-9]+
113 : * \endcode
114 : *
115 : * Note that a single number is considered to be a range and is managed
116 : * the exact same way. A value which matches any of the ranges is considered
117 : * valid.
118 : *
119 : * Example:
120 : *
121 : * \code
122 : * "-100...100,-1000"
123 : * \endcode
124 : *
125 : * This example allows all values between -100 and +100 inclusive and also
126 : * allows the value -1000.
127 : *
128 : * \param[in] ranges The ranges used to limit the integer.
129 : */
130 52 : validator_integer::validator_integer(string_list_t const & range_list)
131 : {
132 52 : range_t range;
133 301 : for(auto r : range_list)
134 : {
135 253 : std::string::size_type const pos(r.find("..."));
136 253 : if(pos == std::string::npos)
137 : {
138 229 : if(!convert_string(r, range.f_minimum))
139 : {
140 2 : cppthread::log << cppthread::log_level_t::error
141 1 : << r
142 : << " is not a valid standalone value for your ranges;"
143 : " it must only be digits, optionally preceeded by a sign (+ or -)"
144 1 : " and not overflow an int64_t value."
145 2 : << cppthread::end;
146 1 : continue;
147 : }
148 227 : range.f_maximum = range.f_minimum;
149 : }
150 : else
151 : {
152 47 : std::string min_value(r.substr(0, pos));
153 25 : boost::trim(min_value);
154 26 : if(!convert_string(min_value, range.f_minimum))
155 : {
156 2 : cppthread::log << cppthread::log_level_t::error
157 1 : << min_value
158 : << " is not a valid value for your range's start;"
159 : " it must only be digits, optionally preceeded by a sign (+ or -)"
160 1 : " and not overflow an int64_t value."
161 2 : << cppthread::end;
162 1 : continue;
163 : }
164 :
165 46 : std::string max_value(r.substr(pos + 3));
166 24 : boost::trim(max_value);
167 25 : if(!convert_string(max_value, range.f_maximum))
168 : {
169 2 : cppthread::log << cppthread::log_level_t::error
170 1 : << max_value
171 : << " is not a valid value for your range's end;"
172 : " it must only be digits, optionally preceeded by a sign (+ or -)"
173 1 : " and not overflow an int64_t value."
174 2 : << cppthread::end;
175 1 : continue;
176 : }
177 :
178 24 : if(range.f_minimum > range.f_maximum)
179 : {
180 2 : cppthread::log << cppthread::log_level_t::error
181 1 : << min_value
182 1 : << " has to be smaller or equal to "
183 1 : << max_value
184 1 : << "; you have an invalid range."
185 2 : << cppthread::end;
186 1 : continue;
187 : }
188 : }
189 249 : f_allowed_values.push_back(range);
190 : }
191 52 : }
192 :
193 :
194 : /** \brief Return the name of this validator.
195 : *
196 : * This function returns "integer".
197 : *
198 : * \return "integer".
199 : */
200 43 : std::string validator_integer::name() const
201 : {
202 43 : return std::string("integer");
203 : }
204 :
205 :
206 : /** \brief Determine whether value is an integer.
207 : *
208 : * This function verifies that the specified value is a valid integer.
209 : *
210 : * It makes sures that the value is only composed of digits (`[0-9]+`).
211 : * It may also start with a sign (`[-+]?`).
212 : *
213 : * The function also makes sure that the value fits in an `int64_t` value.
214 : *
215 : * If ranges were defined, then the function also verifies that the
216 : * value is within at least one of the ranges.
217 : *
218 : * \todo
219 : * Add support for binary, octal, hexadecimal.
220 : *
221 : * \param[in] value The value to validate.
222 : *
223 : * \return true if the value validates.
224 : */
225 135843 : bool validator_integer::validate(std::string const & value) const
226 : {
227 135843 : std::int64_t result(0);
228 135843 : if(convert_string(value, result))
229 : {
230 51833 : if(f_allowed_values.empty())
231 : {
232 1528 : return true;
233 : }
234 :
235 282614 : for(auto f : f_allowed_values)
236 : {
237 243925 : if(result >= f.f_minimum
238 129906 : && result <= f.f_maximum)
239 : {
240 11616 : return true;
241 : }
242 : }
243 38689 : return false;
244 : }
245 :
246 84010 : return false;
247 : }
248 :
249 :
250 : /** \brief Convert a string to an std::int64_t value.
251 : *
252 : * This function is used to convert a string to an integer with full
253 : * boundary verification.
254 : *
255 : * \warning
256 : * There is no range checks in this function since it does not have access
257 : * to the ranges (i.e. it is static).
258 : *
259 : * \param[in] value The value to be converted to an integer.
260 : * \param[out] result The resulting integer.
261 : *
262 : * \return true if the conversion succeeded.
263 : */
264 136499 : bool validator_integer::convert_string(std::string const & value, std::int64_t & result)
265 : {
266 136499 : std::uint64_t integer(0);
267 136499 : char const * s(value.c_str());
268 :
269 136499 : char sign('\0');
270 136499 : if(*s == '-' || *s == '+')
271 : {
272 52369 : sign = *s;
273 52369 : ++s;
274 : }
275 :
276 136499 : if(*s == '\0')
277 : {
278 : // empty string, not considered valid
279 : //
280 8 : return false;
281 : }
282 :
283 : for(;;)
284 : {
285 1912006 : char const c(*s++);
286 1912006 : if(c == '\0')
287 : {
288 : // valid
289 : //
290 52474 : if(sign == '-')
291 : {
292 20846 : if(integer > 0x8000000000000000ULL)
293 : {
294 2 : return false;
295 : }
296 20844 : result = -integer;
297 : }
298 : else
299 : {
300 31628 : if(integer > 0x7FFFFFFFFFFFFFFFULL)
301 : {
302 3 : return false;
303 : }
304 31625 : result = integer;
305 : }
306 52469 : return true;
307 : }
308 1859532 : if(c < '0' || c > '9')
309 : {
310 : // invalid digit
311 : //
312 84013 : return false;
313 : }
314 :
315 1775519 : std::uint64_t const old(integer);
316 1775519 : integer = integer * 10 + c - '0';
317 1775519 : if(integer < old)
318 : {
319 : // overflow
320 : //
321 4 : return false;
322 : }
323 1775515 : }
324 : }
325 :
326 :
327 :
328 6 : } // namespace advgetopt
329 : // vim: ts=4 sw=4 et
|