Robert Brewer avatar Robert Brewer committed 4fffc66

Fix for #433 (mapPathToObject should remove a trailing /, not just add it). CP 3 only. Configurable now via request.redirect_on_extra_slash and request.redirect_on_missing_slash. Also broke the slash logic out of find_handler, so it's more reusable.

Comments (0)

Files changed (3)

cherrypy/_cprequest.py

             return base
         
         # Try successive objects (reverse order)
-        for i in xrange(len(object_trail) - 1, -1, -1):
+        num_candidates = len(object_trail) - 1
+        for i in xrange(num_candidates, -1, -1):
             
             name, candidate, nodeconf, curpath = object_trail[i]
             if candidate is None:
                     return defhandler, names[i:-1]
             
             # Uncomment the next line to restrict positional params to "default".
-            # if i < len(object_trail) - 2: continue
+            # if i < num_candidates - 2: continue
             
             # Try the current leaf.
             if getattr(candidate, 'exposed', False):
                 request.config = set_conf()
-                if i == len(object_trail) - 1:
-                    # We found the extra ".index". Check if the original path
-                    # had a trailing slash (otherwise, do a redirect).
-                    if path[-1:] != '/':
-                        atoms = request.browser_url.split("?", 1)
-                        new_url = atoms.pop(0) + '/'
-                        if atoms:
-                            new_url += "?" + atoms[0]
-                        raise cherrypy.HTTPRedirect(new_url)
+                if i == num_candidates:
+                    # We found the extra ".index". Check that path_info
+                    # has a trailing slash (otherwise, do a redirect).
+                    self.check_missing_slash()
+                else:
+                    # We're not at an 'index' handler. Check that path_info
+                    # had NO trailing slash (if it did, do a redirect).
+                    self.check_extra_slash()
                 return candidate, names[i:-1]
         
         # We didn't find anything
         request.config = set_conf()
         return None, []
+    
+    def check_missing_slash(self):
+        """Redirect if path_info has no trailing slash (if configured)."""
+        request = cherrypy.request
+        pi = request.path_info
+        
+        # Must use config here because tool_up probably hasn't run yet.
+        if request.config.get("request.redirect_on_missing_slash",
+                              request.redirect_on_missing_slash):
+            if pi[-1:] != '/':
+                atoms = request.browser_url.split("?", 1)
+                new_url = atoms.pop(0) + '/'
+                if atoms:
+                    new_url += "?" + atoms[0]
+                raise cherrypy.HTTPRedirect(new_url)
+    
+    def check_extra_slash(self):
+        """Redirect if path_info has trailing slash (if configured)."""
+        request = cherrypy.request
+        pi = request.path_info
+        
+        # Must use config here because tool_up hasn't run yet.
+        if request.config.get("request.redirect_on_extra_slash",
+                              request.redirect_on_extra_slash):
+            # If pi == '/', don't redirect to ''!
+            if pi[-1:] == '/' and pi != '/':
+                atoms = request.browser_url.split("?", 1)
+                new_url = atoms.pop(0)[:-1]
+                if atoms:
+                    new_url += "?" + atoms[0]
+                raise cherrypy.HTTPRedirect(new_url)
 
 
 class MethodDispatcher(Dispatcher):
     toolmap = {}
     config = None
     recursive_redirect = False
+    redirect_on_extra_slash = False
+    redirect_on_missing_slash = True
     
     hookpoints = ['on_start_resource', 'before_request_body',
                   'before_main', 'before_finalize',

cherrypy/lib/cptools.py

     """
     request = cherrypy.request
     
+    # Guard against running twice.
     if hasattr(request, "virtual_prefix"):
         return
     

cherrypy/test/test_objectmapping.py

         def myMethod(self):
             return "myMethod from dir1, path_info is:" + repr(cherrypy.request.path_info)
         myMethod.exposed = True
+        myMethod._cp_config = {'request.redirect_on_extra_slash': True}
         
         def default(self, *params):
             return "default for dir1, param is:" + repr(params)
         def default(self):
             return "default for dir3, not exposed"
     
-    
     class Dir4:
         def index(self):
             return "index for dir4, not exposed"
             self.getPage("/dir1/dir2/")
             self.assertBody('index for dir2, path is:%s/dir1/dir2/' % prefix)
             
+            # Test omitted trailing slash (should be redirected by default).
             self.getPage("/dir1/dir2")
-            self.assert_(self.status in ('302 Found', '303 See Other'))
+            self.assertStatus((302, 303))
             self.assertHeader('Location', 'http://%s:%s%s/dir1/dir2/'
                               % (self.HOST, self.PORT, prefix))
             
+            # Test extra trailing slash (should be redirected if configured).
+            self.getPage("/dir1/myMethod/")
+            self.assertStatus((302, 303))
+            self.assertHeader('Location', 'http://%s:%s%s/dir1/myMethod'
+                              % (self.HOST, self.PORT, prefix))
+            
+            # Test that default method must be exposed in order to match.
             self.getPage("/dir1/dir2/dir3/dir4/index")
             self.assertBody("default for dir1, param is:('dir2', 'dir3', 'dir4', 'index')")
             
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.