From df80d5b0136acba96247a23601849b83b189c87a Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Sun, 6 May 2018 11:59:01 -0700 Subject: [PATCH 1/5] Failing test that lists a directory wit a text path. --- src/twisted/web/test/test_static.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/twisted/web/test/test_static.py b/src/twisted/web/test/test_static.py index 77f805c4c13..b4640de67c6 100644 --- a/src/twisted/web/test/test_static.py +++ b/src/twisted/web/test/test_static.py @@ -155,6 +155,28 @@ def test_emptyChild(self): self.assertEqual(child.path, base.path) + def test_emptyChildUnicodeParent(self): + """ + The C{u''} child of a L{File} which corresponds to a directory + whose path is text is a L{DirectoryLister} that renders to a + binary listing. + + @see: U{https://twistedmatrix.com/trac/ticket/9438} + """ + textBase = FilePath(self.mktemp()).asTextMode() + textBase.makedirs() + textBase.child(u"text-file").open('w').close() + textFile = static.File(textBase.path) + + request = DummyRequest([b'']) + child = resource.getChildForRequest(textFile, request) + self.assertIsInstance(child, static.DirectoryLister) + self.assertEqual(child.path, textBase.asBytesMode().path) + + response = child.render(request) + self.assertIsInstance(response, bytes) + + def test_securityViolationNotFound(self): """ If a request is made which encounters a L{File} before a final segment From 7e9cb28cb5637ed5bb6604e5b00e0bc2d809ad02 Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Sun, 6 May 2018 12:00:55 -0700 Subject: [PATCH 2/5] Encode directories with text paths to bytes on Python 2. This prevents the template from being promoted to unicode via interpolation of unicode paths. --- src/twisted/web/static.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/twisted/web/static.py b/src/twisted/web/static.py index 575570a9c1a..7ffd6fa6d87 100644 --- a/src/twisted/web/static.py +++ b/src/twisted/web/static.py @@ -263,9 +263,31 @@ def ignoreExt(self, ext): childNotFound = resource.NoResource("File not found.") forbidden = resource.ForbiddenResource() + def directoryListing(self): - return DirectoryLister(self.path, - self.listNames(), + """ + Return a resource that generates an HTML listing of the + directory this path represents. + + @return: A L{resource.Resource} that renders the directory to + HTML. + """ + if _PY3: + path = self.path + names = self.listNames() + else: + # DirectoryLister works in terms of native strings, so on + # Python 2, ensure we have a bytes paths for this + # directory and its contents. We use the asBytesMode + # method inherited from FilePath to ensure consistent + # encoding of the actual path. This returns a FilePath + # instance even when called on subclasses, however, so we + # have to create a new File instance. + nativeStringPath = self.createSimilarFile(self.asBytesMode().path) + path = nativeStringPath.path + names = nativeStringPath.listNames() + return DirectoryLister(path, + names, self.contentTypes, self.contentEncodings, self.defaultType) From 65cf988d2719a6c861cc8d31dd4befb12f3a984c Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Sun, 6 May 2018 12:04:20 -0700 Subject: [PATCH 3/5] Newsfragment. --- src/twisted/web/newsfragments/9438.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/twisted/web/newsfragments/9438.bugfix diff --git a/src/twisted/web/newsfragments/9438.bugfix b/src/twisted/web/newsfragments/9438.bugfix new file mode 100644 index 00000000000..8795009c661 --- /dev/null +++ b/src/twisted/web/newsfragments/9438.bugfix @@ -0,0 +1 @@ +twisted.web.static.File renders directory listings on Python 2, including those with text paths. \ No newline at end of file From a672a910b620e532b535e9382470be84af9f8d71 Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Sun, 6 May 2018 12:26:52 -0700 Subject: [PATCH 4/5] directory path is a native string; skip test on unsupported FSes. --- src/twisted/web/test/test_static.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/twisted/web/test/test_static.py b/src/twisted/web/test/test_static.py index b4640de67c6..1cb3c11ed59 100644 --- a/src/twisted/web/test/test_static.py +++ b/src/twisted/web/test/test_static.py @@ -20,7 +20,7 @@ from twisted.internet import abstract, interfaces from twisted.python.runtime import platform from twisted.python.filepath import FilePath -from twisted.python import log +from twisted.python import compat, log from twisted.python.compat import intToBytes, networkString from twisted.trial.unittest import TestCase from twisted.web import static, http, script, resource @@ -171,10 +171,16 @@ def test_emptyChildUnicodeParent(self): request = DummyRequest([b'']) child = resource.getChildForRequest(textFile, request) self.assertIsInstance(child, static.DirectoryLister) - self.assertEqual(child.path, textBase.asBytesMode().path) + + nativePath = compat.nativeString(textBase.path) + self.assertEqual(child.path, nativePath) response = child.render(request) self.assertIsInstance(response, bytes) + if sys.getfilesystemencoding().lower() not in ('utf-8', 'mcbs'): + test_emptyChildUnicodeParent.skip = ( + "Cannot write unicode filenames with file system encoding of" + " %s" % (sys.getfilesystemencoding(),)) def test_securityViolationNotFound(self): From 54bd72a5b26328a01f9571bbd58d4c39bdd43283 Mon Sep 17 00:00:00 2001 From: Mark Williams Date: Sun, 6 May 2018 23:55:23 -0700 Subject: [PATCH 5/5] Remove useless skip and explicitly name DirectoryLister. It should be impossible for SynchronousTestCase.mkstemp().decode(sys.getfilesystemencoding()) + u'' to return an unencodable path, so the skip is unnecessary. --- src/twisted/web/static.py | 4 ++-- src/twisted/web/test/test_static.py | 4 ---- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/twisted/web/static.py b/src/twisted/web/static.py index 489635a546e..c603b6bbdd1 100644 --- a/src/twisted/web/static.py +++ b/src/twisted/web/static.py @@ -277,8 +277,8 @@ def directoryListing(self): Return a resource that generates an HTML listing of the directory this path represents. - @return: A L{resource.Resource} that renders the directory to - HTML. + @return: A resource that renders the directory to HTML. + @rtype: L{DirectoryLister} """ if _PY3: path = self.path diff --git a/src/twisted/web/test/test_static.py b/src/twisted/web/test/test_static.py index 1cb3c11ed59..8d66b4b4335 100644 --- a/src/twisted/web/test/test_static.py +++ b/src/twisted/web/test/test_static.py @@ -177,10 +177,6 @@ def test_emptyChildUnicodeParent(self): response = child.render(request) self.assertIsInstance(response, bytes) - if sys.getfilesystemencoding().lower() not in ('utf-8', 'mcbs'): - test_emptyChildUnicodeParent.skip = ( - "Cannot write unicode filenames with file system encoding of" - " %s" % (sys.getfilesystemencoding(),)) def test_securityViolationNotFound(self):