All of my work from commits: dd4a194, 692644a, 4a60203, 5de46bc, 152042d, 64a2d4e...
[philo.git] / validators.py
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, TemplateSyntaxError
5 from django.utils import simplejson as json
6 import re
7 from philo.utils import LOADED_TEMPLATE_ATTR
8
9
10 INSECURE_TAGS = (
11         'load',
12         'extends',
13         'include',
14         'debug',
15 )
16
17
18 class RedirectValidator(RegexValidator):
19         """Based loosely on the URLValidator, but no option to verify_exists"""
20         regex = re.compile(
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
26                 r'(?:/?|[/?#]?\S+)|'
27                 r'[^?#\s]\S*)$',
28                 re.IGNORECASE)
29         message = _(u'Enter a valid absolute or relative redirect target')
30
31
32 class URLLinkValidator(RegexValidator):
33         """Based loosely on the URLValidator, but no option to verify_exists"""
34         regex = re.compile(
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')
43
44
45 def json_validator(value):
46         try:
47                 json.loads(value)
48         except Exception, e:
49                 raise ValidationError(u'JSON decode error: %s' % e)
50
51
52 class TemplateValidationParser(Parser):
53         def __init__(self, tokens, allow=None, disallow=None, secure=True):
54                 super(TemplateValidationParser, self).__init__(tokens)
55                 
56                 allow, disallow = set(allow or []), set(disallow or [])
57                 
58                 if secure:
59                         disallow |= set(INSECURE_TAGS)
60                 
61                 self.allow, self.disallow, self.secure = allow, disallow, secure
62         
63         def parse(self, parse_until=None):
64                 if parse_until is None:
65                         parse_until = []
66                 
67                 nodelist = self.create_nodelist()
68                 while self.tokens:
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)
74                                 
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)
82                                         return nodelist
83                                 
84                                 try:
85                                         command = token.contents.split()[0]
86                                 except IndexError:
87                                         self.empty_block_tag(token)
88                                 
89                                 if (self.allow and command not in self.allow) or (self.disallow and command in self.disallow):
90                                         self.disallowed_tag(command)
91                                 
92                                 self.enter_command(command, token)
93                                 
94                                 try:
95                                         compile_func = self.tags[command]
96                                 except KeyError:
97                                         self.invalid_block_tag(token, command, parse_until)
98                                 
99                                 try:
100                                         compiled_result = compile_func(self, token)
101                                 except TemplateSyntaxError, e:
102                                         if not self.compile_function_error(token, e):
103                                                 raise
104                                 
105                                 self.extend_nodelist(nodelist, compiled_result, token)
106                                 self.exit_command()
107                 
108                 if parse_until:
109                         self.unclosed_block_tag(parse_until)
110                 
111                 return nodelist
112         
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)
117
118
119 class TemplateValidator(object): 
120         def __init__(self, allow=None, disallow=None, secure=True):
121                 self.allow = allow
122                 self.disallow = disallow
123                 self.secure = secure
124         
125         def __call__(self, value):
126                 try:
127                         self.validate_template(value)
128                 except ValidationError:
129                         raise
130                 except Exception, e:
131                         raise ValidationError("Template code invalid. Error was: %s: %s" % (e.__class__.__name__, e))
132         
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)
138                 
139                 for node in parser.parse():
140                         template = getattr(node, LOADED_TEMPLATE_ATTR, None)