-from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
-from django.core.validators import URLValidator
+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
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)
-
-
-class TreePositionValidator(object):
- code = 'invalid'
-
- 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
+ if secure:
+ disallow |= set(INSECURE_TAGS)
+
+ self.allow, self.disallow, self.secure = allow, disallow, 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__))
+ def parse(self, parse_until=None):
+ if parse_until is None:
+ parse_until = []
- try:
- obj = self.obj_class.objects.get(slug=self.slug, parent=self.parent)
-
- if obj.id != value.id:
- raise ValidationError(self.message)
+ 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)
- except self.obj_class.DoesNotExist:
- pass
+ 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 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 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)
-class URLRedirectValidator(URLValidator):
- 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 redirects
- r'(?:/?|[/?]?\S+)$', re.IGNORECASE)
-
-class URLLinkValidator(URLValidator):
- 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)
+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):
+ try:
+ self.validate_template(value)
+ except ValidationError:
+ raise
+ except Exception, e:
+ raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
+
+ 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