Sign Up
Log In
Log In
or
Sign Up
Places
All Projects
Status Monitor
Collapse sidebar
openSUSE:Step:15-SP6
python-Twisted.26705
CVE-2022-39348-do-not-echo-host-header.patch
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File CVE-2022-39348-do-not-echo-host-header.patch of Package python-Twisted.26705
From 869fbe6b2cc1f7b803085c51b69e0d1f23a6d80b Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Thu, 20 Oct 2022 23:19:53 -0700 Subject: [PATCH 01/12] Deprecate twisted.web.resource.ErrorPage and spawn --- src/twisted/web/newsfragments/11716.feature | 1 + src/twisted/web/newsfragments/11716.removal | 1 + src/twisted/web/resource.py | 69 +++++++++++++++++---- src/twisted/web/test/test_resource.py | 51 +++++++++++++-- 4 files changed, 106 insertions(+), 16 deletions(-) create mode 100644 src/twisted/web/newsfragments/11716.feature create mode 100644 src/twisted/web/newsfragments/11716.removal diff --git a/src/twisted/web/newsfragments/11716.feature b/src/twisted/web/newsfragments/11716.feature new file mode 100644 index 00000000000..5693458b403 --- /dev/null +++ b/src/twisted/web/newsfragments/11716.feature @@ -0,0 +1 @@ +The twisted.web.pages.ErrorPage, NotFoundPage, and ForbiddenPage IResource implementations provide HTML error pages rendered safely using twisted.web.template. diff --git a/src/twisted/web/newsfragments/11716.removal b/src/twisted/web/newsfragments/11716.removal new file mode 100644 index 00000000000..f4d2b36f415 --- /dev/null +++ b/src/twisted/web/newsfragments/11716.removal @@ -0,0 +1 @@ +The twisted.web.resource.ErrorPage, NoResource, and ForbiddenResource classes have been deprecated in favor of new implementations twisted.web.pages module because they permit HTML injection. diff --git a/src/twisted/web/resource.py b/src/twisted/web/resource.py index 5e6bd83f908..93c780740fc 100644 --- a/src/twisted/web/resource.py +++ b/src/twisted/web/resource.py @@ -1,9 +1,11 @@ -# -*- test-case-name: twisted.web.test.test_web -*- +# -*- test-case-name: twisted.web.test.test_web, twisted.web.test.test_resource -*- # Copyright (c) Twisted Matrix Laboratories. # See LICENSE for details. """ Implementation of the lowest-level Resource class. + +See L{twisted.web.pages} for some utility implementations. """ @@ -21,8 +23,11 @@ from zope.interface import Attribute, Interface, implementer +from incremental import Version + from twisted.python.compat import nativeString from twisted.python.components import proxyForInterface +from twisted.python.deprecate import deprecatedModuleAttribute from twisted.python.reflect import prefixedMethodNames from twisted.web._responses import FORBIDDEN, NOT_FOUND from twisted.web.error import UnsupportedMethod @@ -286,20 +291,25 @@ def _computeAllowedMethods(resource): return allowedMethods -class ErrorPage(Resource): +class _UnsafeErrorPage(Resource): """ - L{ErrorPage} is a resource which responds with a particular + L{_UnsafeErrorPage}, publicly available via the deprecated alias + C{ErrorPage}, is a resource which responds with a particular (parameterized) status and a body consisting of HTML containing some descriptive text. This is useful for rendering simple error pages. + Deprecated in Twisted NEXT because it permits HTML injection; use + L{twisted.pages.ErrorPage} instead. + @ivar template: A native string which will have a dictionary interpolated into it to generate the response body. The dictionary has the following keys: - - C{"code"}: The status code passed to L{ErrorPage.__init__}. - - C{"brief"}: The brief description passed to L{ErrorPage.__init__}. + - C{"code"}: The status code passed to L{_UnsafeErrorPage.__init__}. + - C{"brief"}: The brief description passed to + L{_UnsafeErrorPage.__init__}. - C{"detail"}: The detailed description passed to - L{ErrorPage.__init__}. + L{_UnsafeErrorPage.__init__}. @ivar code: An integer status code which will be used for the response. @type code: C{int} @@ -342,26 +352,61 @@ def getChild(self, chnam, request): return self -class NoResource(ErrorPage): +class _UnsafeNoResource(_UnsafeErrorPage): """ - L{NoResource} is a specialization of L{ErrorPage} which returns the HTTP - response code I{NOT FOUND}. + L{_UnsafeNoResource}, publicly available via the deprecated alias + C{NoResource}, is a specialization of L{_UnsafeErrorPage} which + returns the HTTP response code I{NOT FOUND}. + + Deprecated in Twisted NEXT because it permits HTML injection; use + L{twisted.pages.NotFoundPage} instead. """ def __init__(self, message="Sorry. No luck finding that resource."): ErrorPage.__init__(self, NOT_FOUND, "No Such Resource", message) -class ForbiddenResource(ErrorPage): +class _UnsafeForbiddenResource(_UnsafeErrorPage): """ - L{ForbiddenResource} is a specialization of L{ErrorPage} which returns the - I{FORBIDDEN} HTTP response code. + L{_UnsafeForbiddenResource}, publicly available via the deprecated alias + C{ForbiddenResource} is a specialization of L{_UnsafeErrorPage} which + returns the I{FORBIDDEN} HTTP response code. + + Deprecated in Twisted NEXT because it permits HTML injection; use + L{twisted.pages.ForbiddenPage} instead. """ def __init__(self, message="Sorry, resource is forbidden."): ErrorPage.__init__(self, FORBIDDEN, "Forbidden Resource", message) +# Deliberately undocumented public aliases. See GHSA-vg46-2rrj-3647. +ErrorPage = _UnsafeErrorPage +NoResource = _UnsafeNoResource +ForbiddenResource = _UnsafeForbiddenResource + +deprecatedModuleAttribute( + Version("Twisted", "NEXT", 0, 0), + "Use twisted.pages.ErrorPage instead, which properly escapes HTML.", + __name__, + "ErrorPage", +) + +deprecatedModuleAttribute( + Version("Twisted", "NEXT", 0, 0), + "Use twisted.pages.NotFoundPage instead, which properly escapes HTML.", + __name__, + "NoResource", +) + +deprecatedModuleAttribute( + Version("Twisted", "NEXT", 0, 0), + "Use twisted.pages.ForbiddenPage instead, which properly escapes HTML.", + __name__, + "ForbiddenResource", +) + + class _IEncodingResource(Interface): """ A resource which knows about L{_IRequestEncoderFactory}. diff --git a/src/twisted/web/test/test_resource.py b/src/twisted/web/test/test_resource.py index bd2f90887da..3e83d0efdc2 100644 --- a/src/twisted/web/test/test_resource.py +++ b/src/twisted/web/test/test_resource.py @@ -11,10 +11,10 @@ from twisted.web.resource import ( FORBIDDEN, NOT_FOUND, - ErrorPage, - ForbiddenResource, - NoResource, Resource, + _UnsafeErrorPage as ErrorPage, + _UnsafeForbiddenResource as ForbiddenResource, + _UnsafeNoResource as NoResource, getChildForRequest, ) from twisted.web.test.requesthelper import DummyRequest @@ -22,13 +22,56 @@ class ErrorPageTests(TestCase): """ - Tests for L{ErrorPage}, L{NoResource}, and L{ForbiddenResource}. + Tests for L{_UnafeErrorPage}, L{_UnsafeNoResource}, and + L{_UnsafeForbiddenResource}. """ errorPage = ErrorPage noResource = NoResource forbiddenResource = ForbiddenResource + def test_deprecatedErrorPage(self): + """ + The public C{twisted.web.resource.ErrorPage} alias for the + corresponding C{_Unsafe} class produces a deprecation warning when + imported. + """ + from twisted.web.resource import ErrorPage + + self.assertIs(ErrorPage, self.errorPage) + + [warning] = self.flushWarnings() + self.assertEqual(warning["category"], DeprecationWarning) + self.assertIn("twisted.pages.ErrorPage", warning["message"]) + + def test_deprecatedNoResource(self): + """ + The public C{twisted.web.resource.NoResource} alias for the + corresponding C{_Unsafe} class produces a deprecation warning when + imported. + """ + from twisted.web.resource import NoResource + + self.assertIs(NoResource, self.noResource) + + [warning] = self.flushWarnings() + self.assertEqual(warning["category"], DeprecationWarning) + self.assertIn("twisted.pages.NotFoundPage", warning["message"]) + + def test_deprecatedForbiddenResource(self): + """ + The public C{twisted.web.resource.ForbiddenResource} alias for the + corresponding C{_Unsafe} class produce a deprecation warning when + imported. + """ + from twisted.web.resource import ForbiddenResource + + self.assertIs(ForbiddenResource, self.forbiddenResource) + + [warning] = self.flushWarnings() + self.assertEqual(warning["category"], DeprecationWarning) + self.assertIn("twisted.pages.ForbiddenPage", warning["message"]) + def test_getChild(self): """ The C{getChild} method of L{ErrorPage} returns the L{ErrorPage} it is From 5ce023c4d735a895a03f2eb4a622a2322b8990ec Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Thu, 20 Oct 2022 23:38:19 -0700 Subject: [PATCH 02/12] Implement twisted.web.pages --- src/twisted/web/_template_util.py | 6 +- src/twisted/web/newsfragments/11716.feature | 2 +- src/twisted/web/pages.py | 108 ++++++++++++++++++++ src/twisted/web/resource.py | 10 +- src/twisted/web/test/test_pages.py | 106 +++++++++++++++++++ src/twisted/web/test/test_resource.py | 4 +- 6 files changed, 225 insertions(+), 11 deletions(-) create mode 100644 src/twisted/web/pages.py create mode 100644 src/twisted/web/test/test_pages.py diff --git a/src/twisted/web/_template_util.py b/src/twisted/web/_template_util.py index bd081bd54ab..38ebbed1d5b 100644 --- a/src/twisted/web/_template_util.py +++ b/src/twisted/web/_template_util.py @@ -1034,9 +1034,9 @@ class _TagFactory: """ A factory for L{Tag} objects; the implementation of the L{tags} object. - This allows for the syntactic convenience of C{from twisted.web.html import - tags; tags.a(href="linked-page.html")}, where 'a' can be basically any HTML - tag. + This allows for the syntactic convenience of C{from twisted.web.template + import tags; tags.a(href="linked-page.html")}, where 'a' can be basically + any HTML tag. The class is not exposed publicly because you only ever need one of these, and we already made it for you. diff --git a/src/twisted/web/newsfragments/11716.feature b/src/twisted/web/newsfragments/11716.feature index 5693458b403..e8ba00b7ef2 100644 --- a/src/twisted/web/newsfragments/11716.feature +++ b/src/twisted/web/newsfragments/11716.feature @@ -1 +1 @@ -The twisted.web.pages.ErrorPage, NotFoundPage, and ForbiddenPage IResource implementations provide HTML error pages rendered safely using twisted.web.template. +The twisted.web.pages.ErrorPage, notFound, and forbidden IResource implementations provide HTML error pages safely rendered using twisted.web.template. diff --git a/src/twisted/web/pages.py b/src/twisted/web/pages.py new file mode 100644 index 00000000000..8f37b3e45a8 --- /dev/null +++ b/src/twisted/web/pages.py @@ -0,0 +1,108 @@ +# -*- test-case-name: twisted.web.test.test_pages -*- +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Utility implementations of L{IResource}. +""" + +__all__ = ( + "ErrorPage", + "notFound", + "forbidden", +) + + +from twisted.web import http +from twisted.web.iweb import IRequest +from twisted.web.resource import Resource +from twisted.web.template import renderElement, tags + + +class ErrorPage(Resource): + """ + L{ErrorPage} is a resource that responds to all requests with a particular + (parameterized) HTTP status code and a body consisting of HTML containing + some descriptive text. This is useful for rendering simple error pages. + + @ivar _code: An integer HTTP status code which will be used for the + response. + + @ivar _brief: A short string which will be included in the response body as + the page title. + + @ivar _detail: A longer string which will be included in the response body. + """ + + def __init__(self, code: int, brief: str, detail: str) -> None: + """ + @param code: An integer HTTP status code which will be used for the + response. + + @param brief: A short string which will be included in the response + body as the page title. + + @param detail: A longer string which will be included in the + response body. + """ + super().__init__() + self._code: int = code + self._brief: str = brief + self._detail: str = detail + + def render(self, request: IRequest) -> None: + """ + Respond to all requests with the given HTTP status code and an HTML + document containing the explanatory strings. + """ + request.setResponseCode(self._code) + request.setHeader(b"content-type", b"text/html; charset=utf-8") + renderElement( + request, + tags.html( + tags.head(tags.title(f"{self._code} - {self._brief}")), + tags.body(tags.h1(self._brief), tags.p(self._detail)), + ), + ) + + def getChild(self, path: bytes, request: IRequest) -> Resource: + """ + Handle all requests for which L{ErrorPage} lacks a child by returning + this error page. + + @param path: A path segment. + + @param request: HTTP request + """ + return self + + +def notFound( + brief: str = "No Such Resource", + message: str = "Sorry. No luck finding that resource.", +) -> ErrorPage: + """ + Generate an L{ErrorPage} with a 404 Not Found status code. + + @param brief: A short string displayed as the page title. + + @param brief: A longer string displayed in the page body. + + @returns: An L{ErrorPage} + """ + return ErrorPage(http.NOT_FOUND, brief, message) + + +def forbidden( + brief: str = "Forbidden Resource", message: str = "Sorry, resource is forbidden." +) -> ErrorPage: + """ + Generate an L{ErrorPage} with a 403 Forbidden status code. + + @param brief: A short string displayed as the page title. + + @param brief: A longer string displayed in the page body. + + @returns: An L{ErrorPage} + """ + return ErrorPage(http.FORBIDDEN, brief, message) diff --git a/src/twisted/web/resource.py b/src/twisted/web/resource.py index 93c780740fc..09fc74a89fd 100644 --- a/src/twisted/web/resource.py +++ b/src/twisted/web/resource.py @@ -183,7 +183,7 @@ def getChild(self, path, request): Parameters and return value have the same meaning and requirements as those defined by L{IResource.getChildWithDefault}. """ - return NoResource("No such child resource.") + return _UnsafeNoResource() def getChildWithDefault(self, path, request): """ @@ -359,7 +359,7 @@ class _UnsafeNoResource(_UnsafeErrorPage): returns the HTTP response code I{NOT FOUND}. Deprecated in Twisted NEXT because it permits HTML injection; use - L{twisted.pages.NotFoundPage} instead. + L{twisted.pages.notFound} instead. """ def __init__(self, message="Sorry. No luck finding that resource."): @@ -373,7 +373,7 @@ class _UnsafeForbiddenResource(_UnsafeErrorPage): returns the I{FORBIDDEN} HTTP response code. Deprecated in Twisted NEXT because it permits HTML injection; use - L{twisted.pages.ForbiddenPage} instead. + L{twisted.pages.forbidden} instead. """ def __init__(self, message="Sorry, resource is forbidden."): @@ -394,14 +394,14 @@ def __init__(self, message="Sorry, resource is forbidden."): deprecatedModuleAttribute( Version("Twisted", "NEXT", 0, 0), - "Use twisted.pages.NotFoundPage instead, which properly escapes HTML.", + "Use twisted.pages.notFound instead, which properly escapes HTML.", __name__, "NoResource", ) deprecatedModuleAttribute( Version("Twisted", "NEXT", 0, 0), - "Use twisted.pages.ForbiddenPage instead, which properly escapes HTML.", + "Use twisted.pages.forbidden instead, which properly escapes HTML.", __name__, "ForbiddenResource", ) diff --git a/src/twisted/web/test/test_pages.py b/src/twisted/web/test/test_pages.py new file mode 100644 index 00000000000..d83e0eba3e2 --- /dev/null +++ b/src/twisted/web/test/test_pages.py @@ -0,0 +1,106 @@ +# Copyright (c) Twisted Matrix Laboratories. +# See LICENSE for details. + +""" +Test L{twisted.web.pages} +""" + +from twisted.trial.unittest import SynchronousTestCase +from twisted.web.http_headers import Headers +from twisted.web.pages import ErrorPage, forbidden, notFound +from twisted.web.test.requesthelper import DummyRequest + + +def _render(resource: ErrorPage) -> DummyRequest: + """ + Render a response using the given resource. + + @param resource: The resource to use to handle the request. + + @returns: The request that the resource handled, + """ + request = DummyRequest([b""]) + resource.render(request) + return request + + +class ErrorPageTests(SynchronousTestCase): + """ + Test L{twisted.web.pages.ErrorPage} and its convencience helpers + L{notFound} and L{forbidden}. + """ + + maxDiff = None + + def assertResponse(self, request: DummyRequest, code: int, body: bytes) -> None: + self.assertEqual(request.responseCode, code) + self.assertEqual( + request.responseHeaders, + Headers({b"content-type": [b"text/html; charset=utf-8"]}), + ) + self.assertEqual( + # Decode to str because unittest somehow still doesn't diff bytes + # without truncating them in 2022. + b"".join(request.written).decode("latin-1"), + body.decode("latin-1"), + ) + + def test_escapesHTML(self): + """ + The I{brief} and I{detail} parameters are HTML-escaped on render. + """ + self.assertResponse( + _render(ErrorPage(400, "A & B", "<script>alert('oops!')")), + 400, + ( + b"<!DOCTYPE html>\n" + b"<html><head><title>400 - A & B</title></head>" + b"<body><h1>A & B</h1><p><script>alert('oops!')" + b"</p></body></html>" + ), + ) + + def test_getChild(self): + """ + The C{getChild} method of L{ErrorPage} returns the L{ErrorPage} it is + called on. + """ + page = ErrorPage(404, "foo", "bar") + self.assertIs( + page.getChild(b"name", DummyRequest([b""])), + page, + ) + + def test_notFoundDefaults(self): + """ + The default arguments to L{twisted.web.pages.notFound} produce + a reasonable error page. + """ + self.assertResponse( + _render(notFound()), + 404, + ( + b"<!DOCTYPE html>\n" + b"<html><head><title>404 - No Such Resource</title></head>" + b"<body><h1>No Such Resource</h1>" + b"<p>Sorry. No luck finding that resource.</p>" + b"</body></html>" + ), + ) + + def test_forbiddenDefaults(self): + """ + The default arguments to L{twisted.web.pages.forbidden} produce + a reasonable error page. + """ + self.assertResponse( + _render(forbidden()), + 403, + ( + b"<!DOCTYPE html>\n" + b"<html><head><title>403 - Forbidden Resource</title></head>" + b"<body><h1>Forbidden Resource</h1>" + b"<p>Sorry, resource is forbidden.</p>" + b"</body></html>" + ), + ) diff --git a/src/twisted/web/test/test_resource.py b/src/twisted/web/test/test_resource.py index 3e83d0efdc2..c039704a79d 100644 --- a/src/twisted/web/test/test_resource.py +++ b/src/twisted/web/test/test_resource.py @@ -56,7 +56,7 @@ def test_deprecatedNoResource(self): [warning] = self.flushWarnings() self.assertEqual(warning["category"], DeprecationWarning) - self.assertIn("twisted.pages.NotFoundPage", warning["message"]) + self.assertIn("twisted.pages.notFound", warning["message"]) def test_deprecatedForbiddenResource(self): """ @@ -70,7 +70,7 @@ def test_deprecatedForbiddenResource(self): [warning] = self.flushWarnings() self.assertEqual(warning["category"], DeprecationWarning) - self.assertIn("twisted.pages.ForbiddenPage", warning["message"]) + self.assertIn("twisted.pages.forbidden", warning["message"]) def test_getChild(self): """ From 7c48ed2b6282a49a73d31c7e952e0e115599c83f Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Sat, 22 Oct 2022 18:20:39 -0700 Subject: [PATCH 03/12] Update imports to avoid warnings Use _UnsafeErrorPage, _UnsafeNoResource, etc. symbols instead of the public aliases that provoke deprecation warnings. --- src/twisted/web/_auth/wrapper.py | 8 ++++---- src/twisted/web/distrib.py | 7 ++++--- src/twisted/web/script.py | 14 +++++++++----- src/twisted/web/server.py | 11 +++++++---- src/twisted/web/static.py | 6 +++--- 5 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/twisted/web/_auth/wrapper.py b/src/twisted/web/_auth/wrapper.py index 0f71380a4d8..cffdcff66c9 100644 --- a/src/twisted/web/_auth/wrapper.py +++ b/src/twisted/web/_auth/wrapper.py @@ -21,7 +21,7 @@ from twisted.logger import Logger from twisted.python.components import proxyForInterface from twisted.web import util -from twisted.web.resource import ErrorPage, IResource +from twisted.web.resource import IResource, _UnsafeErrorPage @implementer(IResource) @@ -52,7 +52,7 @@ def generateWWWAuthenticate(scheme, challenge): return b" ".join([scheme, b", ".join(lst)]) def quoteString(s): - return b'"' + s.replace(b"\\", br"\\").replace(b'"', br"\"") + b'"' + return b'"' + s.replace(b"\\", rb"\\").replace(b'"', rb"\"") + b'"' request.setResponseCode(401) for fact in self._credentialFactories: @@ -125,7 +125,7 @@ def _authorizedResource(self, request): return UnauthorizedResource(self._credentialFactories) except BaseException: self._log.failure("Unexpected failure from credentials factory") - return ErrorPage(500, None, None) + return _UnsafeErrorPage(500, "Internal Error", "") else: return util.DeferredResource(self._login(credentials)) @@ -213,7 +213,7 @@ def _loginFailed(self, result): "unexpected error", failure=result, ) - return ErrorPage(500, None, None) + return _UnsafeErrorPage(500, "Internal Error", "") def _selectParseHeader(self, header): """ diff --git a/src/twisted/web/distrib.py b/src/twisted/web/distrib.py index 56f83fe2792..05665278ed8 100644 --- a/src/twisted/web/distrib.py +++ b/src/twisted/web/distrib.py @@ -127,9 +127,10 @@ def failed(self, failure): # XXX: Argh. FIXME. failure = str(failure) self.request.write( - resource.ErrorPage( + resource._UnsafeErrorPage( http.INTERNAL_SERVER_ERROR, "Server Connection Lost", + # GHSA-vg46-2rrj-3647 note: _PRE does HTML-escape the input. "Connection to distributed server lost:" + util._PRE(failure), ).render(self.request) ) @@ -377,7 +378,7 @@ def getChild(self, name, request): pw_shell, ) = self._pwd.getpwnam(username) except KeyError: - return resource.NoResource() + return resource._UnsafeNoResource() if sub: twistdsock = os.path.join(pw_dir, self.userSocketName) rs = ResourceSubscription("unix", twistdsock) @@ -386,5 +387,5 @@ def getChild(self, name, request): else: path = os.path.join(pw_dir, self.userDirName) if not os.path.exists(path): - return resource.NoResource() + return resource._UnsafeNoResource() return static.File(path) diff --git a/src/twisted/web/script.py b/src/twisted/web/script.py index eaf4ab8c8fc..bc4a90f748a 100644 --- a/src/twisted/web/script.py +++ b/src/twisted/web/script.py @@ -49,7 +49,7 @@ def recache(self): self.doCache = 1 -noRsrc = resource.ErrorPage(500, "Whoops! Internal Error", rpyNoResource) +noRsrc = resource._UnsafeErrorPage(500, "Whoops! Internal Error", rpyNoResource) def ResourceScript(path, registry): @@ -81,7 +81,9 @@ def ResourceTemplate(path, registry): glob = { "__file__": _coerceToFilesystemEncoding("", path), - "resource": resource.ErrorPage(500, "Whoops! Internal Error", rpyNoResource), + "resource": resource._UnsafeErrorPage( + 500, "Whoops! Internal Error", rpyNoResource + ), "registry": registry, } @@ -133,10 +135,10 @@ def getChild(self, path, request): return ResourceScriptDirectory(fn, self.registry) if os.path.exists(fn): return ResourceScript(fn, self.registry) - return resource.NoResource() + return resource._UnsafeNoResource() def render(self, request): - return resource.NoResource().render(request) + return resource._UnsafeNoResource().render(request) class PythonScript(resource.Resource): @@ -178,7 +180,9 @@ def render(self, request): except OSError as e: if e.errno == 2: # file not found request.setResponseCode(http.NOT_FOUND) - request.write(resource.NoResource("File not found.").render(request)) + request.write( + resource._UnsafeNoResource("File not found.").render(request) + ) except BaseException: io = StringIO() traceback.print_exc(file=io) diff --git a/src/twisted/web/server.py b/src/twisted/web/server.py index d30156b895a..e8e01ec781b 100644 --- a/src/twisted/web/server.py +++ b/src/twisted/web/server.py @@ -335,10 +335,12 @@ def render(self, resrc): "allowed": ", ".join([nativeString(x) for x in allowedMethods]), } ) - epage = resource.ErrorPage(http.NOT_ALLOWED, "Method Not Allowed", s) + epage = resource._UnsafeErrorPage( + http.NOT_ALLOWED, "Method Not Allowed", s + ) body = epage.render(self) else: - epage = resource.ErrorPage( + epage = resource._UnsafeErrorPage( http.NOT_IMPLEMENTED, "Huh?", "I don't know how to treat a %s request." @@ -350,10 +352,11 @@ def render(self, resrc): if body is NOT_DONE_YET: return if not isinstance(body, bytes): - body = resource.ErrorPage( + body = resource._UnsafeErrorPage( http.INTERNAL_SERVER_ERROR, "Request did not return bytes", "Request: " + # GHSA-vg46-2rrj-3647 note: _PRE does HTML-escape the input. + util._PRE(reflect.safe_repr(self)) + "<br />" + "Resource: " @@ -607,7 +610,7 @@ class GzipEncoderFactory: @since: 12.3 """ - _gzipCheckRegex = re.compile(br"(:?^|[\s,])gzip(:?$|[\s,])") + _gzipCheckRegex = re.compile(rb"(:?^|[\s,])gzip(:?$|[\s,])") compressLevel = 9 def encoderForRequest(self, request): diff --git a/src/twisted/web/static.py b/src/twisted/web/static.py index 2689d3cfdab..09a2947f911 100644 --- a/src/twisted/web/static.py +++ b/src/twisted/web/static.py @@ -31,7 +31,7 @@ from twisted.web import http, resource, server from twisted.web.util import redirectTo -dangerousPathError = resource.NoResource("Invalid request URL.") +dangerousPathError = resource._UnsafeNoResource("Invalid request URL.") def isDangerous(path): @@ -255,8 +255,8 @@ def ignoreExt(self, ext): """ self.ignoredExts.append(ext) - childNotFound = resource.NoResource("File not found.") - forbidden = resource.ForbiddenResource() + childNotFound = resource._UnsafeNoResource("File not found.") + forbidden = resource._UnsafeForbiddenResource() def directoryListing(self): """ From 404cbc2455c6d25d4570c50e50958c342b3591f9 Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Sat, 22 Oct 2022 18:32:12 -0700 Subject: [PATCH 04/12] Update the docs --- docs/web/howto/web-in-60/error-handling.rst | 26 ++++++--------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/docs/web/howto/web-in-60/error-handling.rst b/docs/web/howto/web-in-60/error-handling.rst index 7717119f898..7cf1a789fec 100644 --- a/docs/web/howto/web-in-60/error-handling.rst +++ b/docs/web/howto/web-in-60/error-handling.rst @@ -32,21 +32,13 @@ As in the previous examples, we'll start with :py:class:`Site <twisted.web.serve -Next, we'll add one more import. :py:class:`NoResource <twisted.web.resource.NoResource>` is one of the pre-defined error +Next, we'll add one more import. :py:class:`notFound <twisted.web.pages.notFound>` is one of the pre-defined error resources provided by Twisted Web. It generates the necessary 404 response code -and renders a simple html page telling the client there is no such resource. - - - - +and renders a simple HTML page telling the client there is no such resource. .. code-block:: python - - from twisted.web.resource import NoResource - - - + from twisted.web.pages import notFound Next, we'll define a custom resource which does some dynamic URL dispatch. This example is going to be just like @@ -54,10 +46,6 @@ the :doc:`previous one <dynamic-dispatch>` , where the path segment is interpreted as a year; the difference is that this time we'll handle requests which don't conform to that pattern by returning the not found response: - - - - .. code-block:: python @@ -66,7 +54,7 @@ which don't conform to that pattern by returning the not found response: try: year = int(name) except ValueError: - return NoResource() + return notFound() else: return YearPage(year) @@ -88,7 +76,7 @@ complete code for this example: from twisted.web.server import Site from twisted.web.resource import Resource from twisted.internet import reactor, endpoints - from twisted.web.resource import NoResource + from twisted.web.pages import notFound from calendar import calendar @@ -100,14 +88,14 @@ complete code for this example: def render_GET(self, request): cal = calendar(self.year) return (b"<!DOCTYPE html><html><head><meta charset='utf-8'>" - b"<title></title></head><body><pre>" + cal.encode('utf-8') + "</pre>") + b"<title></title></head><body><pre>" + cal.encode('utf-8') + b"</pre>") class Calendar(Resource): def getChild(self, name, request): try: year = int(name) except ValueError: - return NoResource() + return notFound() else: return YearPage(year) From 6cf64b7782e92efeafc5e17e1b59e3bfc1e70c47 Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Sat, 22 Oct 2022 18:45:06 -0700 Subject: [PATCH 05/12] Address DummyRequest MyPy issue Filed https://github.com/twisted/twisted/issues/11719 for this. --- src/twisted/web/test/test_pages.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/twisted/web/test/test_pages.py b/src/twisted/web/test/test_pages.py index d83e0eba3e2..1f66c16afc9 100644 --- a/src/twisted/web/test/test_pages.py +++ b/src/twisted/web/test/test_pages.py @@ -5,8 +5,11 @@ Test L{twisted.web.pages} """ +from typing import cast + from twisted.trial.unittest import SynchronousTestCase from twisted.web.http_headers import Headers +from twisted.web.iweb import IRequest from twisted.web.pages import ErrorPage, forbidden, notFound from twisted.web.test.requesthelper import DummyRequest @@ -20,7 +23,10 @@ def _render(resource: ErrorPage) -> DummyRequest: @returns: The request that the resource handled, """ request = DummyRequest([b""]) - resource.render(request) + # The cast is necessary because DummyRequest isn't annotated + # as an IRequest, and this can't be trivially done. See + # https://github.com/twisted/twisted/issues/11719 + resource.render(cast(IRequest, request)) return request From 8a6437541e0a282ad13fa8ab3cb23e4cddae9bda Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Sat, 22 Oct 2022 19:25:51 -0700 Subject: [PATCH 06/12] Address IRenderable MyPy issue --- src/twisted/web/pages.py | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/twisted/web/pages.py b/src/twisted/web/pages.py index 8f37b3e45a8..524148fba26 100644 --- a/src/twisted/web/pages.py +++ b/src/twisted/web/pages.py @@ -12,9 +12,10 @@ "forbidden", ) +from typing import cast from twisted.web import http -from twisted.web.iweb import IRequest +from twisted.web.iweb import IRenderable, IRequest from twisted.web.resource import Resource from twisted.web.template import renderElement, tags @@ -50,18 +51,24 @@ def __init__(self, code: int, brief: str, detail: str) -> None: self._brief: str = brief self._detail: str = detail - def render(self, request: IRequest) -> None: + def render(self, request: IRequest) -> object: """ Respond to all requests with the given HTTP status code and an HTML document containing the explanatory strings. """ request.setResponseCode(self._code) request.setHeader(b"content-type", b"text/html; charset=utf-8") - renderElement( + return renderElement( request, - tags.html( - tags.head(tags.title(f"{self._code} - {self._brief}")), - tags.body(tags.h1(self._brief), tags.p(self._detail)), + # cast because the type annotations here seem off; Tag isn't an + # IRenderable but also probably should be? See + # https://github.com/twisted/twisted/issues/4982 + cast( + IRenderable, + tags.html( + tags.head(tags.title(f"{self._code} - {self._brief}")), + tags.body(tags.h1(self._brief), tags.p(self._detail)), + ), ), ) From a85a4904439a2f4783f0f2a85fc73c3c7837ac64 Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Sat, 22 Oct 2022 19:45:24 -0700 Subject: [PATCH 07/12] Failing test --- src/twisted/web/test/test_vhost.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/twisted/web/test/test_vhost.py b/src/twisted/web/test/test_vhost.py index f26d5e5d5e9..bb66b55537a 100644 --- a/src/twisted/web/test/test_vhost.py +++ b/src/twisted/web/test/test_vhost.py @@ -66,7 +66,7 @@ def test_renderWithoutHost(self): """ virtualHostResource = NameVirtualHost() virtualHostResource.default = Data(b"correct result", "") - request = DummyRequest([""]) + request = DummyRequest([b""]) self.assertEqual(virtualHostResource.render(request), b"correct result") def test_renderWithoutHostNoDefault(self): @@ -76,7 +76,7 @@ def test_renderWithoutHostNoDefault(self): header in the request. """ virtualHostResource = NameVirtualHost() - request = DummyRequest([""]) + request = DummyRequest([b""]) d = _render(virtualHostResource, request) def cbRendered(ignored): @@ -140,7 +140,7 @@ def test_renderWithUnknownHostNoDefault(self): matching the value of the I{Host} header in the request. """ virtualHostResource = NameVirtualHost() - request = DummyRequest([""]) + request = DummyRequest([b""]) request.requestHeaders.addRawHeader(b"host", b"example.com") d = _render(virtualHostResource, request) @@ -150,6 +150,19 @@ def cbRendered(ignored): d.addCallback(cbRendered) return d + async def test_renderWithHTMLHost(self): + """ + L{NameVirtualHost.render} doesn't echo unescaped HTML when present in + the I{Host} header. + """ + virtualHostResource = NameVirtualHost() + request = DummyRequest([b""]) + request.requestHeaders.addRawHeader(b"host", b"<b>example</b>.com") + + await _render(virtualHostResource, request) + + self.assertNotIn(b"<b>", b"".join(request.written)) + def test_getChild(self): """ L{NameVirtualHost.getChild} returns correct I{Resource} based off From d766a02b053d786e224b4d5449149bdd1bbedf84 Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Sat, 22 Oct 2022 20:01:50 -0700 Subject: [PATCH 08/12] Fix NameVirtualHost HTML injection vulnerability --- src/twisted/web/newsfragments/11716.bugfix | 1 + src/twisted/web/vhost.py | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 src/twisted/web/newsfragments/11716.bugfix diff --git a/src/twisted/web/newsfragments/11716.bugfix b/src/twisted/web/newsfragments/11716.bugfix new file mode 100644 index 00000000000..66189e685c5 --- /dev/null +++ b/src/twisted/web/newsfragments/11716.bugfix @@ -0,0 +1 @@ +twisted.web.vhost.NameVirtualHost no longer echoes HTML received in the Host header without escaping it (GHSA-vg46-2rrj-3647). diff --git a/src/twisted/web/vhost.py b/src/twisted/web/vhost.py index 2c305f94374..9576252b0f2 100644 --- a/src/twisted/web/vhost.py +++ b/src/twisted/web/vhost.py @@ -9,7 +9,7 @@ # Twisted Imports from twisted.python import roots -from twisted.web import resource +from twisted.web import pages, resource class VirtualHostCollection(roots.Homogenous): @@ -77,12 +77,13 @@ def removeHost(self, name): def _getResourceForRequest(self, request): """(Internal) Get the appropriate resource for the given host.""" hostHeader = request.getHeader(b"host") - if hostHeader == None: - return self.default or resource.NoResource() + if hostHeader is None: + return self.default or pages.notFound() else: host = hostHeader.lower().split(b":", 1)[0] - return self.hosts.get(host, self.default) or resource.NoResource( - "host %s not in vhost map" % repr(host) + return self.hosts.get(host, self.default) or pages.notFound( + "Not Found", + f"host {host.decode('ascii', 'replace')!r} not in vhost map", ) def render(self, request): From fee019520ebe31b79c904237e4ac3a7c86d65461 Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Mon, 24 Oct 2022 21:14:07 -0700 Subject: [PATCH 09/12] Fix references to twisted.pages --- src/twisted/web/resource.py | 12 ++++++------ src/twisted/web/test/test_resource.py | 6 +++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/twisted/web/resource.py b/src/twisted/web/resource.py index 09fc74a89fd..f196c3ac6b9 100644 --- a/src/twisted/web/resource.py +++ b/src/twisted/web/resource.py @@ -299,7 +299,7 @@ class _UnsafeErrorPage(Resource): descriptive text. This is useful for rendering simple error pages. Deprecated in Twisted NEXT because it permits HTML injection; use - L{twisted.pages.ErrorPage} instead. + L{twisted.web.pages.ErrorPage} instead. @ivar template: A native string which will have a dictionary interpolated into it to generate the response body. The dictionary has the following @@ -359,7 +359,7 @@ class _UnsafeNoResource(_UnsafeErrorPage): returns the HTTP response code I{NOT FOUND}. Deprecated in Twisted NEXT because it permits HTML injection; use - L{twisted.pages.notFound} instead. + L{twisted.web.pages.notFound} instead. """ def __init__(self, message="Sorry. No luck finding that resource."): @@ -373,7 +373,7 @@ class _UnsafeForbiddenResource(_UnsafeErrorPage): returns the I{FORBIDDEN} HTTP response code. Deprecated in Twisted NEXT because it permits HTML injection; use - L{twisted.pages.forbidden} instead. + L{twisted.web.pages.forbidden} instead. """ def __init__(self, message="Sorry, resource is forbidden."): @@ -387,21 +387,21 @@ def __init__(self, message="Sorry, resource is forbidden."): deprecatedModuleAttribute( Version("Twisted", "NEXT", 0, 0), - "Use twisted.pages.ErrorPage instead, which properly escapes HTML.", + "Use twisted.web.pages.ErrorPage instead, which properly escapes HTML.", __name__, "ErrorPage", ) deprecatedModuleAttribute( Version("Twisted", "NEXT", 0, 0), - "Use twisted.pages.notFound instead, which properly escapes HTML.", + "Use twisted.web.pages.notFound instead, which properly escapes HTML.", __name__, "NoResource", ) deprecatedModuleAttribute( Version("Twisted", "NEXT", 0, 0), - "Use twisted.pages.forbidden instead, which properly escapes HTML.", + "Use twisted.web.pages.forbidden instead, which properly escapes HTML.", __name__, "ForbiddenResource", ) diff --git a/src/twisted/web/test/test_resource.py b/src/twisted/web/test/test_resource.py index c039704a79d..cb37942dbb8 100644 --- a/src/twisted/web/test/test_resource.py +++ b/src/twisted/web/test/test_resource.py @@ -42,7 +42,7 @@ def test_deprecatedErrorPage(self): [warning] = self.flushWarnings() self.assertEqual(warning["category"], DeprecationWarning) - self.assertIn("twisted.pages.ErrorPage", warning["message"]) + self.assertIn("twisted.web.pages.ErrorPage", warning["message"]) def test_deprecatedNoResource(self): """ @@ -56,7 +56,7 @@ def test_deprecatedNoResource(self): [warning] = self.flushWarnings() self.assertEqual(warning["category"], DeprecationWarning) - self.assertIn("twisted.pages.notFound", warning["message"]) + self.assertIn("twisted.web.pages.notFound", warning["message"]) def test_deprecatedForbiddenResource(self): """ @@ -70,7 +70,7 @@ def test_deprecatedForbiddenResource(self): [warning] = self.flushWarnings() self.assertEqual(warning["category"], DeprecationWarning) - self.assertIn("twisted.pages.forbidden", warning["message"]) + self.assertIn("twisted.web.pages.forbidden", warning["message"]) def test_getChild(self): """ From 09ce75e3a7ee0675812ccd7e7164fb4a39970e38 Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Mon, 24 Oct 2022 21:25:21 -0700 Subject: [PATCH 10/12] Call the superclass constructor via private alias --- src/twisted/web/resource.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/twisted/web/resource.py b/src/twisted/web/resource.py index f196c3ac6b9..de3b557048a 100644 --- a/src/twisted/web/resource.py +++ b/src/twisted/web/resource.py @@ -363,7 +363,7 @@ class _UnsafeNoResource(_UnsafeErrorPage): """ def __init__(self, message="Sorry. No luck finding that resource."): - ErrorPage.__init__(self, NOT_FOUND, "No Such Resource", message) + _UnsafeErrorPage.__init__(self, NOT_FOUND, "No Such Resource", message) class _UnsafeForbiddenResource(_UnsafeErrorPage): @@ -377,7 +377,7 @@ class _UnsafeForbiddenResource(_UnsafeErrorPage): """ def __init__(self, message="Sorry, resource is forbidden."): - ErrorPage.__init__(self, FORBIDDEN, "Forbidden Resource", message) + _UnsafeErrorPage.__init__(self, FORBIDDEN, "Forbidden Resource", message) # Deliberately undocumented public aliases. See GHSA-vg46-2rrj-3647. From c0da7805fbb30611df8ac1bce3dffa2c6659373c Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Mon, 24 Oct 2022 21:42:30 -0700 Subject: [PATCH 11/12] =?UTF-8?q?twisted.web.pages.{ErrorPage=20=E2=86=92?= =?UTF-8?q?=20errorPage}?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/twisted/web/newsfragments/11716.feature | 2 +- src/twisted/web/pages.py | 69 +++++++++++++-------- src/twisted/web/resource.py | 4 +- src/twisted/web/test/test_pages.py | 15 ++--- src/twisted/web/test/test_resource.py | 2 +- 5 files changed, 56 insertions(+), 36 deletions(-) diff --git a/src/twisted/web/newsfragments/11716.feature b/src/twisted/web/newsfragments/11716.feature index e8ba00b7ef2..bdcd36d17bd 100644 --- a/src/twisted/web/newsfragments/11716.feature +++ b/src/twisted/web/newsfragments/11716.feature @@ -1 +1 @@ -The twisted.web.pages.ErrorPage, notFound, and forbidden IResource implementations provide HTML error pages safely rendered using twisted.web.template. +The twisted.web.pages.errorPage, notFound, and forbidden each return an IResource that displays an HTML error pages safely rendered using twisted.web.template. diff --git a/src/twisted/web/pages.py b/src/twisted/web/pages.py index 524148fba26..002b8a95d90 100644 --- a/src/twisted/web/pages.py +++ b/src/twisted/web/pages.py @@ -7,7 +7,7 @@ """ __all__ = ( - "ErrorPage", + "errorPage", "notFound", "forbidden", ) @@ -16,15 +16,17 @@ from twisted.web import http from twisted.web.iweb import IRenderable, IRequest -from twisted.web.resource import Resource +from twisted.web.resource import IResource, Resource from twisted.web.template import renderElement, tags -class ErrorPage(Resource): +class _ErrorPage(Resource): """ - L{ErrorPage} is a resource that responds to all requests with a particular - (parameterized) HTTP status code and a body consisting of HTML containing - some descriptive text. This is useful for rendering simple error pages. + L{_ErrorPage} is a resource that responds to all requests with a particular + (parameterized) HTTP status code and an HTML body containing some + descriptive text. This is useful for rendering simple error pages. + + @see: L{twisted.web.pages.errorPage} @ivar _code: An integer HTTP status code which will be used for the response. @@ -36,16 +38,6 @@ class ErrorPage(Resource): """ def __init__(self, code: int, brief: str, detail: str) -> None: - """ - @param code: An integer HTTP status code which will be used for the - response. - - @param brief: A short string which will be included in the response - body as the page title. - - @param detail: A longer string which will be included in the - response body. - """ super().__init__() self._code: int = code self._brief: str = brief @@ -74,7 +66,7 @@ def render(self, request: IRequest) -> object: def getChild(self, path: bytes, request: IRequest) -> Resource: """ - Handle all requests for which L{ErrorPage} lacks a child by returning + Handle all requests for which L{_ErrorPage} lacks a child by returning this error page. @param path: A path segment. @@ -84,32 +76,59 @@ def getChild(self, path: bytes, request: IRequest) -> Resource: return self +def errorPage(code: int, brief: str, detail: str) -> IResource: + """ + Build a resource that responds to all requests with a particular HTTP + status code and an HTML body containing some descriptive text. This is + useful for rendering simple error pages. + + The resource dynamically handles all paths below it. Use + L{IResource.putChild()} override specific path. + + @param code: An integer HTTP status code which will be used for the + response. + + @param brief: A short string which will be included in the response + body as the page title. + + @param detail: A longer string which will be included in the + response body. + + @returns: An L{IResource} + """ + return _ErrorPage(code, brief, detail) + + def notFound( brief: str = "No Such Resource", message: str = "Sorry. No luck finding that resource.", -) -> ErrorPage: +) -> IResource: """ - Generate an L{ErrorPage} with a 404 Not Found status code. + Generate an L{IResource} with a 404 Not Found status code. + + @see: L{twisted.web.pages.errorPage} @param brief: A short string displayed as the page title. @param brief: A longer string displayed in the page body. - @returns: An L{ErrorPage} + @returns: An L{IResource} """ - return ErrorPage(http.NOT_FOUND, brief, message) + return _ErrorPage(http.NOT_FOUND, brief, message) def forbidden( brief: str = "Forbidden Resource", message: str = "Sorry, resource is forbidden." -) -> ErrorPage: +) -> IResource: """ - Generate an L{ErrorPage} with a 403 Forbidden status code. + Generate an L{IResource} with a 403 Forbidden status code. + + @see: L{twisted.web.pages.errorPage} @param brief: A short string displayed as the page title. @param brief: A longer string displayed in the page body. - @returns: An L{ErrorPage} + @returns: An L{IResource} """ - return ErrorPage(http.FORBIDDEN, brief, message) + return _ErrorPage(http.FORBIDDEN, brief, message) diff --git a/src/twisted/web/resource.py b/src/twisted/web/resource.py index de3b557048a..670940f2086 100644 --- a/src/twisted/web/resource.py +++ b/src/twisted/web/resource.py @@ -299,7 +299,7 @@ class _UnsafeErrorPage(Resource): descriptive text. This is useful for rendering simple error pages. Deprecated in Twisted NEXT because it permits HTML injection; use - L{twisted.web.pages.ErrorPage} instead. + L{twisted.web.pages.errorPage} instead. @ivar template: A native string which will have a dictionary interpolated into it to generate the response body. The dictionary has the following @@ -387,7 +387,7 @@ def __init__(self, message="Sorry, resource is forbidden."): deprecatedModuleAttribute( Version("Twisted", "NEXT", 0, 0), - "Use twisted.web.pages.ErrorPage instead, which properly escapes HTML.", + "Use twisted.web.pages.errorPage instead, which properly escapes HTML.", __name__, "ErrorPage", ) diff --git a/src/twisted/web/test/test_pages.py b/src/twisted/web/test/test_pages.py index 1f66c16afc9..acd9b978fe0 100644 --- a/src/twisted/web/test/test_pages.py +++ b/src/twisted/web/test/test_pages.py @@ -10,11 +10,12 @@ from twisted.trial.unittest import SynchronousTestCase from twisted.web.http_headers import Headers from twisted.web.iweb import IRequest -from twisted.web.pages import ErrorPage, forbidden, notFound +from twisted.web.pages import errorPage, forbidden, notFound +from twisted.web.resource import IResource from twisted.web.test.requesthelper import DummyRequest -def _render(resource: ErrorPage) -> DummyRequest: +def _render(resource: IResource) -> DummyRequest: """ Render a response using the given resource. @@ -32,7 +33,7 @@ def _render(resource: ErrorPage) -> DummyRequest: class ErrorPageTests(SynchronousTestCase): """ - Test L{twisted.web.pages.ErrorPage} and its convencience helpers + Test L{twisted.web.pages._ErrorPage} and its public aliases L{errorPage}, L{notFound} and L{forbidden}. """ @@ -56,7 +57,7 @@ def test_escapesHTML(self): The I{brief} and I{detail} parameters are HTML-escaped on render. """ self.assertResponse( - _render(ErrorPage(400, "A & B", "<script>alert('oops!')")), + _render(errorPage(400, "A & B", "<script>alert('oops!')")), 400, ( b"<!DOCTYPE html>\n" @@ -68,10 +69,10 @@ def test_escapesHTML(self): def test_getChild(self): """ - The C{getChild} method of L{ErrorPage} returns the L{ErrorPage} it is - called on. + The C{getChild} method of the resource returned by L{errorPage} returns + the L{_ErrorPage} it is called on. """ - page = ErrorPage(404, "foo", "bar") + page = errorPage(404, "foo", "bar") self.assertIs( page.getChild(b"name", DummyRequest([b""])), page, diff --git a/src/twisted/web/test/test_resource.py b/src/twisted/web/test/test_resource.py index cb37942dbb8..72e9137c1c8 100644 --- a/src/twisted/web/test/test_resource.py +++ b/src/twisted/web/test/test_resource.py @@ -42,7 +42,7 @@ def test_deprecatedErrorPage(self): [warning] = self.flushWarnings() self.assertEqual(warning["category"], DeprecationWarning) - self.assertIn("twisted.web.pages.ErrorPage", warning["message"]) + self.assertIn("twisted.web.pages.errorPage", warning["message"]) def test_deprecatedNoResource(self): """ From 78662333e70eb1b440e7d0025f4fc376709c19ac Mon Sep 17 00:00:00 2001 From: Tom Most <twm@freecog.net> Date: Mon, 24 Oct 2022 22:30:42 -0700 Subject: [PATCH 12/12] Add CVE to newsfragment --- src/twisted/web/newsfragments/11716.bugfix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/twisted/web/newsfragments/11716.bugfix b/src/twisted/web/newsfragments/11716.bugfix index 66189e685c5..5264c8fc202 100644 --- a/src/twisted/web/newsfragments/11716.bugfix +++ b/src/twisted/web/newsfragments/11716.bugfix @@ -1 +1 @@ -twisted.web.vhost.NameVirtualHost no longer echoes HTML received in the Host header without escaping it (GHSA-vg46-2rrj-3647). +twisted.web.vhost.NameVirtualHost no longer echoes HTML received in the Host header without escaping it (CVE-2022-39348, GHSA-vg46-2rrj-3647).
Locations
Projects
Search
Status Monitor
Help
OpenBuildService.org
Documentation
API Documentation
Code of Conduct
Contact
Support
@OBShq
Terms
openSUSE Build Service is sponsored by
The Open Build Service is an
openSUSE project
.
Sign Up
Log In
Places
Places
All Projects
Status Monitor