From 8dde60658f5137d000da3c567d8eb0bd9de81a6c Mon Sep 17 00:00:00 2001 From: Mark Qvist Date: Sun, 17 May 2026 20:27:16 +0200 Subject: [PATCH] Added validate.py for spec validation --- RNS/vendor/validate.py | 537 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 537 insertions(+) create mode 100644 RNS/vendor/validate.py diff --git a/RNS/vendor/validate.py b/RNS/vendor/validate.py new file mode 100644 index 00000000..81aba21e --- /dev/null +++ b/RNS/vendor/validate.py @@ -0,0 +1,537 @@ +# validate.py +# -*- coding: utf-8 -*- +# pylint: disable= +# +# A Validator object. +# +# Copyright (C) 2005-2014: +# (name) : (email) +# Michael Foord: fuzzyman AT voidspace DOT org DOT uk +# Mark Andrews: mark AT la-la DOT com +# Nicola Larosa: nico AT tekNico DOT net +# Rob Dennis: rdennis AT gmail DOT com +# Eli Courtwright: eli AT courtwright DOT org + +# This software is licensed under the terms of the BSD license. +# http://opensource.org/licenses/BSD-3-Clause + +# ConfigObj 5 - main repository for documentation and issue tracking: +# https://github.com/DiffSK/configobj + +import re +import sys +from pprint import pprint + +__version__ = '1.0.1' + +__all__ = ( + 'dottedQuadToNum', + 'numToDottedQuad', + 'ValidateError', + 'VdtUnknownCheckError', + 'VdtParamError', + 'VdtTypeError', + 'VdtValueError', + 'VdtValueTooSmallError', + 'VdtValueTooBigError', + 'VdtValueTooShortError', + 'VdtValueTooLongError', + 'VdtMissingValue', + 'Validator', + 'is_integer', + 'is_float', + 'is_boolean', + 'is_list', + 'is_tuple', + 'is_ip_addr', + 'is_string', + 'is_int_list', + 'is_bool_list', + 'is_float_list', + 'is_string_list', + 'is_ip_addr_list', + 'is_mixed_list', + 'is_option', +) + +_list_arg = re.compile(r''' + (?: + ([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*list\( + ( + (?: + \s* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + ) + \s*,\s* + )* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + )? # last one + ) + \) + ) +''', re.VERBOSE | re.DOTALL) # two groups + +_list_members = re.compile(r''' + ( + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s=][^,=]*?) # unquoted + ) + (?: + (?:\s*,\s*)|(?:\s*$) # comma + ) +''', re.VERBOSE | re.DOTALL) # one group + +_paramstring = r''' + (?: + ( + (?: + [a-zA-Z_][a-zA-Z0-9_]*\s*=\s*list\( + (?: + \s* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + ) + \s*,\s* + )* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s\)][^,\)]*?) # unquoted + )? # last one + \) + )| + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s=][^,=]*?)| # unquoted + (?: # keyword argument + [a-zA-Z_][a-zA-Z0-9_]*\s*=\s* + (?: + (?:".*?")| # double quotes + (?:'.*?')| # single quotes + (?:[^'",\s=][^,=]*?) # unquoted + ) + ) + ) + ) + (?: + (?:\s*,\s*)|(?:\s*$) # comma + ) + ) + ''' + +_matchstring = '^%s*' % _paramstring + +def dottedQuadToNum(ip): + # import here to avoid it when ip_addr values are not used + import socket, struct + + try: + return struct.unpack('!L', + socket.inet_aton(ip.strip()))[0] + except socket.error: + raise ValueError('Not a good dotted-quad IP: %s' % ip) + return + +def numToDottedQuad(num): + # import here to avoid it when ip_addr values are not used + import socket, struct + + # no need to intercept here, 4294967295L is fine + if num > int(4294967295) or num < 0: + raise ValueError('Not a good numeric IP: %s' % num) + try: + return socket.inet_ntoa( + struct.pack('!L', int(num))) + except (socket.error, struct.error, OverflowError): + raise ValueError('Not a good numeric IP: %s' % num) + +class ValidateError(Exception): + """ + This error indicates that the check failed. + It can be the base class for more specific errors. + """ + +class VdtMissingValue(ValidateError): + """No value was supplied to a check that needed one.""" + +class VdtUnknownCheckError(ValidateError): + def __init__(self, value): + ValidateError.__init__(self, 'the check "{}" is unknown.'.format(value)) + + +class VdtParamError(SyntaxError): + NOT_GIVEN = object() + + def __init__(self, name_or_msg, value=NOT_GIVEN): + if value is self.NOT_GIVEN: + SyntaxError.__init__(self, name_or_msg) + else: + SyntaxError.__init__(self, 'passed an incorrect value "{}" for parameter "{}".'.format(value, name_or_msg)) + + +class VdtTypeError(ValidateError): + def __init__(self, value): + ValidateError.__init__(self, 'the value "{}" is of the wrong type.'.format(value)) + + +class VdtValueError(ValidateError): + def __init__(self, value): + ValidateError.__init__(self, 'the value "{}" is unacceptable.'.format(value)) + + +class VdtValueTooSmallError(VdtValueError): + def __init__(self, value): + ValidateError.__init__(self, 'the value "{}" is too small.'.format(value)) + + +class VdtValueTooBigError(VdtValueError): + def __init__(self, value): + ValidateError.__init__(self, 'the value "{}" is too big.'.format(value)) + + +class VdtValueTooShortError(VdtValueError): + def __init__(self, value): + ValidateError.__init__( + self, + 'the value "{}" is too short.'.format(value)) + +class VdtValueTooLongError(VdtValueError): + def __init__(self, value): + ValidateError.__init__(self, 'the value "{}" is too long.'.format(value)) + +class Validator(object): + # this regex does the initial parsing of the checks + _func_re = re.compile(r'([^\(\)]+?)\((.*)\)', re.DOTALL) + + # this regex takes apart keyword arguments + _key_arg = re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)\s*=\s*(.*)$', re.DOTALL) + + + # this regex finds keyword=list(....) type values + _list_arg = _list_arg + + # this regex takes individual values out of lists - in one pass + _list_members = _list_members + + # These regexes check a set of arguments for validity + # and then pull the members out + _paramfinder = re.compile(_paramstring, re.VERBOSE | re.DOTALL) + _matchfinder = re.compile(_matchstring, re.VERBOSE | re.DOTALL) + + def __init__(self, functions=None): + self.functions = { + '': self._pass, + 'integer': is_integer, + 'float': is_float, + 'boolean': is_boolean, + 'ip_addr': is_ip_addr, + 'string': is_string, + 'list': is_list, + 'tuple': is_tuple, + 'int_list': is_int_list, + 'float_list': is_float_list, + 'bool_list': is_bool_list, + 'ip_addr_list': is_ip_addr_list, + 'string_list': is_string_list, + 'mixed_list': is_mixed_list, + 'pass': self._pass, + 'option': is_option, + 'force_list': force_list, + } + if functions is not None: + self.functions.update(functions) + # tekNico: for use by ConfigObj + self.baseErrorClass = ValidateError + self._cache = {} + + def check(self, check, value, missing=False): + fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) + + if missing: + if default is None: + # no information needed here - to be handled by caller + raise VdtMissingValue() + value = self._handle_none(default) + + if value is None: + return None + + return self._check_value(value, fun_name, fun_args, fun_kwargs) + + def _handle_none(self, value): + if value == 'None': + return None + elif value in ("'None'", '"None"'): + # Special case a quoted None + value = self._unquote(value) + return value + + def _parse_with_caching(self, check): + if check in self._cache: + fun_name, fun_args, fun_kwargs, default = self._cache[check] + # We call list and dict below to work with *copies* of the data + # rather than the original (which are mutable of course) + fun_args = list(fun_args) + fun_kwargs = dict(fun_kwargs) + else: + fun_name, fun_args, fun_kwargs, default = self._parse_check(check) + fun_kwargs = {str(key): value for (key, value) in list(fun_kwargs.items())} + self._cache[check] = fun_name, list(fun_args), dict(fun_kwargs), default + return fun_name, fun_args, fun_kwargs, default + + def _check_value(self, value, fun_name, fun_args, fun_kwargs): + try: + fun = self.functions[fun_name] + except KeyError: + raise VdtUnknownCheckError(fun_name) + else: + return fun(value, *fun_args, **fun_kwargs) + + def _parse_check(self, check): + fun_match = self._func_re.match(check) + if fun_match: + fun_name = fun_match.group(1) + arg_string = fun_match.group(2) + arg_match = self._matchfinder.match(arg_string) + if arg_match is None: + # Bad syntax + raise VdtParamError('Bad syntax in check "%s".' % check) + fun_args = [] + fun_kwargs = {} + # pull out args of group 2 + for arg in self._paramfinder.findall(arg_string): + # args may need whitespace removing (before removing quotes) + arg = arg.strip() + listmatch = self._list_arg.match(arg) + if listmatch: + key, val = self._list_handle(listmatch) + fun_kwargs[key] = val + continue + keymatch = self._key_arg.match(arg) + if keymatch: + val = keymatch.group(2) + if not val in ("'None'", '"None"'): + # Special case a quoted None + val = self._unquote(val) + fun_kwargs[keymatch.group(1)] = val + continue + + fun_args.append(self._unquote(arg)) + else: + # allows for function names without (args) + return check, (), {}, None + + # Default must be deleted if the value is specified too, + # otherwise the check function will get a spurious "default" keyword arg + default = fun_kwargs.pop('default', None) + return fun_name, fun_args, fun_kwargs, default + + def _unquote(self, val): + if (len(val) >= 2) and (val[0] in ("'", '"')) and (val[0] == val[-1]): + val = val[1:-1] + return val + + def _list_handle(self, listmatch): + out = [] + name = listmatch.group(1) + args = listmatch.group(2) + for arg in self._list_members.findall(args): + out.append(self._unquote(arg)) + return name, out + + def _pass(self, value): + return value + + def get_default_value(self, check): + fun_name, fun_args, fun_kwargs, default = self._parse_with_caching(check) + if default is None: + raise KeyError('Check "%s" has no default value.' % check) + value = self._handle_none(default) + if value is None: + return value + return self._check_value(value, fun_name, fun_args, fun_kwargs) + +def _is_num_param(names, values, to_float=False): + fun = to_float and float or int + out_params = [] + for (name, val) in zip(names, values): + if val is None: + out_params.append(val) + elif isinstance(val, (int, float, str)): + try: + out_params.append(fun(val)) + except ValueError: + raise VdtParamError(name, val) + else: + raise VdtParamError(name, val) + return out_params + +# built in checks +# you can override these by setting the appropriate name +# in Validator.functions +# note: if the params are specified wrongly in your input string, +# you will also raise errors. +def is_integer(value, min=None, max=None): + (min_val, max_val) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking + ('min', 'max'), (min, max)) + if not isinstance(value, (int, str)): + raise VdtTypeError(value) + if isinstance(value, str): + # if it's a string - does it represent an integer ? + try: + value = int(value) + except ValueError: + raise VdtTypeError(value) + if (min_val is not None) and (value < min_val): + raise VdtValueTooSmallError(value) + if (max_val is not None) and (value > max_val): + raise VdtValueTooBigError(value) + return value + + +def is_float(value, min=None, max=None): + (min_val, max_val) = _is_num_param( + ('min', 'max'), (min, max), to_float=True) + if not isinstance(value, (int, float, str)): + raise VdtTypeError(value) + if not isinstance(value, float): + # if it's a string - does it represent a float ? + try: + value = float(value) + except ValueError: + raise VdtTypeError(value) + if (min_val is not None) and (value < min_val): + raise VdtValueTooSmallError(value) + if (max_val is not None) and (value > max_val): + raise VdtValueTooBigError(value) + return value + +bool_dict = { + True: True, 'on': True, '1': True, 'true': True, 'yes': True, + False: False, 'off': False, '0': False, 'false': False, 'no': False, +} + +def is_boolean(value): + if isinstance(value, str): + try: + return bool_dict[value.lower()] + except KeyError: + raise VdtTypeError(value) + # we do an equality test rather than an identity test + # this ensures Python 2.2 compatibility + # and allows 0 and 1 to represent True and False + if value == False: + return False + elif value == True: + return True + else: + raise VdtTypeError(value) + + +def is_ip_addr(value): + if not isinstance(value, str): + raise VdtTypeError(value) + value = value.strip() + try: + dottedQuadToNum(value) + except ValueError: + raise VdtValueError(value) + return value + + +def is_list(value, min=None, max=None): + (min_len, max_len) = _is_num_param( # pylint: disable=unbalanced-tuple-unpacking + ('min', 'max'), (min, max)) + if isinstance(value, str): + raise VdtTypeError(value) + try: + num_members = len(value) + except TypeError: + raise VdtTypeError(value) + if min_len is not None and num_members < min_len: + raise VdtValueTooShortError(value) + if max_len is not None and num_members > max_len: + raise VdtValueTooLongError(value) + return list(value) + + +def is_tuple(value, min=None, max=None): + return tuple(is_list(value, min, max)) + +def is_string(value, min=None, max=None): + if not isinstance(value, str): + raise VdtTypeError(value) + (min_len, max_len) = _is_num_param( + ('min', 'max'), (min, max)) + try: + num_members = len(value) + except TypeError: + raise VdtTypeError(value) + if min_len is not None and num_members < min_len: + raise VdtValueTooShortError(value) + if max_len is not None and num_members > max_len: + raise VdtValueTooLongError(value) + return value + + +def is_int_list(value, min=None, max=None): + return [is_integer(mem) for mem in is_list(value, min, max)] + +def is_bool_list(value, min=None, max=None): + return [is_boolean(mem) for mem in is_list(value, min, max)] + +def is_float_list(value, min=None, max=None): + return [is_float(mem) for mem in is_list(value, min, max)] + +def is_string_list(value, min=None, max=None): + if isinstance(value, str): + raise VdtTypeError(value) + return [is_string(mem) for mem in is_list(value, min, max)] + +def is_ip_addr_list(value, min=None, max=None): + return [is_ip_addr(mem) for mem in is_list(value, min, max)] + +def force_list(value, min=None, max=None): + if not isinstance(value, (list, tuple)): + value = [value] + return is_list(value, min, max) + +fun_dict = { + int: is_integer, + 'int': is_integer, + 'integer': is_integer, + float: is_float, + 'float': is_float, + 'ip_addr': is_ip_addr, + str: is_string, + 'str': is_string, + 'string': is_string, + bool: is_boolean, + 'bool': is_boolean, + 'boolean': is_boolean, +} + +def is_mixed_list(value, *args): + try: length = len(value) + except TypeError: raise VdtTypeError(value) + if length < len(args): raise VdtValueTooShortError(value) + elif length > len(args): raise VdtValueTooLongError(value) + try: return [fun_dict[arg](val) for arg, val in zip(args, value)] + except KeyError as cause: raise VdtParamError('mixed_list', cause) + + +def is_option(value, *options): + if not isinstance(value, str): raise VdtTypeError(value) + if not value in options: raise VdtValueError(value) + return value +