Line data Source code
1 : /*
2 : * File:
3 : * advgetopt/validator.cpp -- advanced get option implementation
4 : *
5 : * License:
6 : * Copyright (c) 2006-2019 Made to Order Software Corp. All Rights Reserved
7 : *
8 : * https://snapwebsites.org/
9 : * contact@m2osw.com
10 : *
11 : * This program is free software; you can redistribute it and/or modify
12 : * it under the terms of the GNU General Public License as published by
13 : * the Free Software Foundation; either version 2 of the License, or
14 : * (at your option) any later version.
15 : *
16 : * This program is distributed in the hope that it will be useful,
17 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 : * GNU General Public License for more details.
20 : *
21 : * You should have received a copy of the GNU General Public License along
22 : * with this program; if not, write to the Free Software Foundation, Inc.,
23 : * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
24 : *
25 : * Authors:
26 : * Alexis Wilke alexis@m2osw.com
27 : * Doug Barbieri doug@m2osw.com
28 : */
29 :
30 : /** \file
31 : * \brief Advanced getopt version functions.
32 : *
33 : * The advgetopt environment is versioned. The functions available here
34 : * give you access to the version, in case you wanted to make sure you
35 : * had a minimum version or had some special case options when you
36 : * want to be able to support various versions.
37 : */
38 :
39 : // self
40 : //
41 : #include "advgetopt/validator.h"
42 :
43 :
44 : // advgetopt lib
45 : //
46 : #include "advgetopt/exception.h"
47 : #include "advgetopt/log.h"
48 :
49 :
50 : // snapdev lib
51 : //
52 : #include <snapdev/not_used.h>
53 :
54 :
55 : // boost lib
56 : //
57 : #include <boost/algorithm/string/trim.hpp>
58 :
59 :
60 : // last include
61 : //
62 : #include <snapdev/poison.h>
63 :
64 :
65 :
66 :
67 : namespace advgetopt
68 : {
69 :
70 :
71 :
72 : namespace
73 : {
74 :
75 :
76 2 : std::map<std::string, validator_factory const *> g_validator_factories;
77 :
78 :
79 2 : class validator_integer_factory
80 : : public validator_factory
81 : {
82 : public:
83 2 : validator_integer_factory()
84 2 : {
85 2 : validator::register_validator(*this);
86 2 : }
87 :
88 4 : virtual std::string get_name() const override
89 : {
90 4 : return std::string("integer");
91 : }
92 :
93 48 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
94 : {
95 48 : snap::NOTUSED(data); // ignore `data`
96 48 : return std::make_shared<validator_integer>(data);
97 : }
98 : };
99 :
100 2 : validator_integer_factory g_validator_integer_factory;
101 :
102 :
103 :
104 2 : class validator_regex_factory
105 : : public validator_factory
106 : {
107 : public:
108 2 : validator_regex_factory()
109 2 : {
110 2 : validator::register_validator(*this);
111 2 : }
112 :
113 4 : virtual std::string get_name() const override
114 : {
115 4 : return std::string("regex");
116 : }
117 :
118 18 : virtual std::shared_ptr<validator> create(string_list_t const & data) const override
119 : {
120 18 : return std::make_shared<validator_regex>(data);
121 : }
122 : };
123 :
124 2 : validator_regex_factory g_validator_regex_factory;
125 :
126 :
127 :
128 : } // no name namespace
129 :
130 :
131 :
132 : /** \brief The destructor to ease derived classes.
133 : *
134 : * At this point this destructor does nothing more than help with the
135 : * virtual table.
136 : */
137 5 : validator_factory::~validator_factory()
138 : {
139 5 : }
140 :
141 :
142 :
143 :
144 :
145 :
146 : /** \brief The validator destructor to support virtuals.
147 : *
148 : * This destructor is defined so virtual functions work as expected including
149 : * the deleter.
150 : */
151 66 : validator::~validator()
152 : {
153 66 : }
154 :
155 :
156 : /** \fn std::string const & validator::name() const;
157 : * \brief Return the name of the validator.
158 : *
159 : * The name() function is used to get the name of the validator.
160 : * Validators are recognized by name and added to your options
161 : * using their name.
162 : *
163 : * Note that when an option specifies a validator which it can't find,
164 : * then an error occurs.
165 : *
166 : * \return The name of the validator.
167 : */
168 :
169 :
170 : /** \fn bool validator::validate(std::string const & value) const;
171 : * \brief Return true if \p value validates agains this validator.
172 : *
173 : * The function parses the \p value parameter and if it matches the
174 : * allowed parameters, then it returns true.
175 : *
176 : * \param[in] value The value to validate.
177 : *
178 : * \return true if the value validates.
179 : */
180 :
181 :
182 5 : void validator::register_validator(validator_factory const & factory)
183 : {
184 5 : auto it(g_validator_factories.find(factory.get_name()));
185 5 : if(it != g_validator_factories.end())
186 : {
187 : throw getopt_exception_logic(
188 : "you have two or more validator factories named \""
189 2 : + factory.get_name()
190 3 : + "\".");
191 : }
192 4 : g_validator_factories[factory.get_name()] = &factory;
193 4 : }
194 :
195 :
196 67 : validator::pointer_t validator::create(std::string const & name, string_list_t const & data)
197 : {
198 67 : auto it(g_validator_factories.find(name));
199 67 : if(it == g_validator_factories.end())
200 : {
201 1 : return validator::pointer_t();
202 : }
203 :
204 66 : return it->second->create(data);
205 : }
206 :
207 :
208 : /** \brief Set the validator for this option.
209 : *
210 : * This function parses the specified name and optional parameters and
211 : * create a corresponding validator for this option.
212 : *
213 : * The \p name_and_params string can be defined as:
214 : *
215 : * \code
216 : * <validator-name>(<param1>, <param2>, ...)
217 : * \endcode
218 : *
219 : * The list of parameters is optional. There may be an empty, just one,
220 : * or any number of parameters. How the parameters are parsed is left
221 : * to the validator to decide.
222 : *
223 : * If the input string is empty, the current validator, if one is
224 : * installed, gets removed.
225 : *
226 : * \param[in] name_and_params The validator name and parameters.
227 : */
228 34 : validator::pointer_t validator::create(std::string const & name_and_params)
229 : {
230 34 : if(name_and_params.empty())
231 : {
232 15 : return validator::pointer_t();
233 : }
234 :
235 38 : if(name_and_params.length() >= 2
236 19 : && name_and_params[0] == '/')
237 : {
238 : // for the regex we have a special case
239 : //
240 14 : string_list_t data{name_and_params};
241 7 : return create("regex", data);
242 : }
243 : else
244 : {
245 12 : std::string::size_type const params(name_and_params.find('('));
246 24 : std::string name(name_and_params);
247 24 : string_list_t data;
248 12 : if(params != std::string::npos)
249 : {
250 10 : if(name_and_params.back() != ')')
251 : {
252 : throw getopt_exception_logic(
253 : "invalid validator parameter definition: \""
254 8 : + name_and_params
255 12 : + "\", the ')' is missing.");
256 : }
257 6 : name = name_and_params.substr(0, params);
258 18 : split_string(name_and_params.substr(params + 1, name_and_params.length() - params - 2)
259 : , data
260 12 : , {","});
261 : }
262 8 : return create(name, data);
263 : }
264 : }
265 :
266 :
267 :
268 :
269 :
270 :
271 : /** \brief Initialize the integer validator.
272 : *
273 : * The constructor accepts a string with values and ranges which are
274 : * used to limit the values that can be used with this parameter.
275 : *
276 : * Remember that the default does not have to be included in these
277 : * values. It will still be viewed as \em valid.
278 : *
279 : * The string uses the following format:
280 : *
281 : * \code
282 : * start: range
283 : * | start ',' range
284 : *
285 : * range: number
286 : * | number '...' number
287 : *
288 : * number: [-+]?[0-9]+
289 : * \endcode
290 : *
291 : * Note that a single number is considered to be a range and is managed
292 : * the exact same way. A value which matches any of the ranges is considered
293 : * valid.
294 : *
295 : * Example:
296 : *
297 : * \code
298 : * "-100...100,-1000"
299 : * \endcode
300 : *
301 : * This example allows all values between -100 and +100 inclusive and also
302 : * allows the value -1000.
303 : *
304 : * \param[in] ranges The ranges used to limit the integer.
305 : */
306 48 : validator_integer::validator_integer(string_list_t const & range_list)
307 : {
308 48 : range_t range;
309 290 : for(auto r : range_list)
310 : {
311 246 : std::string::size_type const pos(r.find("..."));
312 246 : if(pos == std::string::npos)
313 : {
314 222 : if(!convert_string(r, range.f_minimum))
315 : {
316 2 : log << log_level_t::error
317 1 : << r
318 : << " is not a valid value for your ranges;"
319 : " it must only be digits, optionally preceeded by a sign (+ or -)"
320 1 : " and not overflow an int64_t value."
321 1 : << end;
322 1 : continue;
323 : }
324 221 : range.f_maximum = range.f_minimum;
325 : }
326 : else
327 : {
328 45 : std::string min_value(r.substr(0, pos));
329 24 : boost::trim(min_value);
330 24 : if(!convert_string(min_value, range.f_minimum))
331 : {
332 2 : log << log_level_t::error
333 1 : << min_value
334 : << " is not a valid value for your ranges;"
335 : " it must only be digits, optionally preceeded by a sign (+ or -)"
336 1 : " and not overflow an int64_t value."
337 1 : << end;
338 1 : continue;
339 : }
340 :
341 44 : std::string max_value(r.substr(pos + 3));
342 23 : boost::trim(max_value);
343 23 : if(!convert_string(max_value, range.f_maximum))
344 : {
345 2 : log << log_level_t::error
346 1 : << max_value
347 : << " is not a valid value for your ranges;"
348 : " it must only be digits, optionally preceeded by a sign (+ or -)"
349 1 : " and not overflow an int64_t value."
350 1 : << end;
351 1 : continue;
352 : }
353 :
354 22 : if(range.f_minimum > range.f_maximum)
355 : {
356 2 : log << log_level_t::error
357 1 : << min_value
358 1 : << " has to be smaller or equal to "
359 1 : << max_value
360 1 : << "; you have an invalid range."
361 1 : << end;
362 1 : continue;
363 : }
364 : }
365 242 : f_allowed_values.push_back(range);
366 : }
367 48 : }
368 :
369 :
370 : /** \brief Return the name of this validator.
371 : *
372 : * This function returns "integer".
373 : *
374 : * \return "integer".
375 : */
376 42 : std::string const validator_integer::name() const
377 : {
378 42 : return std::string("integer");
379 : }
380 :
381 :
382 : /** \brief Determine whether value is an integer.
383 : *
384 : * This function verifies that the specified value is a valid integer.
385 : *
386 : * It makes sures that the value is only composed of digits (`[0-9]+`).
387 : * It may also start with a sign (`[-+]?`).
388 : *
389 : * The function also makes sure that the value fits in an `int64_t` value.
390 : *
391 : * \todo
392 : * Add support for binary, octal, hexadecimal.
393 : *
394 : * \param[in] value The value to validate.
395 : *
396 : * \return true if the value validates.
397 : */
398 135786 : bool validator_integer::validate(std::string const & value) const
399 : {
400 135786 : std::int64_t result(0);
401 135786 : if(convert_string(value, result))
402 : {
403 51776 : if(f_allowed_values.empty())
404 : {
405 1548 : return true;
406 : }
407 :
408 281396 : for(auto f : f_allowed_values)
409 : {
410 240226 : if(result >= f.f_minimum
411 132997 : && result <= f.f_maximum)
412 : {
413 9058 : return true;
414 : }
415 : }
416 41170 : return false;
417 : }
418 :
419 84010 : return false;
420 : }
421 :
422 :
423 : /** \brief Convert a string to an std::int64_t value.
424 : *
425 : * This function is used to convert a string to an integer with full
426 : * boundary verification.
427 : *
428 : * The result can also get checked against ranges as defined in the
429 : * constructor.
430 : *
431 : * \return true if the conversion succeeded.
432 : */
433 136157 : bool validator_integer::convert_string(std::string const & value, std::int64_t & result)
434 : {
435 136157 : std::uint64_t integer(0);
436 136157 : char const * s(value.c_str());
437 :
438 136157 : char sign('\0');
439 136157 : if(*s == '-' || *s == '+')
440 : {
441 52272 : sign = *s;
442 52272 : ++s;
443 : }
444 :
445 136157 : if(*s == '\0')
446 : {
447 : // empty string, not considered valid
448 : //
449 8 : return false;
450 : }
451 :
452 1774981 : for(;;)
453 : {
454 1911130 : char const c(*s++);
455 1911130 : if(c == '\0')
456 : {
457 : // valid
458 : //
459 52138 : if(sign == '-')
460 : {
461 20817 : if(integer > 0x8000000000000000ULL)
462 : {
463 2 : return false;
464 : }
465 20815 : result = -integer;
466 : }
467 : else
468 : {
469 31321 : if(integer > 0x7FFFFFFFFFFFFFFFULL)
470 : {
471 3 : return false;
472 : }
473 31318 : result = integer;
474 : }
475 52133 : return true;
476 : }
477 1858992 : if(c < '0' || c > '9')
478 : {
479 : // invalid digit
480 : //
481 84007 : return false;
482 : }
483 :
484 1774985 : std::uint64_t const old(integer);
485 1774985 : integer = integer * 10 + c - '0';
486 1774985 : if(integer < old)
487 : {
488 : // overflow
489 : //
490 4 : return false;
491 : }
492 : }
493 : }
494 :
495 :
496 :
497 :
498 :
499 :
500 :
501 :
502 :
503 :
504 18 : validator_regex::validator_regex(string_list_t const & regex_list)
505 : {
506 18 : if(regex_list.size() > 1)
507 : {
508 8 : log << log_level_t::error
509 4 : << "validator_regex() only supports one parameter; "
510 12 : << regex_list.size()
511 4 : << " were supplied; single or double quotation may be required?"
512 4 : << end;
513 4 : return;
514 : }
515 :
516 28 : std::string regex;
517 14 : if(!regex_list.empty())
518 : {
519 14 : regex = regex_list[0];
520 : }
521 14 : std::regex::flag_type flags = std::regex_constants::extended;
522 28 : if(regex.length() >= 2
523 14 : && regex[0] == '/')
524 : {
525 11 : auto it(regex.end());
526 30 : for(--it; it != regex.begin(); --it)
527 : {
528 29 : if(*it == '/')
529 : {
530 10 : break;
531 : }
532 19 : switch(*it)
533 : {
534 : case 'i':
535 4 : flags |= std::regex_constants::icase;
536 4 : break;
537 :
538 : default:
539 30 : log << log_level_t::error
540 15 : << "unsupported regex flag "
541 30 : << *it
542 15 : << " in regular expression \""
543 15 : << regex
544 15 : << "\"."
545 15 : << end;
546 15 : break;
547 :
548 : }
549 : }
550 11 : if(it == regex.begin())
551 : {
552 2 : log << log_level_t::error
553 1 : << "invalid regex definition, ending / is missing in \""
554 1 : << regex
555 1 : << "\"."
556 1 : << end;
557 :
558 1 : f_regex = std::regex(std::string(regex.begin() + 1, regex.end()), flags);
559 : }
560 : else
561 : {
562 10 : f_regex = std::regex(std::string(regex.begin() + 1, it), flags);
563 : }
564 : }
565 : else
566 : {
567 3 : f_regex = std::regex(regex, flags);
568 : }
569 : }
570 :
571 :
572 : /** \brief Return the name of this validator.
573 : *
574 : * This function returns "regex".
575 : *
576 : * \return "regex".
577 : */
578 7 : std::string const validator_regex::name() const
579 : {
580 7 : return std::string("regex");
581 : }
582 :
583 :
584 : /** \brief Check the value against a regular expression.
585 : *
586 : * This function is used to match the value of an argument against a
587 : * regular expression. It returns true when it does match.
588 : *
589 : * \param[in] value The value to be validated.
590 : *
591 : * \return true on a match.
592 : */
593 75 : bool validator_regex::validate(std::string const & value) const
594 : {
595 150 : std::smatch info;
596 150 : return std::regex_match(value, info, f_regex);
597 : }
598 :
599 :
600 :
601 :
602 :
603 6 : } // namespace advgetopt
604 : // vim: ts=4 sw=4 et
|