Package wsgiwebapi :: Module validation
[frames] | no frames]

Source Code for Module wsgiwebapi.validation

  1  # Copyright (c) 2009 Richard Boulton 
  2  # 
  3  # Permission is hereby granted, free of charge, to any person obtaining a copy 
  4  # of this software and associated documentation files (the "Software"), to deal 
  5  # in the Software without restriction, including without limitation the rights 
  6  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  7  # copies of the Software, and to permit persons to whom the Software is 
  8  # furnished to do so, subject to the following conditions: 
  9  # 
 10  # The above copyright notice and this permission notice shall be included in 
 11  # all copies or substantial portions of the Software. 
 12  # 
 13  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 14  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 15  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 16  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 17  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 18  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 19  # SOFTWARE. 
 20  r"""Support for validation of request parameters. 
 21   
 22  """ 
 23  __docformat__ = "restructuredtext en" 
 24   
 25  import re 
 26  from wsgisupport import HTTPNotFound 
 27   
28 -class ValidationError(Exception):
29 """Exception used to indicate that parameters failed validation. 30 31 """
32 - def __init__(self, message):
33 self.message = message
34
35 - def __str__(self):
36 return "ValidationError(\"%s\")" % self.message.\ 37 replace('\\', '\\\\').\ 38 replace('"', '\"')
39
40 -def validate_param(key, vals, minreps, maxreps, pattern, 41 compiled_pattern, default, doc):
42 """Validate a particular parameter. 43 44 """ 45 l = len(vals) 46 47 # If not present, and there is a default, set vals to default 48 if l == 0 and default is not None: 49 vals = default 50 51 # Check we've got an acceptable number of values. 52 if l < minreps: 53 raise ValidationError(u"Too few instances of %r supplied " 54 u"(needed %d, got %d)" % 55 (key, minreps, l)) 56 if maxreps is not None and l > maxreps: 57 raise ValidationError(u"Too many instances of %r supplied " 58 u"(maximum %d, got %d)" % 59 (key, maxreps, l)) 60 61 if compiled_pattern is not None: 62 # Check the regexp pattern matches 63 for val in vals: 64 if not compiled_pattern.match(val): 65 raise ValidationError(u"Invalid parameter value for %r" % key) 66 67 return vals
68
69 -def validate_params(requestobj, constraints):
70 """Validate parameters, raising ValidationError for problems. 71 72 `constraints` is a dict of tuples, one for each field. Unknown fields 73 raise an error. 74 75 """ 76 p = {} 77 78 # Check for missing parameters - add if they have a default, otherwise give 79 # an error. 80 missing_params = set() 81 for key, constraint in constraints.iteritems(): 82 if constraint[3] is not None: 83 if key not in requestobj: 84 p[key] = constraint[3] 85 else: 86 # check for missing params 87 if constraint[0] > 0 and key not in requestobj: 88 missing_params.add(key) 89 90 if len(missing_params) != 0: 91 # We trust the list of missing_params not to be trying to hack us. 92 raise ValidationError(u"Missing required parameters %s" % 93 u', '.join(u"'%s'" % p for p in missing_params)) 94 95 for key in requestobj: 96 constraint = constraints.get(key, None) 97 if constraint is None: 98 if re.match('\w+$', key): 99 # No potentially dangerous characters 100 raise ValidationError(u"Unknown parameter %r supplied" % key) 101 else: 102 raise ValidationError(u"Unknown parameter supplied") 103 p[key] = validate_param(key, requestobj[key], *constraint) 104 105 return p
106
107 -def check_valid_params(request, props):
108 constraints = props.get('valid_params', None) 109 if constraints != None: 110 request.params = validate_params(request.params, constraints) 111 return request
112
113 -def check_no_params(request, props):
114 if len(request.params) != 0: 115 raise ValidationError(u"This resource does not accept parameters") 116 return request
117 118
119 -def validate_pathinfo_params(request, param_rules):
120 """Check that the pathinfo satisfies the supplied rules. 121 122 """ 123 index = 0 124 for name, pattern, compiled_pattern, default, required in param_rules: 125 if len(request.pathinfo.tail) <= index: 126 if required: 127 raise HTTPNotFound(request.path) 128 # Put default value into dictionary. 129 request.pathinfo[name] = default 130 index += 1 131 continue 132 param = request.pathinfo.tail[index] 133 index += 1 134 # Validate the param, and put it into the dictionary. 135 if compiled_pattern is not None: 136 if not compiled_pattern.match(param): 137 raise HTTPNotFound(request.path) 138 request.pathinfo[name] = param 139 request.pathinfo.tail = request.pathinfo.tail[index:]
140
141 -def validate_pathinfo_tail(request, tail_rules):
142 """Check that the pathinfo tail satisfies the supplied rules. 143 144 """ 145 if tail_rules is None: 146 if len(request.pathinfo.tail) > 0: 147 raise HTTPNotFound(request.path) 148 else: 149 return 150 minreps, maxreps, pattern, compiled_pattern, default, doc = tail_rules
151
152 -def check_pathinfo(request, props):
153 """Check the pathinfo for validity, and populate the pathinfo dictionary. 154 155 """ 156 param_rules = props['pathinfo_params_rules'] 157 tail_rules = props['pathinfo_tail_rules'] 158 159 validate_pathinfo_params(request, param_rules) 160 validate_pathinfo_tail(request, tail_rules) 161 162 return request
163
164 -def _pad_none(args, length):
165 """Pad a list of arguments with None to specified length. 166 167 """ 168 return (list(args) + [None] * length)[:length]
169
170 -def parse_pathinfo_rules(pathinfo_items, tail_rules):
171 """Parse pathinfo rules. 172 173 """ 174 # Build the parameter validation rules from the args. 175 param_rules = [] 176 previous_required = True 177 for pathinfo_item in pathinfo_items: 178 if len(pathinfo_item) < 1 or len(pathinfo_item) > 3: 179 raise TypeError("pathinfo decorator arguments must be " 180 "sequences of length 1 to 3 items - got %d" 181 " items" % len(pathinfo_item)) 182 required = False 183 if len(pathinfo_item) < 3: 184 # No default specified, so a required parameter. 185 required = True 186 if not previous_required: 187 raise TypeError("required parameter in pathinfo decorator" 188 " following non-required parameter") 189 previous_required = True 190 name, pattern, default = _pad_none(pathinfo_item, 3) 191 compiled_pattern = None 192 if pattern is not None: 193 compiled_pattern = re.compile(pattern) 194 param_rules.append((name, pattern, compiled_pattern, default, required)) 195 196 # Check the "tail" keyword argument. 197 if not tail_rules is None: 198 if len(tail_rules) < 1 or len(tail_rules) > 5: 199 raise TypeError("pathinfo tail argument must be " 200 "sequence of length 1 to 5 items - got %d " 201 "items" % len(tail_rules)) 202 tail_rules = _pad_none(tail_rules, 5) 203 pattern = tail_rules[2] 204 compiled_pattern = None 205 if pattern is not None: 206 compiled_pattern = re.compile(pattern) 207 tail_rules = tail_rules[:2] + [compiled_pattern] + tail_rules[2:] 208 209 return param_rules, tail_rules
210 211 # vim: set fileencoding=utf-8 : 212