# -*- coding: utf-8 -*- "option types and option description" # Copyright (C) 2012-2013 Team tiramisu (see AUTHORS for all contributors) # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU Lesser General Public License as published by the # Free Software Foundation, either version 3 of the License, or (at your # option) any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS # FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more # details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # # The original `Config` design model is unproudly borrowed from # the rough pypy's guys: http://codespeak.net/svn/pypy/dist/pypy/config/ # the whole pypy projet is under MIT licence # ____________________________________________________________ import re import sys from IPy import IP from types import FunctionType from ..setting import undefined from ..error import ConfigError from ..i18n import _ from .baseoption import Option, validate_callback, display_list from ..autolib import carry_out_calculation class ChoiceOption(Option): """represents a choice out of several objects. The option can also have the value ``None`` """ __slots__ = tuple() _display_name = _('choice') def __init__(self, name, doc, values, default=None, values_params=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, properties=None, warnings_only=False): """ :param values: is a list of values the option can possibly take """ if isinstance(values, FunctionType): validate_callback(values, values_params, 'values') else: if values_params is not None: raise ValueError(_('values is not a function, so values_params must be None')) if not isinstance(values, tuple): # pragma: optional cover raise TypeError(_('values must be a tuple or a function for {0}' ).format(name)) session = self.getsession() self.impl_set_choice_values_params(values, values_params, session) super(ChoiceOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_params=validator_params, properties=properties, warnings_only=warnings_only, session=session) self.commit(session) def impl_get_values(self, context, current_opt=undefined): if current_opt is undefined: current_opt = self params = undefined #FIXME cache? but in context... values = self._choice_values if isinstance(values, FunctionType): if context is None: values = [] else: if params is not undefined: values_params = params else: values_params = self.impl_get_choice_values_params() values = carry_out_calculation(current_opt, context=context, callback=values, callback_params=values_params) if isinstance(values, Exception): return values if values is not undefined and not isinstance(values, list): # pragma: optional cover raise ConfigError(_('calculated values for {0} is not a list' '').format(self.impl_getname())) return values def _validate(self, value, context=undefined, current_opt=undefined): values = self.impl_get_values(context, current_opt=current_opt) if isinstance(values, Exception): return values if values is not undefined and not value in values: # pragma: optional cover if len(values) == 1: return ValueError(_('only {0} is allowed' '').format(values[0])) else: return ValueError(_('only {0} are allowed' '').format(display_list(values))) class BoolOption(Option): "represents a choice between ``True`` and ``False``" __slots__ = tuple() _display_name = _('boolean') def _validate(self, value, context=undefined, current_opt=undefined): if not isinstance(value, bool): return ValueError() # pragma: optional cover class IntOption(Option): "represents a choice of an integer" __slots__ = tuple() _display_name = _('integer') def _validate(self, value, context=undefined, current_opt=undefined): if not isinstance(value, int): return ValueError() # pragma: optional cover class FloatOption(Option): "represents a choice of a floating point number" __slots__ = tuple() _display_name = _('float') def _validate(self, value, context=undefined, current_opt=undefined): if not isinstance(value, float): return ValueError() # pragma: optional cover class StrOption(Option): "represents the choice of a string" __slots__ = tuple() _display_name = _('string') def _validate(self, value, context=undefined, current_opt=undefined): if not isinstance(value, str): return ValueError() # pragma: optional cover if sys.version_info[0] >= 3: # pragma: optional cover #UnicodeOption is same as StrOption in python 3+ class UnicodeOption(StrOption): __slots__ = tuple() pass else: class UnicodeOption(Option): "represents the choice of a unicode string" __slots__ = tuple() _empty = u'' _display_name = _('unicode string') def _validate(self, value, context=undefined, current_opt=undefined): if not isinstance(value, unicode): return ValueError() # pragma: optional cover class PasswordOption(Option): "represents the choice of a password" __slots__ = tuple() _display_name = _('password') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err class IPOption(Option): "represents the choice of an ip" __slots__ = tuple() _display_name = _('IP') def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, properties=None, private_only=False, allow_reserved=False, warnings_only=False): extra = {'_private_only': private_only, '_allow_reserved': allow_reserved} super(IPOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_params=validator_params, properties=properties, warnings_only=warnings_only, extra=extra) def _validate(self, value, context=undefined, current_opt=undefined): # sometimes an ip term starts with a zero # but this does not fit in some case, for example bind does not like it err = self._impl_valid_unicode(value) if err: return err if value.count('.') != 3: return ValueError() for val in value.split('.'): if val.startswith("0") and len(val) > 1: return ValueError() # pragma: optional cover # 'standard' validation try: IP('{0}/32'.format(value)) except ValueError: # pragma: optional cover return ValueError() def _second_level_validation(self, value, warnings_only): ip = IP('{0}/32'.format(value)) if not self._get_extra('_allow_reserved') and ip.iptype() == 'RESERVED': # pragma: optional cover if warnings_only: msg = _("shouldn't in reserved class") else: msg = _("mustn't be in reserved class") return ValueError(msg) if self._get_extra('_private_only') and not ip.iptype() == 'PRIVATE': # pragma: optional cover if warnings_only: msg = _("should be in private class") else: msg = _("must be in private class") return ValueError(msg) def _cons_in_network(self, current_opt, opts, vals, warnings_only): if len(vals) != 3: raise ConfigError(_('invalid len for vals')) # pragma: optional cover if None in vals: return ip, network, netmask = vals if IP(ip) not in IP('{0}/{1}'.format(network, netmask)): # pragma: optional cover if warnings_only: msg = _('should be in network {0}/{1} ({2}/{3})') else: msg = _('must be in network {0}/{1} ({2}/{3})') return ValueError(msg.format(network, netmask, opts[1].impl_getname(), opts[2].impl_getname())) # test if ip is not network/broadcast IP return opts[2]._cons_ip_netmask(current_opt, (opts[2], opts[0]), (netmask, ip), warnings_only) class PortOption(Option): """represents the choice of a port The port numbers are divided into three ranges: the well-known ports, the registered ports, and the dynamic or private ports. You can actived this three range. Port number 0 is reserved and can't be used. see: http://en.wikipedia.org/wiki/Port_numbers """ __slots__ = tuple() port_re = re.compile(r"^[0-9]*$") _display_name = _('port') def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, properties=None, allow_range=False, allow_zero=False, allow_wellknown=True, allow_registred=True, allow_private=False, warnings_only=False): extra = {'_allow_range': allow_range, '_min_value': None, '_max_value': None} ports_min = [0, 1, 1024, 49152] ports_max = [0, 1023, 49151, 65535] is_finally = False for index, allowed in enumerate([allow_zero, allow_wellknown, allow_registred, allow_private]): if extra['_min_value'] is None: if allowed: extra['_min_value'] = ports_min[index] elif not allowed: is_finally = True elif allowed and is_finally: raise ValueError(_('inconsistency in allowed range')) # pragma: optional cover if allowed: extra['_max_value'] = ports_max[index] if extra['_max_value'] is None: raise ValueError(_('max value is empty')) # pragma: optional cover super(PortOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_params=validator_params, properties=properties, warnings_only=warnings_only, extra=extra) def _validate(self, value, context=undefined, current_opt=undefined): if isinstance(value, int): if sys.version_info[0] >= 3: # pragma: optional cover value = str(value) else: value = unicode(value) err = self._impl_valid_unicode(value) if err: return err if self._get_extra('_allow_range') and ":" in str(value): # pragma: optional cover value = str(value).split(':') if len(value) != 2: return ValueError(_('range must have two values only')) if not value[0] < value[1]: return ValueError(_('first port in range must be' ' smaller than the second one')) else: value = [value] for val in value: if not self.port_re.search(val): return ValueError() val = int(val) if not self._get_extra('_min_value') <= val <= self._get_extra('_max_value'): # pragma: optional cover return ValueError(_('must be an integer between {0} ' 'and {1}').format(self._get_extra('_min_value'), self._get_extra('_max_value'))) class NetworkOption(Option): "represents the choice of a network" __slots__ = tuple() _display_name = _('network address') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err if value.count('.') != 3: return ValueError() for val in value.split('.'): if val.startswith("0") and len(val) > 1: return ValueError() try: IP(value) except ValueError: # pragma: optional cover return ValueError() def _second_level_validation(self, value, warnings_only): ip = IP(value) if ip.iptype() == 'RESERVED': # pragma: optional cover if warnings_only: msg = _("shouldn't be in reserved class") else: msg = _("mustn't be in reserved class") return ValueError(msg) class NetmaskOption(Option): "represents the choice of a netmask" __slots__ = tuple() _display_name = _('netmask address') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err if value.count('.') != 3: return ValueError() for val in value.split('.'): if val.startswith("0") and len(val) > 1: return ValueError() try: IP('0.0.0.0/{0}'.format(value)) except ValueError: # pragma: optional cover return ValueError() def _cons_network_netmask(self, current_opt, opts, vals, warnings_only): #opts must be (netmask, network) options if None in vals: return return self.__cons_netmask(opts, vals[0], vals[1], False, warnings_only) def _cons_ip_netmask(self, current_opt, opts, vals, warnings_only): #opts must be (netmask, ip) options if None in vals: return return self.__cons_netmask(opts, vals[0], vals[1], True, warnings_only) def __cons_netmask(self, opts, val_netmask, val_ipnetwork, make_net, warnings_only): if len(opts) != 2: raise ConfigError(_('invalid len for opts')) # pragma: optional cover msg = None try: ip = IP('{0}/{1}'.format(val_ipnetwork, val_netmask), make_net=make_net) #if cidr == 32, ip same has network if make_net and ip.prefixlen() != 32: val_ip = IP(val_ipnetwork) if ip.net() == val_ip: msg = _("this is a network with netmask {0} ({1})") if ip.broadcast() == val_ip: msg = _("this is a broadcast with netmask {0} ({1})") except ValueError: # pragma: optional cover if not make_net: msg = _('with netmask {0} ({1})') if msg is not None: # pragma: optional cover return ValueError(msg.format(val_netmask, opts[1].impl_getname())) class BroadcastOption(Option): __slots__ = tuple() _display_name = _('broadcast address') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err try: IP('{0}/32'.format(value)) except ValueError: # pragma: optional cover return ValueError() def _cons_broadcast(self, current_opt, opts, vals, warnings_only): if len(vals) != 3: raise ConfigError(_('invalid len for vals')) # pragma: optional cover if None in vals: return broadcast, network, netmask = vals if IP('{0}/{1}'.format(network, netmask)).broadcast() != IP(broadcast): return ValueError(_('with network {0}/{1} ({2}/{3})').format( network, netmask, opts[1].impl_getname(), opts[2].impl_getname())) # pragma: optional cover class DomainnameOption(Option): """represents the choice of a domain name netbios: for MS domain hostname: to identify the device domainname: fqdn: with tld, not supported yet """ __slots__ = tuple() _display_name = _('domain name') def __init__(self, name, doc, default=None, default_multi=None, requires=None, multi=False, callback=None, callback_params=None, validator=None, validator_params=None, properties=None, allow_ip=False, type_='domainname', warnings_only=False, allow_without_dot=False): if type_ not in ['netbios', 'hostname', 'domainname']: raise ValueError(_('unknown type_ {0} for hostname').format(type_)) # pragma: optional cover extra = {'_dom_type': type_} if allow_ip not in [True, False]: raise ValueError(_('allow_ip must be a boolean')) # pragma: optional cover if allow_without_dot not in [True, False]: raise ValueError(_('allow_without_dot must be a boolean')) # pragma: optional cover extra['_allow_ip'] = allow_ip extra['_allow_without_dot'] = allow_without_dot extra['_domain_re'] = re.compile(r'^[a-z\d][a-z\d\-]*$') extra['_has_upper'] = re.compile('[A-Z]') super(DomainnameOption, self).__init__(name, doc, default=default, default_multi=default_multi, callback=callback, callback_params=callback_params, requires=requires, multi=multi, validator=validator, validator_params=validator_params, properties=properties, warnings_only=warnings_only, extra=extra) def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err def _valid_length(val): if len(val) < 1: return ValueError(_("invalid length (min 1)")) if len(val) > part_name_length: return ValueError(_("invalid length (max {0})" "").format(part_name_length)) if self._get_extra('_allow_ip') is True: # pragma: optional cover try: IP('{0}/32'.format(value)) return except ValueError: pass else: try: IP('{0}/32'.format(value)) except ValueError: pass else: raise ValueError(_('must not be an IP')) if self._get_extra('_dom_type') == 'netbios': part_name_length = 15 else: part_name_length = 63 if self._get_extra('_dom_type') == 'domainname': if not self._get_extra('_allow_without_dot') and not "." in value: return ValueError(_("must have dot")) if len(value) > 255: return ValueError(_("invalid length (max 255)")) for dom in value.split('.'): err = _valid_length(dom) if err: return err else: return _valid_length(value) def _second_level_validation(self, value, warnings_only): def _valid_char(val): if self._get_extra('_has_upper').search(val): return ValueError(_('some characters are uppercase')) if not self._get_extra('_domain_re').search(val): if warnings_only: return ValueError(_('some characters may cause problems')) else: return ValueError() #not for IP if self._get_extra('_allow_ip') is True: try: IP('{0}/32'.format(value)) return except ValueError: pass if self._get_extra('_dom_type') == 'domainname': for dom in value.split('.'): return _valid_char(dom) else: return _valid_char(value) class EmailOption(DomainnameOption): __slots__ = tuple() username_re = re.compile(r"^[\w!#$%&'*+\-/=?^`{|}~.]+$") _display_name = _('email address') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err splitted = value.split('@', 1) if len(splitted) == 1: return ValueError(_('must contains one @')) username, domain = value.split('@', 1) if not self.username_re.search(username): return ValueError(_('invalid username in email address')) # pragma: optional cover err = super(EmailOption, self)._validate(domain) if err: return err return super(EmailOption, self)._second_level_validation(domain, False) def _second_level_validation(self, value, warnings_only): pass class URLOption(DomainnameOption): __slots__ = tuple() proto_re = re.compile(r'(http|https)://') path_re = re.compile(r"^[A-Za-z0-9\-\._~:/\?#\[\]@!%\$&\'\(\)\*\+,;=]+$") _display_name = _('URL') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err match = self.proto_re.search(value) if not match: # pragma: optional cover return ValueError(_('must start with http:// or ' 'https://')) value = value[len(match.group(0)):] # get domain/files splitted = value.split('/', 1) if len(splitted) == 1: domain = value files = None else: domain, files = splitted # if port in domain splitted = domain.split(':', 1) if len(splitted) == 1: domain = splitted[0] port = 0 else: domain, port = splitted if not 0 <= int(port) <= 65535: return ValueError(_('port must be an between 0 and ' '65536')) # pragma: optional cover # validate domainname err = super(URLOption, self)._validate(domain) if err: return err err = super(URLOption, self)._second_level_validation(domain, False) if err: return err # validate file if files is not None and files != '' and not self.path_re.search(files): return ValueError(_('must ends with a valid resource name')) # pragma: optional cover def _second_level_validation(self, value, warnings_only): pass class UsernameOption(Option): __slots__ = tuple() #regexp build with 'man 8 adduser' informations username_re = re.compile(r"^[a-z_][a-z0-9_-]{0,30}[$a-z0-9_-]{0,1}$") _display_name = _('username') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err match = self.username_re.search(value) if not match: return ValueError() # pragma: optional cover class FilenameOption(Option): __slots__ = tuple() path_re = re.compile(r"^[a-zA-Z0-9\-\._~/+]+$") _display_name = _('file name') def _validate(self, value, context=undefined, current_opt=undefined): err = self._impl_valid_unicode(value) if err: return err match = self.path_re.search(value) if not match: return ValueError('some characters are not allowed') # pragma: optional cover