Commits

Robert Brewer committed 8c6a5df

Improvements to cherrypy.url:

1. Bugfix for relative input ("/leaf" + "new"}}} was generating "/leaf/new", now generates "/new").
2. Support for single and double dots.
3. New 'relative' arg for producing relative output.

Comments (0)

Files changed (2)

cherrypy/__init__.py

 
 import os as _os
 _localdir = _os.path.dirname(__file__)
+from urlparse import urljoin as _urljoin
 
 from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect, NotFound, CherryPyException
 from cherrypy._cperror import TimeoutError
             # expose is returning a decorator "@expose(alias=...)"
             return expose_
 
-def url(path="", qs="", script_name=None, base=None):
+def url(path="", qs="", script_name=None, base=None, relative=False):
     """Create an absolute URL for the given path.
     
     If 'path' starts with a slash ('/'), this will return
     If script_name is None, cherrypy.request will be used
     to find a script_name, if available.
     
-    If base is None, cherrypy.request.base will be used if available.
+    If base is None, cherrypy.request.base will be used (if available).
     Note that you can use cherrypy.tools.proxy to change this.
     
     Finally, note that this function can be used to obtain an absolute URL
         if path == "":
             path = request.path_info
         if not path.startswith("/"):
-            path = request.path_info + "/" + path
+            path = _urljoin(request.path_info, path)
         if script_name is None:
             script_name = request.app.script_name
         if base is None:
             base = request.base
         
-        return base + script_name + path + qs
+        newurl = base + script_name + path + qs
     else:
         # No request.app (we're being called outside a request).
         # We'll have to guess the base from server.* attributes.
                         host += ":%s" % port
                 base = "%s://%s" % (scheme, host)
         path = (script_name or "") + path
-        return base + path + qs
+        newurl = base + path + qs
+    
+    if './' in newurl:
+        # Normalize the URL by removing ./ and ../
+        atoms = []
+        for atom in newurl.split('/'):
+            if atom == '.':
+                pass
+            elif atom == '..':
+                atoms.pop()
+            else:
+                atoms.append(atom)
+        newurl = '/'.join(atoms)
+    
+    if relative:
+        old = url().split('/')[:-1]
+        new = newurl.split('/')
+        while old and new:
+            a, b = old[0], new[0]
+            if a != b:
+                break
+            old.pop(0)
+            new.pop(0)
+        new = (['..'] * len(old)) + new
+        newurl = '/'.join(new)
+    
+    return newurl
+
 
 # Set up config last so it can wrap other top-level objects
 from cherrypy import _cpconfig

cherrypy/test/test_core.py

         upload.exposed = True
     
     root = Root()
-
-
+    
+    
     class TestType(type):
         """Metaclass which automatically exposes all functions in each subclass,
         and adds an instance of the subclass as an attribute of root.
             setattr(root, name.lower(), cls())
     class Test(object):
         __metaclass__ = TestType
-
-
+    
+    
+    class URL(Test):
+        
+        def index(self, path_info, relative=None):
+            return cherrypy.url(path_info, relative=bool(relative))
+        
+        def leaf(self, path_info, relative=None):
+            return cherrypy.url(path_info, relative=bool(relative))
+    
+    
     class Params(Test):
         
         def index(self, thing):
         self.getPage('/')
         self.assertHeader('Content-Type', 'text/plain')
         self.getPage('/defct/html')
+    
+    def test_cherrypy_url(self):
+        # Input relative to current
+        self.getPage('/url/leaf?path_info=page1')
+        self.assertBody('%s/url/page1' % self.base())
+        self.getPage('/url/?path_info=page1')
+        self.assertBody('%s/url/page1' % self.base())
+        
+        # Input is 'absolute'; that is, relative to script_name
+        self.getPage('/url/leaf?path_info=/page1')
+        self.assertBody('%s/page1' % self.base())
+        self.getPage('/url/?path_info=/page1')
+        self.assertBody('%s/page1' % self.base())
+        
+        # Single dots
+        self.getPage('/url/leaf?path_info=./page1')
+        self.assertBody('%s/url/page1' % self.base())
+        self.getPage('/url/leaf?path_info=other/./page1')
+        self.assertBody('%s/url/other/page1' % self.base())
+        self.getPage('/url/?path_info=/other/./page1')
+        self.assertBody('%s/other/page1' % self.base())
+        
+        # Double dots
+        self.getPage('/url/leaf?path_info=../page1')
+        self.assertBody('%s/page1' % self.base())
+        self.getPage('/url/leaf?path_info=other/../page1')
+        self.assertBody('%s/url/page1' % self.base())
+        self.getPage('/url/leaf?path_info=/other/../page1')
+        self.assertBody('%s/page1' % self.base())
+        
+        # Output relative to current path or script_name
+        self.getPage('/url/?path_info=page1&relative=True')
+        self.assertBody('page1')
+        self.getPage('/url/leaf?path_info=/page1&relative=True')
+        self.assertBody('../page1')
+        self.getPage('/url/leaf?path_info=../page1&relative=True')
+        self.assertBody('../page1')
+        self.getPage('/url/?path_info=other/../page1&relative=True')
+        self.assertBody('page1')
 
 
 if __name__ == '__main__':
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.