PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/flax_search_service/ext/wsgiwapi/wsgiwapi/validation.py

http://flaxcode.googlecode.com/
Python | 203 lines | 118 code | 24 blank | 61 comment | 40 complexity | 33ffa75c5056231afb62dc06929448cb MD5 | raw file
Possible License(s): BSD-2-Clause, Apache-2.0, GPL-2.0, BSD-3-Clause, AGPL-1.0
  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. __docformat__ = "restructuredtext en"
  23. import re
  24. from wsgisupport import HTTPNotFound
  25. from application import ValidationError
  26. def validate_param(key, vals, minreps, maxreps, pattern,
  27. compiled_pattern, default, doc):
  28. """Validate a particular parameter.
  29. """
  30. l = len(vals)
  31. # If not present, and there is a default, set vals to default
  32. if l == 0 and default is not None:
  33. vals = default
  34. # Check we've got an acceptable number of values.
  35. if l < minreps:
  36. raise ValidationError(u"Too few instances of %r supplied "
  37. u"(needed %d, got %d)" %
  38. (key, minreps, l))
  39. if maxreps is not None and l > maxreps:
  40. raise ValidationError(u"Too many instances of %r supplied "
  41. u"(maximum %d, got %d)" %
  42. (key, maxreps, l))
  43. if compiled_pattern is not None:
  44. # Check the regexp pattern matches
  45. for val in vals:
  46. if not compiled_pattern.match(val):
  47. raise ValidationError(u"Invalid parameter value for %r" % key)
  48. return vals
  49. def validate_params(requestobj, constraints):
  50. """Validate parameters, raising ValidationError for problems.
  51. `constraints` is a dict of tuples, one for each field. Unknown fields
  52. raise an error.
  53. """
  54. p = {}
  55. # Check for missing parameters - add if they have a default, otherwise give
  56. # an error.
  57. missing_params = set()
  58. for key, constraint in constraints.iteritems():
  59. if constraint[4] is not None:
  60. if key not in requestobj:
  61. p[key] = constraint[4]
  62. else:
  63. # check for missing params
  64. if constraint[0] > 0 and key not in requestobj:
  65. missing_params.add(key)
  66. if len(missing_params) != 0:
  67. # We trust the list of missing_params not to be trying to hack us.
  68. raise ValidationError(u"Missing required parameters %s" %
  69. u', '.join(u"'%s'" % p for p in missing_params))
  70. for key in requestobj:
  71. constraint = constraints.get(key, None)
  72. if constraint is None:
  73. if re.match('\w+$', key):
  74. # No potentially dangerous characters
  75. raise ValidationError(u"Unknown parameter %r supplied" % key)
  76. else:
  77. raise ValidationError(u"Unknown parameter supplied")
  78. p[key] = validate_param(key, requestobj[key], *constraint)
  79. return p
  80. def check_valid_params(request, props):
  81. constraints = props.get('valid_params', None)
  82. if constraints != None:
  83. request.params = validate_params(request.params, constraints)
  84. return request
  85. def check_no_params(request, props):
  86. if len(request.params) != 0:
  87. raise ValidationError(u"This resource does not accept parameters")
  88. return request
  89. def validate_pathinfo_params(request, param_rules):
  90. """Check that the pathinfo satisfies the supplied rules.
  91. """
  92. index = 0
  93. for name, pattern, compiled_pattern, default, required in param_rules:
  94. if len(request.pathinfo.tail) <= index:
  95. if required:
  96. raise ValidationError("Required pathinfo component missing")
  97. # Put default value into dictionary.
  98. request.pathinfo[name] = default
  99. index += 1
  100. continue
  101. param = request.pathinfo.tail[index]
  102. index += 1
  103. # Validate the param, and put it into the dictionary.
  104. if compiled_pattern is not None:
  105. if not compiled_pattern.match(param):
  106. raise ValidationError("Pathinfo component does not match "
  107. "allowed pattern")
  108. request.pathinfo[name] = param
  109. request.pathinfo.tail = request.pathinfo.tail[index:]
  110. def validate_pathinfo_tail(request, tail_rules):
  111. """Check that the pathinfo tail satisfies the supplied rules.
  112. """
  113. if tail_rules is None:
  114. if len(request.pathinfo.tail) > 0:
  115. raise ValidationError("Unexpected trailing pathinfo")
  116. else:
  117. return
  118. minreps, maxreps, pattern, compiled_pattern, default, doc = tail_rules
  119. # FIXME - validate pathinfo
  120. def check_pathinfo(request, props):
  121. """Check the pathinfo for validity, and populate the pathinfo dictionary.
  122. """
  123. param_rules = props['pathinfo_params_rules']
  124. tail_rules = props['pathinfo_tail_rules']
  125. validate_pathinfo_params(request, param_rules)
  126. validate_pathinfo_tail(request, tail_rules)
  127. return request
  128. def _pad_none(args, length):
  129. """Pad a list of arguments with None to specified length.
  130. """
  131. return (list(args) + [None] * length)[:length]
  132. def parse_pathinfo_rules(pathinfo_items, tail_rules):
  133. """Parse pathinfo rules.
  134. """
  135. # Build the parameter validation rules from the args.
  136. param_rules = []
  137. previous_required = True
  138. for pathinfo_item in pathinfo_items:
  139. if len(pathinfo_item) < 1 or len(pathinfo_item) > 3:
  140. raise TypeError("pathinfo decorator arguments must be "
  141. "sequences of length 1 to 3 items - got %d"
  142. " items" % len(pathinfo_item))
  143. required = False
  144. if len(pathinfo_item) < 3:
  145. # No default specified, so a required parameter.
  146. required = True
  147. if not previous_required:
  148. raise TypeError("required parameter in pathinfo decorator"
  149. " following non-required parameter")
  150. else:
  151. previous_required = False
  152. name, pattern, default = _pad_none(pathinfo_item, 3)
  153. compiled_pattern = None
  154. if pattern is not None:
  155. compiled_pattern = re.compile(pattern, re.UNICODE)
  156. param_rules.append((name, pattern, compiled_pattern, default, required))
  157. # Check the "tail" keyword argument.
  158. if not tail_rules is None:
  159. if len(tail_rules) > 5:
  160. raise TypeError("pathinfo tail argument must be "
  161. "sequence of length 0 to 5 items - got %d "
  162. "items" % len(tail_rules))
  163. tail_rules = _pad_none(tail_rules, 5)
  164. pattern = tail_rules[2]
  165. compiled_pattern = None
  166. if pattern is not None:
  167. compiled_pattern = re.compile(pattern, re.UNICODE)
  168. tail_rules = tail_rules[:2] + [compiled_pattern] + tail_rules[2:]
  169. return param_rules, tail_rules
  170. # vim: set fileencoding=utf-8 :