Line data Source code
1 : // Copyright (c) 2006-2024 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/trim_string.h> 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 : 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 : 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 : * The start and end values of a range are optional. If not specified, the 120 : * start value is set to the minimum int64_t value. If the not specified, 121 : * the end value is set to the maximum int64_t value. 122 : * 123 : * Examples: 124 : * 125 : * \code 126 : * "-100...100,-1000" 127 : * \endcode 128 : * 129 : * This example allows all values between -100 and +100 inclusive and also 130 : * allows the value -1000. 131 : * 132 : * \code 133 : * "1..." 134 : * \endcode 135 : * 136 : * This example allows all positive values. 137 : * 138 : * \param[in] ranges The ranges used to limit the integer. 139 : */ 140 52 : validator_integer::validator_integer(string_list_t const & range_list) 141 : { 142 52 : range_t range; 143 277 : for(auto r : range_list) 144 : { 145 225 : std::string::size_type const pos(r.find("...")); 146 225 : if(pos == std::string::npos) 147 : { 148 200 : if(!convert_string(r, range.f_minimum)) 149 : { 150 2 : cppthread::log << cppthread::log_level_t::error 151 1 : << r 152 : << " is not a valid standalone value for your ranges;" 153 : " it must only be digits, optionally preceeded by a sign (+ or -)" 154 1 : " and not overflow an int64_t value." 155 2 : << cppthread::end; 156 1 : continue; 157 : } 158 199 : range.f_maximum = range.f_minimum; 159 : } 160 : else 161 : { 162 75 : std::string const min_value(snapdev::trim_string(r.substr(0, pos))); 163 25 : if(!min_value.empty()) 164 : { 165 25 : if(!convert_string(min_value, range.f_minimum)) 166 : { 167 2 : cppthread::log << cppthread::log_level_t::error 168 1 : << min_value 169 : << " is not a valid value for your range's start;" 170 : " it must only be digits, optionally preceeded by a sign (+ or -)" 171 1 : " and not overflow an int64_t value." 172 2 : << cppthread::end; 173 1 : continue; 174 : } 175 : } 176 : 177 72 : std::string const max_value(snapdev::trim_string(r.substr(pos + 3))); 178 24 : if(!max_value.empty()) 179 : { 180 24 : if(!convert_string(max_value, range.f_maximum)) 181 : { 182 2 : cppthread::log << cppthread::log_level_t::error 183 1 : << max_value 184 : << " is not a valid value for your range's end;" 185 : " it must only be digits, optionally preceeded by a sign (+ or -)" 186 1 : " and not overflow an int64_t value." 187 2 : << cppthread::end; 188 1 : continue; 189 : } 190 : } 191 : 192 23 : if(range.f_minimum > range.f_maximum) 193 : { 194 2 : cppthread::log << cppthread::log_level_t::error 195 1 : << min_value 196 1 : << " has to be smaller or equal to " 197 1 : << max_value 198 1 : << "; you have an invalid range." 199 2 : << cppthread::end; 200 1 : continue; 201 : } 202 27 : } 203 221 : f_allowed_values.push_back(range); 204 225 : } 205 52 : } 206 : 207 : 208 : /** \brief Return the name of this validator. 209 : * 210 : * This function returns "integer". 211 : * 212 : * \return "integer". 213 : */ 214 43 : std::string validator_integer::name() const 215 : { 216 43 : return std::string("integer"); 217 : } 218 : 219 : 220 : /** \brief Determine whether value is an integer. 221 : * 222 : * This function verifies that the specified value is a valid integer. 223 : * 224 : * It makes sures that the value is only composed of digits (`[0-9]+`). 225 : * It may also start with a sign (`[-+]?`). 226 : * 227 : * The function also makes sure that the value fits in an `int64_t` value. 228 : * 229 : * If ranges were defined, then the function also verifies that the 230 : * value is within at least one of the ranges. 231 : * 232 : * \todo 233 : * Add support for binary, octal, hexadecimal. 234 : * 235 : * \param[in] value The value to validate. 236 : * 237 : * \return true if the value validates. 238 : */ 239 135631 : bool validator_integer::validate(std::string const & value) const 240 : { 241 135631 : std::int64_t result(0); 242 135631 : if(convert_string(value, result)) 243 : { 244 51621 : if(f_allowed_values.empty()) 245 : { 246 1494 : return true; 247 : } 248 : 249 254258 : for(auto f : f_allowed_values) 250 : { 251 216016 : if(result >= f.f_minimum 252 120511 : && result <= f.f_maximum) 253 : { 254 11885 : return true; 255 : } 256 : } 257 38242 : return false; 258 : } 259 : 260 84010 : return false; 261 : } 262 : 263 : 264 : /** \brief Convert a string to an std::int64_t value. 265 : * 266 : * This function is used to convert a string to an integer with full 267 : * boundary verification. 268 : * 269 : * \warning 270 : * There is no range checks in this function since it does not have access 271 : * to the ranges (i.e. it is static). 272 : * 273 : * \param[in] value The value to be converted to an integer. 274 : * \param[out] result The resulting integer. 275 : * 276 : * \return true if the conversion succeeded. 277 : */ 278 136266 : bool validator_integer::convert_string(std::string const & value, std::int64_t & result) 279 : { 280 136266 : std::uint64_t integer(0); 281 136266 : char const * s(value.c_str()); 282 : 283 136266 : char sign('\0'); 284 136266 : if(*s == '-' || *s == '+') 285 : { 286 52822 : sign = *s; 287 52822 : ++s; 288 : } 289 : 290 136266 : if(*s == '\0') 291 : { 292 : // empty string, not considered valid 293 : // 294 8 : return false; 295 : } 296 : 297 : for(;;) 298 : { 299 1906720 : char const c(*s++); 300 1906720 : if(c == '\0') 301 : { 302 : // valid 303 : // 304 52241 : if(sign == '-') 305 : { 306 21110 : if(integer > 0x8000000000000000ULL) 307 : { 308 2 : return false; 309 : } 310 21108 : result = -integer; 311 : } 312 : else 313 : { 314 31131 : if(integer > 0x7FFFFFFFFFFFFFFFULL) 315 : { 316 3 : return false; 317 : } 318 31128 : result = integer; 319 : } 320 52236 : return true; 321 : } 322 1854479 : if(c < '0' || c > '9') 323 : { 324 : // invalid digit 325 : // 326 84013 : return false; 327 : } 328 : 329 1770466 : std::uint64_t const old(integer); 330 1770466 : integer = integer * 10 + c - '0'; 331 1770466 : if(integer < old) 332 : { 333 : // overflow 334 : // 335 4 : return false; 336 : } 337 1770462 : } 338 : } 339 : 340 : 341 : 342 : } // namespace advgetopt 343 : // vim: ts=4 sw=4 et