3 from django.core.exceptions import ValidationError
4 from django.core.validators import RegexValidator
5 from django.template import Template, Parser, Lexer, TOKEN_BLOCK, TOKEN_VAR, TemplateSyntaxError
6 from django.utils import simplejson as json
7 from django.utils.html import escape, mark_safe
8 from django.utils.translation import ugettext_lazy as _
10 from philo.utils import LOADED_TEMPLATE_ATTR
21 class RedirectValidator(RegexValidator):
22 """Based loosely on the URLValidator, but no option to verify_exists"""
24 r'^(?:https?://' # http:// or https://
25 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
26 r'localhost|' #localhost...
27 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
28 r'(?::\d+)?' # optional port
32 message = _(u'Enter a valid absolute or relative redirect target')
35 class URLLinkValidator(RegexValidator):
36 """Based loosely on the URLValidator, but no option to verify_exists"""
38 r'^(?:https?://' # http:// or https://
39 r'(?:(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?|' #domain...
40 r'localhost|' #localhost...
41 r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
42 r'(?::\d+)?' # optional port
43 r'|)' # also allow internal links
44 r'(?:/?|[/?#]?\S+)$', re.IGNORECASE)
45 message = _(u'Enter a valid absolute or relative redirect target')
48 def json_validator(value):
52 raise ValidationError(u'JSON decode error: %s' % e)
55 class TemplateValidationParser(Parser):
56 def __init__(self, tokens, allow=None, disallow=None, secure=True):
57 super(TemplateValidationParser, self).__init__(tokens)
59 allow, disallow = set(allow or []), set(disallow or [])
62 disallow |= set(INSECURE_TAGS)
64 self.allow, self.disallow, self.secure = allow, disallow, secure
66 def parse(self, parse_until=None):
67 if parse_until is None:
70 nodelist = self.create_nodelist()
72 token = self.next_token()
73 # We only need to parse var and block tokens.
74 if token.token_type == TOKEN_VAR:
75 if not token.contents:
76 self.empty_variable(token)
78 filter_expression = self.compile_filter(token.contents)
79 var_node = self.create_variable_node(filter_expression)
80 self.extend_nodelist(nodelist, var_node,token)
81 elif token.token_type == TOKEN_BLOCK:
82 if token.contents in parse_until:
83 # put token back on token list so calling code knows why it terminated
84 self.prepend_token(token)
88 command = token.contents.split()[0]
90 self.empty_block_tag(token)
92 if (self.allow and command not in self.allow) or (self.disallow and command in self.disallow):
93 self.disallowed_tag(command)
95 self.enter_command(command, token)
98 compile_func = self.tags[command]
100 self.invalid_block_tag(token, command, parse_until)
103 compiled_result = compile_func(self, token)
104 except TemplateSyntaxError, e:
105 if not self.compile_function_error(token, e):
108 self.extend_nodelist(nodelist, compiled_result, token)
112 self.unclosed_block_tag(parse_until)
116 def disallowed_tag(self, command):
117 if self.secure and command in INSECURE_TAGS:
118 raise ValidationError('Tag "%s" is not permitted for security reasons.' % command)
119 raise ValidationError('Tag "%s" is not permitted here.' % command)
122 def linebreak_iter(template_source):
123 # Cribbed from django/views/debug.py:18
125 p = template_source.find('\n')
128 p = template_source.find('\n', p+1)
129 yield len(template_source) + 1
132 class TemplateValidator(object):
133 def __init__(self, allow=None, disallow=None, secure=True):
135 self.disallow = disallow
138 def __call__(self, value):
140 self.validate_template(value)
141 except ValidationError:
144 if hasattr(e, 'source') and isinstance(e, TemplateSyntaxError):
145 origin, (start, end) = e.source
146 template_source = origin.reload()
148 for num, next in enumerate(linebreak_iter(template_source)):
149 if start >= upto and end <= next:
150 raise ValidationError(mark_safe("Template code invalid: \"%s\" (%s:%d).<br />%s" % (escape(template_source[start:end]), origin.loadname, num, e)))
152 raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
154 def validate_template(self, template_string):
155 # We want to tokenize like normal, then use a custom parser.
156 lexer = Lexer(template_string, None)
157 tokens = lexer.tokenize()
158 parser = TemplateValidationParser(tokens, self.allow, self.disallow, self.secure)
160 for node in parser.parse():
161 template = getattr(node, LOADED_TEMPLATE_ATTR, None)