1 from django.utils.translation import ugettext_lazy as _
2 from django.core.validators import RegexValidator
3 from django.core.exceptions import ValidationError
4 from django.template import Template, Parser, Lexer, TOKEN_BLOCK, TOKEN_VAR
5 from django.utils import simplejson as json
9 LOADED_TEMPLATE_ATTR = '_philo_loaded_template'
18 class RedirectValidator(RegexValidator):
19 """Based loosely on the URLValidator, but no option to verify_exists"""
21 r'^(?:https?://' # http:// or https://
22 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
23 r'localhost|' #localhost...
24 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
25 r'(?::\d+)?' # optional port
29 message = _(u'Enter a valid absolute or relative redirect target')
32 class URLLinkValidator(RegexValidator):
33 """Based loosely on the URLValidator, but no option to verify_exists"""
35 r'^(?:https?://' # http:// or https://
36 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
37 r'localhost|' #localhost...
38 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
39 r'(?::\d+)?' # optional port
40 r'|)' # also allow internal links
41 r'(?:/?|[/?#]?\S+)$', re.IGNORECASE)
42 message = _(u'Enter a valid absolute or relative redirect target')
45 def json_validator(value):
49 raise ValidationError(u'\'%s\' is not valid JSON' % value)
52 class TemplateValidationParser(Parser):
53 def __init__(self, tokens, allow=None, disallow=None, secure=True):
54 super(TemplateValidationParser, self).__init__(tokens)
56 allow, disallow = set(allow or []), set(disallow or [])
59 disallow |= set(INSECURE_TAGS)
61 self.allow, self.disallow, self.secure = allow, disallow, secure
63 def parse(self, parse_until=None):
64 if parse_until is None:
67 nodelist = self.create_nodelist()
69 token = self.next_token()
70 # We only need to parse var and block tokens.
71 if token.token_type == TOKEN_VAR:
72 if not token.contents:
73 self.empty_variable(token)
75 filter_expression = self.compile_filter(token.contents)
76 var_node = self.create_variable_node(filter_expression)
77 self.extend_nodelist(nodelist, var_node,token)
78 elif token.token_type == TOKEN_BLOCK:
79 if token.contents in parse_until:
80 # put token back on token list so calling code knows why it terminated
81 self.prepend_token(token)
85 command = token.contents.split()[0]
87 self.empty_block_tag(token)
89 if (self.allow and command not in self.allow) or (self.disallow and command in self.disallow):
90 self.disallowed_tag(command)
92 self.enter_command(command, token)
95 compile_func = self.tags[command]
97 self.invalid_block_tag(token, command, parse_until)
100 compiled_result = compile_func(self, token)
101 except TemplateSyntaxError, e:
102 if not self.compile_function_error(token, e):
105 self.extend_nodelist(nodelist, compiled_result, token)
109 self.unclosed_block_tag(parse_until)
113 def disallowed_tag(self, command):
114 if self.secure and command in INSECURE_TAGS:
115 raise ValidationError('Tag "%s" is not permitted for security reasons.' % command)
116 raise ValidationError('Tag "%s" is not permitted here.' % command)
119 class TemplateValidator(object):
120 def __init__(self, allow=None, disallow=None, secure=True):
122 self.disallow = disallow
125 def __call__(self, value):
127 self.validate_template(value)
128 except ValidationError:
131 raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
133 def validate_template(self, template_string):
134 # We want to tokenize like normal, then use a custom parser.
135 lexer = Lexer(template_string, None)
136 tokens = lexer.tokenize()
137 parser = TemplateValidationParser(tokens, self.allow, self.disallow, self.secure)
139 for node in parser.parse():
140 template = getattr(node, LOADED_TEMPLATE_ATTR, None)