Personal wiki notebook (not under development)

Validate.py 9.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. import cherrypy
  2. import re
  3. from cgi import escape
  4. from Html_cleaner import Html_cleaner
  5. class Validation_error( Exception ):
  6. """
  7. An exception raised when form validation fails for some reason.
  8. """
  9. MESSAGE_MAP = {
  10. int: u"can only contain digits",
  11. }
  12. def __init__( self, name, value, value_type, message = None ):
  13. Exception.__init__( self )
  14. self.__name = name
  15. self.__value = value
  16. self.__value_type = value_type
  17. if message is None:
  18. # if the value's type has a message member, use that. otherwise, look up the type in a map
  19. if hasattr( value_type, u"message" ):
  20. self.__message = value_type.message
  21. else:
  22. self.__message = self.MESSAGE_MAP.get( value_type, u"is invalid" )
  23. else:
  24. self.__message = message
  25. def __str__( self ):
  26. return self.__message
  27. def to_dict( self ):
  28. return dict(
  29. error = u"The %s %s." % ( self.__name.replace( u"_", " " ), self.__message ),
  30. name = self.__name,
  31. value = self.__value,
  32. )
  33. name = property( lambda self: self.__name )
  34. value = property( lambda self: self.__value )
  35. value_type = property( lambda self: self.__value_type )
  36. message = property( lambda self: self.__message )
  37. class Valid_string( object ):
  38. """
  39. Validator for a string of certain minimum and maximum lengths.
  40. """
  41. moron_map = {
  42. u"\xa0": u" ",
  43. u"\xa9": u"(c)",
  44. u"\xae": u"(r)",
  45. u"\xb7": u"*",
  46. u"\u2002": u" ",
  47. u"\u2003": u" ",
  48. u"\u2009": u" ",
  49. u"\u2010": u"-",
  50. u"\u2011": u"-",
  51. u"\u2013": u"-",
  52. u"\u2014": u"--",
  53. u"\u2015": u"--",
  54. u"\u2016": u"--",
  55. u"\u2017": u"||",
  56. u"\u2018": u"'",
  57. u"\u2019": u"'",
  58. u"\u201a": u",",
  59. u"\u201b": u"'",
  60. u"\u201c": u'"',
  61. u"\u201d": u'"',
  62. u"\u201e": u",,",
  63. u"\u201f": u'"',
  64. u"\u2022": u"*",
  65. u"\u2023": u"*",
  66. u"\u2024": u".",
  67. u"\u2025": u"..",
  68. u"\u2026": u"...",
  69. u"\u2027": u".",
  70. u"\u2122": u"(tm)",
  71. }
  72. def __init__( self, min = None, max = None, escape_html = True, require_link_target = False ):
  73. self.min = min
  74. self.max = max
  75. self.escape_html = escape_html
  76. self.require_link_target = require_link_target
  77. self.message = None
  78. def __call__( self, value ):
  79. value = self.__demoronize( value.strip() )
  80. if self.min is not None and len( value ) < self.min:
  81. if self.min == 1:
  82. self.message = u"is missing"
  83. else:
  84. self.message = u"must be at least %s characters long" % self.min
  85. raise ValueError()
  86. # either escape all html completely or just clean up the html, stripping out everything that's
  87. # not on a tag/attribute whitelist
  88. if self.escape_html:
  89. value = escape( value, quote = True )
  90. else:
  91. cleaner = Html_cleaner( self.require_link_target )
  92. value = cleaner.strip( value )
  93. # check for max length after cleaning html, as cleaning can reduce the html's size
  94. if self.max is not None and len( value ) > self.max:
  95. self.message = u"must be no longer than %s characters. Please try removing some of the text" % self.max
  96. raise ValueError()
  97. return value
  98. def __demoronize( self, value ):
  99. """
  100. Convert stupid Microsoft unicode symbols to saner, cross-platform equivalents.
  101. """
  102. try:
  103. for ( moron_symbol, replacement ) in self.moron_map.items():
  104. value = value.replace( moron_symbol, replacement )
  105. except:
  106. import traceback
  107. traceback.print_exc()
  108. raise
  109. return value
  110. class Valid_bool( object ):
  111. """
  112. Validator for a boolean value.
  113. """
  114. def __init__( self, none_okay = False ):
  115. self.__none_okay = none_okay
  116. def __call__( self, value ):
  117. value = value.strip()
  118. if self.__none_okay and value in ( None, "None", "" ): return None
  119. if value in ( u"True", u"true" ): return True
  120. if value in ( u"False", u"false" ): return False
  121. raise ValueError()
  122. class Valid_int( object ):
  123. """
  124. Validator for an integer value.
  125. """
  126. def __init__( self, min = None, max = None, none_okay = False ):
  127. self.min = min
  128. self.max = max
  129. self.message = None
  130. self.__none_okay = none_okay
  131. def __call__( self, value ):
  132. if self.__none_okay and value in ( None, "None", "" ): return None
  133. value = int( value )
  134. if self.min is not None and value < self.min:
  135. self.message = "is too small"
  136. raise ValueError()
  137. if self.max is not None and value > self.max:
  138. self.message = "is too large"
  139. raise ValueError()
  140. return value
  141. class Valid_friendly_id( object ):
  142. FRIENDLY_ID_PATTERN = re.compile( "^[a-zA-Z0-9\-]+$" )
  143. def __call__( self, value ):
  144. if self.FRIENDLY_ID_PATTERN.search( value ):
  145. return value
  146. raise ValueError()
  147. def validate( **expected ):
  148. """
  149. validate() can be used to require that the arguments of the decorated method successfully pass
  150. through particular validators. The validate() method itself is evaluated where it is used as a
  151. decorator, which just returns decorate() to be used as the actual decorator.
  152. Example usage:
  153. @validate(
  154. foo = Valid_string( min = 5, max = 10 ),
  155. bar = int
  156. )
  157. def method( self, foo, bar ): pass
  158. Note that validate() currently only works for instance methods (methods that take self as the
  159. first argument). Also note that you can use multiple validators for a single argument.
  160. Example usage:
  161. @validate(
  162. foo = Valid_string( min = 5, max = 10 ),
  163. bar = ( int, valid_bar )
  164. )
  165. def method( self, foo, bar ): pass
  166. """
  167. def decorate( function ):
  168. """
  169. When the method being decorated is invoked, its decorator gets invoked instead and is supposed
  170. to return a new function to use in place of the method being decorated (or a modified version
  171. of that function). In this case, the decorator is our decorate() function, and the function it
  172. returns is the check() function. decorate()'s first argument is the method being decorated.
  173. """
  174. def check( *args, **kwargs ):
  175. """
  176. check() pretends that it's the method being decorated. It takes the same arguments and then
  177. invokes the actual method being decorated, passing in those arguments, but only after first
  178. validating all of those arguments to that function. If validation fails, a Validation_error
  179. is raised. Note that in Python, keyword argument names have to be str, not unicode.
  180. """
  181. args = list( args )
  182. args_index = 1 # skip the self argument
  183. # make sure all kwarg names are lowercase and don't have dashes
  184. for ( kwarg_name, value ) in kwargs.items():
  185. new_kwarg_name = kwarg_name.replace( "-", "_" ).lower()
  186. if new_kwarg_name != kwarg_name:
  187. del( kwargs[ kwarg_name ] )
  188. kwargs[ new_kwarg_name ] = value
  189. # determine the expected argument names from the decorated function itself
  190. code = function.func_code
  191. expected_names = code.co_varnames[ : code.co_argcount ]
  192. # validate each of the expected arguments
  193. for expected_name in expected_names:
  194. if expected_name == u"self": continue
  195. expected_type = expected.get( expected_name )
  196. # look for expected_name in kwargs and store the validated value there
  197. if expected_name in kwargs:
  198. value = kwargs.get( expected_name )
  199. # if there's a tuple of multiple validators for this expected_name, use all of them
  200. if isinstance( expected_type, tuple ):
  201. for validator in expected_type:
  202. try:
  203. value = validator( value )
  204. except ( ValueError, TypeError ):
  205. raise Validation_error( expected_name, value, validator )
  206. kwargs[ str( expected_name ) ] = value
  207. # otherwise, there's just a single validator
  208. else:
  209. try:
  210. kwargs[ str( expected_name ) ] = expected_type( value )
  211. except ( ValueError, TypeError ):
  212. raise Validation_error( expected_name, value, expected_type )
  213. continue
  214. # expected_name wasn't found in kwargs, so look for it in args. if it's not there either,
  215. # raise unless there's a default value for the argument in the decorated function
  216. if args_index >= len( args ):
  217. if function.func_defaults and args_index >= len( args ) - len( function.func_defaults ):
  218. continue
  219. raise Validation_error( expected_name, None, expected_type, message = u"is required" )
  220. value = args[ args_index ]
  221. # if there's a tuple of multiple validators for this expected_name, use all of them
  222. if isinstance( expected_type, tuple ):
  223. for validator in expected_type:
  224. try:
  225. value = validator( value )
  226. except ( ValueError, TypeError ):
  227. raise Validation_error( expected_name, value, validator )
  228. args[ args_index ] = value
  229. # otherwise, there's just a single validator
  230. else:
  231. try:
  232. args[ args_index ] = expected_type( value )
  233. except ( ValueError, TypeError ):
  234. raise Validation_error( expected_name, value, expected_type )
  235. args_index += 1
  236. # if there are any unexpected arguments, raise
  237. for ( arg_name, arg_value ) in kwargs.items():
  238. if not arg_name in expected_names:
  239. raise Validation_error( arg_name, arg_value, None, message = u"is an unknown argument" )
  240. return function( *args, **kwargs )
  241. return check
  242. return decorate