Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

Changeset 2030

Show
Ignore:
Timestamp:
08/04/08 11:31:30
Author:
lakin
Message:

#733 - Return a 404 when query parameters passed to a handler are incorect. Similarly return a 404 when path atoms are incorrectly passed to a handler. Alternatively return a 400 when body params are incorrectly passed to a handler. Includes tests.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/_cpdispatch.py

    r1893 r2030  
    2222     
    2323    def __call__(self): 
    24         return self.callable(*self.args, **self.kwargs) 
     24        try: 
     25            return self.callable(*self.args, **self.kwargs) 
     26        except TypeError, x: 
     27            test_callable_spec(self.callable, self.args, self.kwargs) 
     28            raise 
     29 
     30def test_callable_spec(callable, callable_args, callable_kwargs): 
     31    """ 
     32    Inspect callable and test to see if the given args are suitable for it. 
     33 
     34    When an error occurs during the handler's invoking stage there are 2 
     35    erroneous cases: 
     36    1.  Too many parameters passed to a function which doesn't define 
     37        one of *args or **kwargs. 
     38    2.  Too little parameters are passed to the function. 
     39 
     40    There are 3 sources of parameters to a cherrypy handler. 
     41    1.  query string parameters are passed as keyword parameters to the handler. 
     42    2.  body parameters are also passed as keyword parameters. 
     43    3.  when partial matching occurs, the final path atoms are passed as 
     44        positional args. 
     45    Both the query string and path atoms are part of the URI.  If they are 
     46    incorrect, then a 404 Not Found should be raised. Conversely the body 
     47    parameters are part of the request; if they are invalid a 400 Bad Request. 
     48    """ 
     49    (args, varargs, varkw, defaults) = inspect.getargspec(callable) 
     50 
     51    if args and args[0] == 'self': 
     52        args = args[1:] 
     53 
     54    arg_usage = dict([(arg, 0,) for arg in args]) 
     55    vararg_usage = 0 
     56    varkw_usage = 0 
     57    extra_kwargs = set() 
     58 
     59    for i, value in enumerate(callable_args): 
     60        try: 
     61            arg_usage[args[i]] += 1 
     62        except IndexError: 
     63            vararg_usage += 1 
     64 
     65    for key in callable_kwargs.keys(): 
     66        try: 
     67            arg_usage[key] += 1 
     68        except KeyError: 
     69            varkw_usage += 1 
     70            extra_kwargs.add(key) 
     71 
     72    for i, val in enumerate(defaults or []): 
     73        # Defaults take effect only when the arg hasn't been used yet. 
     74        if arg_usage[args[i]] == 0: 
     75            arg_usage[args[i]] += 1 
     76 
     77    missing_args = [] 
     78    multiple_args = [] 
     79    for key, usage in arg_usage.iteritems(): 
     80        if usage == 0: 
     81            missing_args.append(key) 
     82        elif usage > 1: 
     83            multiple_args.append(key) 
     84 
     85    if missing_args: 
     86        # In the case where the method allows body arguments 
     87        # there are 3 potential errors: 
     88        # 1. not enough query string parameters -> 404 
     89        # 2. not enough body parameters -> 400 
     90        # 3. not enough path parts (partial matches) -> 404 
     91        # 
     92        # We can't actually tell which case it is,  
     93        # so I'm raising a 404 because that covers 2/3 of the 
     94        # possibilities 
     95        #  
     96        # In the case where the method does not allow body 
     97        # arguments it's definitely a 404. 
     98        raise cherrypy.HTTPError(404, 
     99                message="Missing parameters: %s" % ",".join(missing_args)) 
     100 
     101    # the extra positional arguments come from the path - 404 Not Found 
     102    if not varargs and vararg_usage > 0: 
     103        raise cherrypy.HTTPError(404) 
     104 
     105    body_params = cherrypy.request.body_params or {} 
     106    body_params = set(body_params.keys()) 
     107    qs_params = set(callable_kwargs.keys()) - body_params 
     108 
     109    if multiple_args: 
     110 
     111        if qs_params.intersection(set(multiple_args)): 
     112            # If any of the multiple parameters came from the query string then 
     113            # it's a 404 Not Found 
     114            error = 404 
     115        else: 
     116            # Otherwise it's a 400 Bad Request 
     117            error = 400 
     118 
     119        raise cherrypy.HTTPError(error, 
     120                message="Multiple values for parameters: "\ 
     121                        "%s" % ",".join(multiple_args)) 
     122 
     123    if not varkw and varkw_usage > 0: 
     124 
     125        # If there were extra query string parameters, it's a 404 Not Found 
     126        extra_qs_params = set(qs_params).intersection(extra_kwargs) 
     127        if extra_qs_params: 
     128            raise cherrypy.HTTPError(404, 
     129                message="Unexpected query string "\ 
     130                        "parameters: %s" % ", ".join(extra_qs_params)) 
     131 
     132        # If there were any extra body parameters, it's a 400 Not Found 
     133        extra_body_params = set(body_params).intersection(extra_kwargs) 
     134        if extra_body_params: 
     135            raise cherrypy.HTTPError(400, 
     136                message="Unexpected body parameters: "\ 
     137                        "%s" % ", ".join(extra_body_params)) 
     138 
     139 
     140try: 
     141    import inspect 
     142except ImportError: 
     143    test_callable_spec = lambda callable, args, kwargs: None 
     144 
    25145 
    26146 
  • trunk/cherrypy/test/test_core.py

    r1992 r2030  
    9999            return "args: %s kwargs: %s" % (args, kwargs) 
    100100 
     101    class ParamErrors(Test): 
     102 
     103        def one_positional(self, param1): 
     104            return "data" 
     105        one_positional.exposed = True 
     106 
     107        def one_positional_args(self, param1, *args): 
     108            return "data" 
     109        one_positional_args.exposed = True 
     110 
     111        def one_positional_args_kwargs(self, param1, *args, **kwargs): 
     112            return "data" 
     113        one_positional_args_kwargs.exposed = True 
     114 
     115        def one_positional_kwargs(self, param1, **kwargs): 
     116            return "data" 
     117        one_positional_kwargs.exposed = True 
     118 
     119        def no_positional(self): 
     120            return "data" 
     121        no_positional.exposed = True 
     122 
     123        def no_positional_args(self, *args): 
     124            return "data" 
     125        no_positional_args.exposed = True 
     126 
     127        def no_positional_args_kwargs(self, *args, **kwargs): 
     128            return "data" 
     129        no_positional_args_kwargs.exposed = True 
     130 
     131        def no_positional_kwargs(self, **kwargs): 
     132            return "data" 
     133        no_positional_kwargs.exposed = True 
     134 
    101135 
    102136    class Status(Test): 
     
    444478        self.getPage("/params/?thing=a&thing=b&thing=c") 
    445479        self.assertBody("['a', 'b', 'c']") 
    446          
     480 
    447481        # Test friendly error message when given params are not accepted. 
    448         ignore = helper.webtest.ignored_exceptions 
    449         ignore.append(TypeError) 
    450         try: 
    451             self.getPage("/params/?notathing=meeting") 
    452             self.assertInBody("index() got an unexpected keyword argument 'notathing'") 
    453         finally: 
    454             ignore.pop() 
     482        self.getPage("/params/?notathing=meeting") 
     483        self.assertInBody("Missing parameters: thing") 
     484        self.getPage("/params/?thing=meeting&notathing=meeting") 
     485        self.assertInBody("Unexpected query string parameters: notathing") 
    455486         
    456487        # Test "% HEX HEX"-encoded URL, param keys, and values 
     
    467498        self.getPage("/params/ismap?223,114") 
    468499        self.assertBody("Coordinates: 223, 114") 
    469      
     500 
     501    def testParamErrors(self): 
     502 
     503        # test that all of the handlers work when given  
     504        # the correct parameters in order to ensure that the 
     505        # errors below aren't coming from some other source. 
     506        for uri in ( 
     507                '/paramerrors/one_positional?param1=foo', 
     508                '/paramerrors/one_positional_args?param1=foo', 
     509                '/paramerrors/one_positional_args/foo', 
     510                '/paramerrors/one_positional_args/foo/bar/baz', 
     511                '/paramerrors/one_positional_args_kwargs?param1=foo&param2=bar', 
     512                '/paramerrors/one_positional_args_kwargs/foo?param2=bar&param3=baz', 
     513                '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz', 
     514                '/paramerrors/one_positional_kwargs?param1=foo&param2=bar&param3=baz', 
     515                '/paramerrors/one_positional_kwargs/foo?param4=foo&param2=bar&param3=baz', 
     516                '/paramerrors/no_positional', 
     517                '/paramerrors/no_positional_args/foo', 
     518                '/paramerrors/no_positional_args/foo/bar/baz', 
     519                '/paramerrors/no_positional_args_kwargs?param1=foo&param2=bar', 
     520                '/paramerrors/no_positional_args_kwargs/foo?param2=bar', 
     521                '/paramerrors/no_positional_args_kwargs/foo/bar/baz?param2=bar&param3=baz', 
     522                '/paramerrors/no_positional_kwargs?param1=foo&param2=bar', 
     523            ): 
     524            self.getPage(uri) 
     525            self.assertStatus(200) 
     526 
     527        # query string parameters are part of the URI, so if they are wrong 
     528        # for a particular handler, the status MUST be a 404. 
     529        for uri in ( 
     530                '/paramerrors/one_positional', 
     531                '/paramerrors/one_positional?foo=foo', 
     532                '/paramerrors/one_positional/foo/bar/baz', 
     533                '/paramerrors/one_positional/foo?param1=foo', 
     534                '/paramerrors/one_positional/foo?param1=foo&param2=foo', 
     535                '/paramerrors/one_positional_args/foo?param1=foo&param2=foo', 
     536                '/paramerrors/one_positional_args/foo/bar/baz?param2=foo', 
     537                '/paramerrors/one_positional_args_kwargs/foo/bar/baz?param1=bar&param3=baz', 
     538                '/paramerrors/one_positional_kwargs/foo?param1=foo&param2=bar&param3=baz', 
     539                '/paramerrors/no_positional/boo', 
     540                '/paramerrors/no_positional?param1=foo', 
     541                '/paramerrors/no_positional_args/boo?param1=foo', 
     542                '/paramerrors/no_positional_kwargs/boo?param1=foo', 
     543            ): 
     544            self.getPage(uri) 
     545            self.assertStatus(404) 
     546 
     547        # if body parameters are wrong, a 400 must be returned. 
     548        for uri, body in ( 
     549                ('/paramerrors/one_positional/foo', 'param1=foo',), 
     550                ('/paramerrors/one_positional/foo', 'param1=foo&param2=foo',), 
     551                ('/paramerrors/one_positional_args/foo', 'param1=foo&param2=foo',), 
     552                ('/paramerrors/one_positional_args/foo/bar/baz', 'param2=foo',), 
     553                ('/paramerrors/one_positional_args_kwargs/foo/bar/baz', 'param1=bar&param3=baz',), 
     554                ('/paramerrors/one_positional_kwargs/foo', 'param1=foo&param2=bar&param3=baz',), 
     555                ('/paramerrors/no_positional', 'param1=foo',), 
     556                ('/paramerrors/no_positional_args/boo', 'param1=foo',), 
     557            ): 
     558            self.getPage(uri, method='POST', body=body) 
     559            self.assertStatus(400) 
     560 
     561 
     562        # even if body parameters are wrong, if we get the uri wrong, then  
     563        # it's a 404 
     564        for uri, body in ( 
     565                ('/paramerrors/one_positional?param2=foo', 'param1=foo',), 
     566                ('/paramerrors/one_positional/foo/bar', 'param2=foo',), 
     567                ('/paramerrors/one_positional_args/foo/bar?param2=foo', 'param3=foo',), 
     568                ('/paramerrors/one_positional_kwargs/foo/bar', 'param2=bar&param3=baz',), 
     569                ('/paramerrors/no_positional?param1=foo', 'param2=foo',), 
     570                ('/paramerrors/no_positional_args/boo?param2=foo', 'param1=foo',), 
     571            ): 
     572            self.getPage(uri, method='POST', body=body) 
     573            self.assertStatus(404) 
     574 
     575 
    470576    def testStatus(self): 
    471577        self.getPage("/status/") 
  • trunk/cherrypy/test/test_objectmapping.py

    r1767 r2030  
    269269        self.assertBody("default for dir1, param is:('dir2', '5', '3', 'sir')") 
    270270         
    271         # test that extra positional args raises an error. 
    272         # 500 for now, maybe 404 in the future. 
     271        # test that extra positional args raises an 404 Not Found 
    273272        # See http://www.cherrypy.org/ticket/733. 
    274273        self.getPage("/dir1/dir2/script_name/extra/stuff") 
    275         self.assertStatus(500
     274        self.assertStatus(404
    276275     
    277276    def testExpose(self): 

Hosted by WebFaction

Log in as guest/cpguest to create tickets