""" jsonspec.validators.util ~~~~~~~~~~~~~~~~~~~~~~~~ """ from __future__ import absolute_import import logging import re import time from copy import deepcopy from decimal import Decimal from datetime import tzinfo, timedelta, datetime, date from six import text_type from six import integer_types from six.moves.urllib.parse import urlparse from .exceptions import ValidationError __all__ = [] number_types = (integer_types, float, Decimal) logger = logging.getLogger(__name__) HOSTNAME_TOKENS = re.compile('(?!-)[a-z\d-]{1,63}(?<!-)$', re.IGNORECASE) HOSTNAME_LAST_TOKEN = re.compile('[a-z]+$', re.IGNORECASE) EMAIL = re.compile('[^@]+@[^@]+\.[^@]+') CSS_COLORS = set([ 'aliceblue', 'antiquewhite', 'aqua', 'aquamarine', 'azure', 'beige', 'bisque', 'black', 'blanchedalmond', 'blue', 'blueviolet', 'brown', 'burlywood', 'cadetblue', 'chartreuse', 'chocolate', 'coral', 'cornflowerblue', 'cornsilk', 'crimson', 'darkblue', 'darkcyan', 'darkgoldenrod', 'darkgray', 'darkgreen', 'darkkhaki', 'darkmagenta', 'darkolivegreen', 'darkorange', 'darkorchid', 'darkred', 'darksalmon', 'darkseagreen', 'darkslateblue', 'darkslategray', 'darkturquoise', 'darkviolet', 'deeppink', 'deepskyblue', 'dimgray', 'dodgerblue', 'feldspar', 'firebrick', 'floralwhite', 'forestgreen', 'fuchsia', 'gainsboro', 'ghostwhite', 'gold', 'goldenrod', 'gray', 'green', 'greenyellow', 'honeydew', 'hotpink', 'indianred', 'indigo', 'ivory', 'khaki', 'lavender', 'lavenderblush', 'lawngreen', 'lemonchiffon', 'lightblue', 'lightcoral', 'lightcyan', 'lightgoldenrodyellow', 'lightgreen', 'lightgrey', 'lightpink', 'lightsalmon', 'lightseagreen', 'lightskyblue', 'lightslateblue', 'lightslategray', 'lightsteelblue', 'lightyellow', 'lime', 'limegreen', 'linen', 'maroon', 'mediumaquamarine', 'mediumblue', 'mediumorchid', 'mediumpurple', 'mediumseagreen', 'mediumslateblue', 'mediumspringgreen', 'mediumturquoise', 'mediumvioletred', 'midnightblue', 'mintcream', 'mistyrose', 'moccasin', 'navajowhite', 'navy', 'oldlace', 'olive', 'olivedrab', 'orange', 'orangered', 'orchid', 'palegoldenrod', 'palegreen', 'paleturquoise', 'palevioletred', 'papayawhip', 'peachpuff', 'peru', 'pink', 'plum', 'powderblue', 'purple', 'red', 'rosybrown', 'royalblue', 'saddlebrown', 'salmon', 'sandybrown', 'seagreen', 'seashell', 'sienna', 'silver', 'skyblue', 'slateblue', 'slategray', 'snow', 'springgreen', 'steelblue', 'tan', 'teal', 'thistle', 'tomato', 'turquoise', 'violetred', 'wheat', 'white', 'whitesmoke', 'yellow', 'yellowgreen']) def uncamel(name): """converts camelcase to underscore >>> uncamel('fooBar') 'foo_bar' >>> uncamel('FooBar') 'foo_bar' >>> uncamel('_fooBar') '_foo_bar' >>> uncamel('_FooBar') '__foo_bar' """ response, name = name[0].lower(), name[1:] for n in name: if n.isupper(): response += '_' + n.lower() else: response += n return response class offset(tzinfo): def __init__(self, value): self.value = value def utcoffset(self, dt): hours, minutes = self.value.split(':', 1) return timedelta(hours=int(hours), minutes=int(minutes)) def tzname(self, dt): return '{}'.format(self.value) def rfc3339_to_datetime(data): """convert a rfc3339 date representation into a Python datetime""" try: ts = time.strptime(data, '%Y-%m-%d') return date(*ts[:3]) except ValueError: pass try: dt, _, tz = data.partition('Z') if tz: tz = offset(tz) else: tz = offset('00:00') if '.' in dt and dt.rsplit('.', 1)[-1].isdigit(): ts = time.strptime(dt, '%Y-%m-%dT%H:%M:%S.%f') else: ts = time.strptime(dt, '%Y-%m-%dT%H:%M:%S') return datetime(*ts[:6], tzinfo=tz) except ValueError: raise ValueError('date-time {!r} is not a valid rfc3339 date representation'.format(data)) # noqa def validate_css_color(obj): color = obj.lower() if len(color) == 7 and re.match('^#[0-9a-f]{6}$', color): return obj elif len(color) == 4 and re.match('^#[0-9a-f]{3}$', color): return obj elif color not in CSS_COLORS: raise ValidationError('Not a css color {!r}'.format(obj)) return obj def validate_rfc3339_datetime(obj): try: rfc3339_to_datetime(obj) except ValueError: raise ValidationError('{!r} is not a valid datetime', obj) return obj def validate_utc_datetime(obj): if not obj.endswith('Z'): raise ValidationError('{!r} is not a valid datetime', obj) obj = obj[:-1] if '.' in obj: obj, milli = obj.split('.', 1) if not milli.isdigit(): raise ValidationError('{!r} is not a valid datetime', obj) try: time.strptime(obj, '%Y-%m-%dT%H:%M:%S') except ValueError: raise ValidationError('{!r} is not a valid datetime', obj) return obj def validate_utc_date(obj): try: time.strptime(obj, '%Y-%m-%d') except (TypeError, ValueError): raise ValidationError('{!r} is not a valid date', obj) return obj def validate_utc_time(obj): try: time.strptime(obj, '%H:%M:%S') except (TypeError, ValueError): raise ValidationError('{!r} is not a valid time', obj) return obj def validate_utc_millisec(obj): try: if not isinstance(obj, number_types): raise TypeError datetime.utcfromtimestamp(obj / 1000) except (TypeError, ValueError): raise ValidationError('{!r} is not a valid utc millis', obj) return obj def validate_email(obj): if not EMAIL.match(obj): raise ValidationError('{!r} is not defined') return obj def validate_hostname(obj): try: host = deepcopy(obj) if len(host) > 255: raise ValueError if host[-1] == '.': host = host[:-1] tokens = host.split('.') if not all(HOSTNAME_TOKENS.match(x) for x in tokens): raise ValueError if not HOSTNAME_LAST_TOKEN.search(tokens[-1]): raise ValueError except ValueError: raise ValidationError('{!r} is not a valid hostname'.format(obj)) return obj def validate_ipv4(obj): try: import ipaddress obj = text_type(obj) ipaddress.IPv4Address(obj) except ImportError: raise ValidationError('IPv4 relies on ipaddress package', obj) except (ipaddress.AddressValueError, ipaddress.NetmaskValueError): raise ValidationError('{!r} does not appear to ' 'be an IPv4 address'.format(obj)) return obj def validate_ipv6(obj): try: import ipaddress obj = text_type(obj) ipaddress.IPv6Address(obj) except ImportError: raise ValidationError('IPv6 relies on ipaddress package', obj) except (ipaddress.AddressValueError, ipaddress.NetmaskValueError): raise ValidationError('{!r} does not appear to ' 'be an IPv6 address'.format(obj)) return obj def validate_regex(obj): # TODO implement ECMA 262 regex import re try: re.compile(obj) except: raise ValidationError('Not a regex', obj) return obj def validate_uri(obj): try: if ':' not in obj: raise ValueError('missing scheme') urlparse(obj) except Exception as error: logger.exception(error) raise ValidationError('{!r} is not an uri'.format(obj)) return obj