X-Git-Url: http://git.ithinksw.org/philo.git/blobdiff_plain/6a9c8723936694a6b55f8b4bdcac7009c6c3cccf..ad61c0ead793098e8c9cbec326cdd3a4163f25ba:/validators.py?ds=inline diff --git a/validators.py b/validators.py index bc41d02..5ae9409 100644 --- a/validators.py +++ b/validators.py @@ -1,77 +1,159 @@ -from django.core.exceptions import ValidationError from django.utils.translation import ugettext_lazy as _ +from django.core.validators import RegexValidator +from django.core.exceptions import ValidationError +from django.template import Template, Parser, Lexer, TOKEN_BLOCK, TOKEN_VAR, TemplateSyntaxError +from django.utils import simplejson as json +from django.utils.html import escape, mark_safe +import re +from philo.utils import LOADED_TEMPLATE_ATTR -class TreeParentValidator(object): - """ - constructor takes instance and parent_attr, where instance is the model - being validated and parent_attr is where to look on that parent for the - comparison. - """ - #message = _("A tree element can't be its own parent.") - code = 'invalid' - - def __init__(self, instance, parent_attr=None, message=None, code=None): - self.instance = instance - self.parent_attr = parent_attr - self.static_message = message - if code is not None: - self.code = code - - def __call__(self, value): - """ - Validates that the self.instance is not found in the parent tree of - the node given as value. - """ - parent = value +INSECURE_TAGS = ( + 'load', + 'extends', + 'include', + 'debug', +) + + +class RedirectValidator(RegexValidator): + """Based loosely on the URLValidator, but no option to verify_exists""" + regex = re.compile( + r'^(?:https?://' # http:// or https:// + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... + r'localhost|' #localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'(?::\d+)?' # optional port + r'(?:/?|[/?#]?\S+)|' + r'[^?#\s]\S*)$', + re.IGNORECASE) + message = _(u'Enter a valid absolute or relative redirect target') + + +class URLLinkValidator(RegexValidator): + """Based loosely on the URLValidator, but no option to verify_exists""" + regex = re.compile( + r'^(?:https?://' # http:// or https:// + r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain... + r'localhost|' #localhost... + r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip + r'(?::\d+)?' # optional port + r'|)' # also allow internal links + r'(?:/?|[/?#]?\S+)$', re.IGNORECASE) + message = _(u'Enter a valid absolute or relative redirect target') + + +def json_validator(value): + try: + json.loads(value) + except Exception, e: + raise ValidationError(u'JSON decode error: %s' % e) + + +class TemplateValidationParser(Parser): + def __init__(self, tokens, allow=None, disallow=None, secure=True): + super(TemplateValidationParser, self).__init__(tokens) - while parent: - comparison=self.get_comparison(parent) - if comparison == self.instance: - # using (self.message, code=self.code) results in the admin interface - # screwing with the error message and making it be 'Enter a valid value' - raise ValidationError(self.message) - parent=parent.parent - - def get_comparison(self, parent): - if self.parent_attr and hasattr(parent, self.parent_attr): - return getattr(parent, self.parent_attr) + allow, disallow = set(allow or []), set(disallow or []) - return parent - - def get_message(self): - return self.static_message or _(u"A %s can't be its own parent." % self.instance.__class__.__name__) - message = property(get_message) + if secure: + disallow |= set(INSECURE_TAGS) + + self.allow, self.disallow, self.secure = allow, disallow, secure -class TreePositionValidator(object): - code = 'invalid' + def parse(self, parse_until=None): + if parse_until is None: + parse_until = [] + + nodelist = self.create_nodelist() + while self.tokens: + token = self.next_token() + # We only need to parse var and block tokens. + if token.token_type == TOKEN_VAR: + if not token.contents: + self.empty_variable(token) + + filter_expression = self.compile_filter(token.contents) + var_node = self.create_variable_node(filter_expression) + self.extend_nodelist(nodelist, var_node,token) + elif token.token_type == TOKEN_BLOCK: + if token.contents in parse_until: + # put token back on token list so calling code knows why it terminated + self.prepend_token(token) + return nodelist + + try: + command = token.contents.split()[0] + except IndexError: + self.empty_block_tag(token) + + if (self.allow and command not in self.allow) or (self.disallow and command in self.disallow): + self.disallowed_tag(command) + + self.enter_command(command, token) + + try: + compile_func = self.tags[command] + except KeyError: + self.invalid_block_tag(token, command, parse_until) + + try: + compiled_result = compile_func(self, token) + except TemplateSyntaxError, e: + if not self.compile_function_error(token, e): + raise + + self.extend_nodelist(nodelist, compiled_result, token) + self.exit_command() + + if parse_until: + self.unclosed_block_tag(parse_until) + + return nodelist - def __init__(self, parent, slug, obj_class, message=None, code=None): - self.parent = parent - self.slug = slug - self.obj_class = obj_class - self.static_message = message - - if code is not None: - self.code = code + def disallowed_tag(self, command): + if self.secure and command in INSECURE_TAGS: + raise ValidationError('Tag "%s" is not permitted for security reasons.' % command) + raise ValidationError('Tag "%s" is not permitted here.' % command) + + +def linebreak_iter(template_source): + # Cribbed from django/views/debug.py + yield 0 + p = template_source.find('\n') + while p >= 0: + yield p+1 + p = template_source.find('\n', p+1) + yield len(template_source) + 1 + + +class TemplateValidator(object): + def __init__(self, allow=None, disallow=None, secure=True): + self.allow = allow + self.disallow = disallow + self.secure = secure def __call__(self, value): - """ - Validates that there is no obj of obj_class with the same position - as the compared obj (value) but a different id. - """ - if not isinstance(value, self.obj_class): - raise ValidationError(_(u"The value must be an instance of %s." % self.obj_class.__name__)) - try: - obj = self.obj_class.objects.get(slug=self.slug, parent=self.parent) - - if obj.id != value.id: - raise ValidationError(self.message) - - except self.obj_class.DoesNotExist: - pass + self.validate_template(value) + except ValidationError: + raise + except Exception, e: + if hasattr(e, 'source') and isinstance(e, TemplateSyntaxError): + origin, (start, end) = e.source + template_source = origin.reload() + upto = 0 + for num, next in enumerate(linebreak_iter(template_source)): + if start >= upto and end <= next: + raise ValidationError(mark_safe("Template code invalid: \"%s\" (%s:%d).
%s" % (escape(template_source[start:end]), origin.loadname, num, e))) + upto = next + raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e)) - def get_message(self): - return self.static_message or _(u"A %s with that path (parent and slug) already exists." % self.obj_class.__name__) - message = property(get_message) + def validate_template(self, template_string): + # We want to tokenize like normal, then use a custom parser. + lexer = Lexer(template_string, None) + tokens = lexer.tokenize() + parser = TemplateValidationParser(tokens, self.allow, self.disallow, self.secure) + + for node in parser.parse(): + template = getattr(node, LOADED_TEMPLATE_ATTR, None) \ No newline at end of file