r46802 - Merge addSlash-8169-3: Fix twisted.web.static.addSlash

hawkowl-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: hawkowl
Date: Mon Feb 15 17:56:37 2016
New Revision: 46802

Added:
   trunk/twisted/web/topfiles/8169.bugfix
   trunk/twisted/web/topfiles/8169.removal
Modified:
   trunk/twisted/web/static.py
   trunk/twisted/web/test/requesthelper.py
   trunk/twisted/web/test/test_static.py

Log:
Merge addSlash-8169-3: Fix twisted.web.static.addSlash

Author: hawkowl
Reviewer: adiroiban
Fixes: #8169

Modified: trunk/twisted/web/static.py
==============================================================================
--- trunk/twisted/web/static.py	(original)
+++ trunk/twisted/web/static.py	Mon Feb 15 17:56:37 2016
@@ -29,6 +29,9 @@
 from twisted.internet import abstract, interfaces
 from twisted.python.util import InsensitiveDict
 from twisted.python.runtime import platformType
+from twisted.python.url import URL
+from twisted.python.versions import Version
+from twisted.python.deprecate import deprecated
 
 if _PY3:
     from urllib.parse import quote, unquote
@@ -61,22 +64,37 @@
     render_HEAD = render_GET
 
 
+
+@deprecated(Version("Twisted", 16, 0, 0))
 def addSlash(request):
-    qs = ''
-    qindex = request.uri.find('?')
-    if qindex != -1:
-        qs = request.uri[qindex:]
-
-    return "http%s://%s%s/%s" % (
-        request.isSecure() and 's' or '',
-        request.getHeader("host"),
-        (request.uri.split('?')[0]),
-        qs)
+    """
+    Add a trailing slash to C{request}'s URI. Deprecated, do not use.
+    """
+    return _addSlash(request)
+
+
+
+def _addSlash(request):
+    """
+    Add a trailing slash to C{request}'s URI.
+
+    @param request: The incoming request to add the ending slash to.
+    @type request: An object conforming to L{twisted.web.iweb.IRequest}
+
+    @return: A URI with a trailing slash, with query and fragment preserved.
+    @rtype: L{bytes}
+    """
+    url = URL.fromText(request.uri.decode('ascii'))
+    # Add an empty path segment at the end, so that it adds a trailing slash
+    url = url.replace(path=list(url.path) + [u""])
+    return url.asText().encode('ascii')
+
+
 
 class Redirect(resource.Resource):
     def __init__(self, request):
         resource.Resource.__init__(self)
-        self.url = addSlash(request)
+        self.url = _addSlash(request)
 
     def render(self, request):
         return redirectTo(self.url, request)
@@ -610,7 +628,7 @@
 
 
     def redirect(self, request):
-        return redirectTo(addSlash(request), request)
+        return redirectTo(_addSlash(request), request)
 
 
     def listNames(self):

Modified: trunk/twisted/web/test/requesthelper.py
==============================================================================
--- trunk/twisted/web/test/requesthelper.py	(original)
+++ trunk/twisted/web/test/requesthelper.py	Mon Feb 15 17:56:37 2016
@@ -23,6 +23,7 @@
 from twisted.web.http_headers import Headers
 from twisted.web.resource import Resource
 from twisted.web.server import NOT_DONE_YET, Session, Site
+from twisted.web._responses import FOUND
 
 
 class DummyChannel:
@@ -73,7 +74,7 @@
 
 class DummyRequest(object):
     """
-    Represents a dummy or fake request.
+    Represents a dummy or fake request. See L{twisted.web.server.Request}.
 
     @ivar _finishedDeferreds: C{None} or a C{list} of L{Deferreds} which will
         be called back with C{None} when C{finish} is called or which will be
@@ -98,11 +99,13 @@
     method = b'GET'
     client = None
 
+
     def registerProducer(self, prod,s):
         self.go = 1
         while self.go:
             prod.resumeProducing()
 
+
     def unregisterProducer(self):
         self.go = 0
 
@@ -123,6 +126,7 @@
         self._serverName = b"dummy"
         self.clientproto = b"HTTP/1.0"
 
+
     def getAllHeaders(self):
         """
         Return dictionary mapping the names of all received headers to the last
@@ -139,6 +143,7 @@
             headers[k.lower()] = v[-1]
         return headers
 
+
     def getHeader(self, name):
         """
         Retrieve the value of a request header.
@@ -158,6 +163,7 @@
         """
         self.responseHeaders.addRawHeader(name, value)
 
+
     def getSession(self):
         if self.session:
             return self.session
@@ -191,6 +197,7 @@
             raise TypeError("write() only accepts bytes")
         self.written.append(data)
 
+
     def notifyFinish(self):
         """
         Return a L{Deferred} which is called back with C{None} when the request
@@ -277,6 +284,7 @@
         """
         return IPv4Address('TCP', '127.0.0.1', 80)
 
+
     def setHost(self, host, port, ssl=0):
         """
         Change the host and port the request thinks it's using.
@@ -299,6 +307,7 @@
             hostHeader = host + b":" + intToBytes(port)
         self.requestHeaders.addRawHeader(b"host", hostHeader)
 
+
     def getClient(self):
         """
         Get the client's IP address, if it has one.
@@ -308,6 +317,16 @@
         """
         return self.getClientIP()
 
+
+    def redirect(self, url):
+        """
+        Utility function that does a redirect.
+
+        The request should have finish() called after this.
+        """
+        self.setResponseCode(FOUND)
+        self.setHeader(b"location", url)
+
 DummyRequest.getClient = deprecated(
     Version("Twisted", 15, 0, 0),
     "Twisted Names to resolve hostnames")(DummyRequest.getClient)

Modified: trunk/twisted/web/test/test_static.py
==============================================================================
--- trunk/twisted/web/test/test_static.py	(original)
+++ trunk/twisted/web/test/test_static.py	Mon Feb 15 17:56:37 2016
@@ -25,6 +25,7 @@
 from twisted.web.server import UnsupportedMethod
 from twisted.web.test.requesthelper import DummyRequest
 from twisted.web.test._util import _render
+from twisted.web._responses import FOUND
 
 
 class StaticDataTests(TestCase):
@@ -382,6 +383,26 @@
         return d
 
 
+    def test_directoryWithoutTrailingSlashRedirects(self):
+        """
+        A request for a path which is a directory but does not have a trailing
+        slash will be redirected to a URL which does have a slash by L{File}.
+        """
+        base = FilePath(self.mktemp())
+        base.makedirs()
+        base.child('folder').makedirs()
+        file = static.File(base.path)
+
+        request = DummyRequest([b"folder"])
+        request.uri = b"http://dummy/folder#baz?foo=bar"
+        child = resource.getChildForRequest(file, request)
+
+        self.successResultOf(self._render(child, request))
+        self.assertEqual(request.responseCode, FOUND)
+        self.assertEqual(request.responseHeaders.getRawHeaders(b"location"),
+                         [b"http://dummy/folder/#baz?foo=bar"])
+
+
     def _makeFilePathWithStringIO(self):
         """
         Create a L{File} that when opened for reading, returns a L{StringIO}.
@@ -1689,3 +1710,19 @@
         args, _, _, defaults = inspect.getargspec(static.loadMimeTypes)
         defaultInit = defaults[args.index("init")]
         self.assertIdentical(defaultInit, mimetypes.init)
+
+
+class StaticDeprecationTests(TestCase):
+
+    def test_addSlashDeprecated(self):
+        """
+        L{twisted.web.static.addSlash} is deprecated.
+        """
+        from twisted.web.static import addSlash
+
+        addSlash(DummyRequest([b'']))
+
+        warnings = self.flushWarnings([self.test_addSlashDeprecated])
+        self.assertEqual(len(warnings), 1)
+        self.assertEqual(warnings[0]['message'],
+            "twisted.web.static.addSlash was deprecated in Twisted 16.0.0")
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.