LCOV - code coverage report
Current view: top level - advgetopt - validator.cpp (source / functions) Hit Total Coverage
Test: coverage.info Lines: 177 177 100.0 %
Date: 2019-07-15 03:11:49 Functions: 22 26 84.6 %
Legend: Lines: hit not hit

          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

Generated by: LCOV version 1.12