[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

Bug#1010615: buster-pu: package twisted/18.9.0-3+deb10u1



Package: release.debian.org
Severity: normal
Tags: buster
User: release.debian.org@packages.debian.org
Usertags: pu
X-Debbugs-Cc: doko@debian.org

[ Reason ]
Catching up on outstanding security issues.
Security team deemed them no-dsa.

[ Impact ]
Outstanding security issues remain unresolved.

[ Tests ]
Twisted has a comprehensive test-suite, the relevant updates come with
tests, and no regressions were noticed.

[ Risks ]
The same patches are carried in Ubuntu, and in Debian LTS + ELTS.
They did need some backporting to older releases, but nothing too risky.

[ Checklist ]
  [x] *all* changes are documented in the d/changelog
  [x] I reviewed all changes and I approve them
  [x] attach debdiff against the package in (old)stable
  [x] the issue is verified as fixed in unstable

[ Changes ]
* SECURITY UPDATE: incorrect URI and HTTP method validation
  - debian/patches/CVE-2019-12387.patch: prevent CRLF injections in
    src/twisted/web/_newclient.py, src/twisted/web/client.py,
    src/twisted/web/test/injectionhelpers.py,
    src/twisted/web/test/test_agent.py,
    src/twisted/web/test/test_webclient.py.
  - CVE-2019-12387
  - Thanks Marc Deslauriers at Canonical for backporting the patches.
* SECURITY UPDATE: incorrect cert validation in XMPP support
  - debian/patches/CVE-2019-12855-*.patch: upstream patches to implement
    certificate checking.
  - CVE-2019-12855
  - Thanks Marc Deslauriers at Canonical for backporting the patches.
* SECURITY UPDATE: HTTP/2 denial of service issues
  - debian/patches/CVE-2019-951x.patch: buffer outbound control frames
    and timeout invalid clients in src/twisted/web/_http2.py,
    src/twisted/web/error.py, src/twisted/web/http.py,
    src/twisted/web/test/test_http.py,
    src/twisted/web/test/test_http2.py.
  - CVE-2019-9511
  - CVE-2019-9514
  - CVE-2019-9515
  - Thanks Marc Deslauriers at Canonical for backporting the patches.
* SECURITY UPDATE: request smuggling attacks
  - debian/patches/CVE-2020-1010x-pre1.patch: refactor to reduce
    duplication in src/twisted/web/test/test_http.py.
  - debian/patches/CVE-2020-1010x.patch: fix several request smuggling
    attacks in src/twisted/web/http.py,
    src/twisted/web/test/test_http.py.
  - CVE-2020-10108
  - CVE-2020-10109
  - Thanks Marc Deslauriers at Canonical for backporting the patches.
* SECURITY UPDATE: Information disclosure results in leaking of HTTP cookie
  and authorization headers when following cross origin redirects
  - debian/patches/CVE-2022-21712-*.patch: Ensure sensitive HTTP headers are
    removed when forming requests, in src/twisted/web/client.py,
    src/twisted/web/test/test_agent.py and src/twisted/web/iweb.py.
  - CVE-2022-21712
  - Thanks Ray Veldkamp at Canonical for backporting the patches.
* SECURITY UPDATE: Parsing of SSH version identifier field during an SSH
  handshake can result in a denial of service when excessively large packets
  are received
  - debian/patches/CVE-2022-21716-*.patch: Ensure that length of received
    handshake buffer is checked, prior to processing version string in
    src/twisted/conch/ssh/transport.py and
    src/twisted/conch/test/test_transport.py
  - CVE-2022-21716
  - Thanks Ray Veldkamp at Canonical for backporting the patches.
* CVE-2022-24801: Correct several defects in HTTP request parsing that could
  permit HTTP request smuggling: disallow signed Content-Length headers,
  forbid illegal characters in chunked extensions, forbid 0x prefix to chunk
  lengths, and only strip space and horizontal tab from header values.
  - debian/patches/CVE-2022-24801-*.patch
* Patch: remove spurious test for illegal whitespace in xmlns, to allow
  tests to pass, again.
  This was a regression introduced by the patch to expat for CVE-2022-25236.
  The resolution upstream was to just delete the test.

[ Other info ]
See bug 1010613 for bullseye.
diff -Nru twisted-18.9.0/debian/changelog twisted-18.9.0/debian/changelog
--- twisted-18.9.0/debian/changelog	2018-12-07 06:23:30.000000000 -0400
+++ twisted-18.9.0/debian/changelog	2022-05-05 10:01:06.000000000 -0400
@@ -1,3 +1,64 @@
+twisted (18.9.0-3+deb10u1) buster; urgency=medium
+
+  * Team upload.
+  * SECURITY UPDATE: incorrect URI and HTTP method validation
+    - debian/patches/CVE-2019-12387.patch: prevent CRLF injections in
+      src/twisted/web/_newclient.py, src/twisted/web/client.py,
+      src/twisted/web/test/injectionhelpers.py,
+      src/twisted/web/test/test_agent.py,
+      src/twisted/web/test/test_webclient.py.
+    - CVE-2019-12387
+    - Thanks Marc Deslauriers at Canonical for backporting the patches.
+  * SECURITY UPDATE: incorrect cert validation in XMPP support
+    - debian/patches/CVE-2019-12855-*.patch: upstream patches to implement
+      certificate checking.
+    - CVE-2019-12855
+    - Thanks Marc Deslauriers at Canonical for backporting the patches.
+  * SECURITY UPDATE: HTTP/2 denial of service issues
+    - debian/patches/CVE-2019-951x.patch: buffer outbound control frames
+      and timeout invalid clients in src/twisted/web/_http2.py,
+      src/twisted/web/error.py, src/twisted/web/http.py,
+      src/twisted/web/test/test_http.py,
+      src/twisted/web/test/test_http2.py.
+    - CVE-2019-9511
+    - CVE-2019-9514
+    - CVE-2019-9515
+    - Thanks Marc Deslauriers at Canonical for backporting the patches.
+  * SECURITY UPDATE: request smuggling attacks
+    - debian/patches/CVE-2020-1010x-pre1.patch: refactor to reduce
+      duplication in src/twisted/web/test/test_http.py.
+    - debian/patches/CVE-2020-1010x.patch: fix several request smuggling
+      attacks in src/twisted/web/http.py,
+      src/twisted/web/test/test_http.py.
+    - CVE-2020-10108
+    - CVE-2020-10109
+    - Thanks Marc Deslauriers at Canonical for backporting the patches.
+  * SECURITY UPDATE: Information disclosure results in leaking of HTTP cookie
+    and authorization headers when following cross origin redirects
+    - debian/patches/CVE-2022-21712-*.patch: Ensure sensitive HTTP headers are
+      removed when forming requests, in src/twisted/web/client.py,
+      src/twisted/web/test/test_agent.py and src/twisted/web/iweb.py.
+    - CVE-2022-21712
+    - Thanks Ray Veldkamp at Canonical for backporting the patches.
+  * SECURITY UPDATE: Parsing of SSH version identifier field during an SSH
+    handshake can result in a denial of service when excessively large packets
+    are received
+    - debian/patches/CVE-2022-21716-*.patch: Ensure that length of received
+      handshake buffer is checked, prior to processing version string in
+      src/twisted/conch/ssh/transport.py and
+      src/twisted/conch/test/test_transport.py
+    - CVE-2022-21716
+    - Thanks Ray Veldkamp at Canonical for backporting the patches.
+  * CVE-2022-24801: Correct several defects in HTTP request parsing that could
+    permit HTTP request smuggling: disallow signed Content-Length headers,
+    forbid illegal characters in chunked extensions, forbid 0x prefix to chunk
+    lengths, and only strip space and horizontal tab from header values.
+    - debian/patches/CVE-2022-24801-*.patch
+  * Patch: remove spurious test for illegal whitespace in xmlns, to allow
+    tests to pass, again.
+
+ -- Stefano Rivera <stefanor@debian.org>  Thu, 05 May 2022 10:01:06 -0400
+
 twisted (18.9.0-3) unstable; urgency=medium
 
   * Don't fix package substvars in binary-arch:, they did not depend on hamcrest
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12387.patch twisted-18.9.0/debian/patches/CVE-2019-12387.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12387.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12387.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,962 @@
+Backport of:
+
+From 6c61fc4503ae39ab8ecee52d10f10ee2c371d7e2 Mon Sep 17 00:00:00 2001
+From: Mark Williams <mrw@enotuniq.org>
+Date: Wed, 5 Jun 2019 00:03:37 -0700
+Subject: [PATCH] Prevent CRLF injections described in CVE-2019-12387
+
+Author: markrwilliams
+
+Reviewers: glyph
+
+Fixes: ticket:9647
+
+Twisted's HTTP client APIs were vulnerable to maliciously constructed
+HTTP methods, hosts, and/or paths, URI components such as paths and
+query parameters.  These vulnerabilities were beyond the header name
+and value injection vulnerabilities addressed in:
+
+https://twistedmatrix.com/trac/ticket/9420
+https://github.com/twisted/twisted/pull/999/
+
+The following client APIs will raise a ValueError if given a method,
+host, or URI that includes newlines or other disallowed characters:
+
+- twisted.web.client.Agent.request
+- twisted.web.client.ProxyAgent.request
+- twisted.web.client.Request.__init__
+- twisted.web.client.Request.writeTo
+
+ProxyAgent is patched separately from Agent because unlike other
+agents (e.g. CookieAgent) it is not implemented as an Agent wrapper.
+
+Request.__init__ checks its method and URI so that errors occur closer
+to their originating input.  Request.method and Request.uri are both
+public APIs, however, so Request.writeTo (via Request._writeHeaders)
+also checks the validity of both before writing anything to the wire.
+
+Additionally, the following deprecated client APIs have also been
+patched:
+
+- twisted.web.client.HTTPPageGetter.__init__
+- twisted.web.client.HTTPPageDownloader.__init__
+- twisted.web.client.HTTPClientFactory.__init__
+- twisted.web.client.HTTPClientFactory.setURL
+- twisted.web.client.HTTPDownloader.__init__
+- twisted.web.client.HTTPDownloader.setURL
+- twisted.web.client.getPage
+- twisted.web.client.downloadPage
+
+These have been patched prior to their removal so that they won't be
+vulnerable in the last Twisted release that includes them.  They
+represent a best effort, because testing every combination of these
+public APIs would require more code than deprecated APIs warrant.
+
+In all cases URI components, including hostnames, are restricted to
+the characters allowed in path components.  This mirrors the CPython
+patch (for bpo-30458) that addresses equivalent vulnerabilities:
+
+https://github.com/python/cpython/commit/bb8071a4cae5ab3fe321481dd3d73662ffb26052
+
+HTTP methods, however, are checked against the set of characters
+described in RFC-7230.
+---
+ src/twisted/web/_newclient.py             |  85 +++++-
+ src/twisted/web/client.py                 |  22 +-
+ src/twisted/web/newsfragments/9647.bugfix |   1 +
+ src/twisted/web/test/injectionhelpers.py  | 168 ++++++++++++
+ src/twisted/web/test/test_agent.py        | 147 +++++++++-
+ src/twisted/web/test/test_webclient.py    | 313 +++++++++++++++++++++-
+ 6 files changed, 725 insertions(+), 11 deletions(-)
+ create mode 100644 src/twisted/web/newsfragments/9647.bugfix
+ create mode 100644 src/twisted/web/test/injectionhelpers.py
+
+--- a/src/twisted/web/_newclient.py
++++ b/src/twisted/web/_newclient.py
+@@ -29,6 +29,8 @@ Various other classes in this module sup
+ from __future__ import division, absolute_import
+ __metaclass__ = type
+ 
++import re
++
+ from zope.interface import implementer
+ 
+ from twisted.python.compat import networkString
+@@ -579,6 +581,74 @@ class HTTPClientParser(HTTPParser):
+ 
+ 
+ 
++_VALID_METHOD = re.compile(
++    br"\A[%s]+\Z" % (
++        bytes().join(
++            (
++                b"!", b"#", b"$", b"%", b"&", b"'", b"*",
++                b"+", b"-", b".", b"^", b"_", b"`", b"|", b"~",
++                b"\x30-\x39",
++                b"\x41-\x5a",
++                b"\x61-\x7A",
++            ),
++        ),
++    ),
++)
++
++
++
++def _ensureValidMethod(method):
++    """
++    An HTTP method is an HTTP token, which consists of any visible
++    ASCII character that is not a delimiter (i.e. one of
++    C{"(),/:;<=>?@[\\]{}}.)
++
++    @param method: the method to check
++    @type method: L{bytes}
++
++    @return: the method if it is valid
++    @rtype: L{bytes}
++
++    @raise ValueError: if the method is not valid
++
++    @see: U{https://tools.ietf.org/html/rfc7230#section-3.1.1},
++        U{https://tools.ietf.org/html/rfc7230#section-3.2.6},
++        U{https://tools.ietf.org/html/rfc5234#appendix-B.1}
++    """
++    if _VALID_METHOD.match(method):
++        return method
++    raise ValueError("Invalid method {!r}".format(method))
++
++
++
++_VALID_URI = re.compile(br'\A[\x21-\x7e]+\Z')
++
++
++
++def _ensureValidURI(uri):
++    """
++    A valid URI cannot contain control characters (i.e., characters
++    between 0-32, inclusive and 127) or non-ASCII characters (i.e.,
++    characters with values between 128-255, inclusive).
++
++    @param uri: the URI to check
++    @type uri: L{bytes}
++
++    @return: the URI if it is valid
++    @rtype: L{bytes}
++
++    @raise ValueError: if the URI is not valid
++
++    @see: U{https://tools.ietf.org/html/rfc3986#section-3.3},
++        U{https://tools.ietf.org/html/rfc3986#appendix-A},
++        U{https://tools.ietf.org/html/rfc5234#appendix-B.1}
++    """
++    if _VALID_URI.match(uri):
++        return uri
++    raise ValueError("Invalid URI {!r}".format(uri))
++
++
++
+ @implementer(IClientRequest)
+ class Request:
+     """
+@@ -618,8 +688,8 @@ class Request:
+             connection, defaults to C{False}.
+         @type persistent: L{bool}
+         """
+-        self.method = method
+-        self.uri = uri
++        self.method = _ensureValidMethod(method)
++        self.uri = _ensureValidURI(uri)
+         self.headers = headers
+         self.bodyProducer = bodyProducer
+         self.persistent = persistent
+@@ -664,8 +734,15 @@ class Request:
+         # method would probably be good.  It would be nice if this method
+         # weren't limited to issuing HTTP/1.1 requests.
+         requestLines = []
+-        requestLines.append(b' '.join([self.method, self.uri,
+-            b'HTTP/1.1\r\n']))
++        requestLines.append(
++            b' '.join(
++                [
++                    _ensureValidMethod(self.method),
++                    _ensureValidURI(self.uri),
++                    b'HTTP/1.1\r\n',
++                ]
++            ),
++        )
+         if not self.persistent:
+             requestLines.append(b'Connection: close\r\n')
+         if TEorCL is not None:
+--- a/src/twisted/web/client.py
++++ b/src/twisted/web/client.py
+@@ -46,6 +46,9 @@ from twisted.web.iweb import UNKNOWN_LEN
+ from twisted.web.http_headers import Headers
+ from twisted.logger import Logger
+ 
++from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
++
++
+ 
+ class PartialDownloadError(error.Error):
+     """
+@@ -77,11 +80,13 @@ class HTTPPageGetter(http.HTTPClient):
+ 
+     _completelyDone = True
+ 
+-    _specialHeaders = set((b'host', b'user-agent', b'cookie', b'content-length'))
++    _specialHeaders = set(
++        (b'host', b'user-agent', b'cookie', b'content-length'),
++    )
+ 
+     def connectionMade(self):
+-        method = getattr(self.factory, 'method', b'GET')
+-        self.sendCommand(method, self.factory.path)
++        method = _ensureValidMethod(getattr(self.factory, 'method', b'GET'))
++        self.sendCommand(method, _ensureValidURI(self.factory.path))
+         if self.factory.scheme == b'http' and self.factory.port != 80:
+             host = self.factory.host + b':' + intToBytes(self.factory.port)
+         elif self.factory.scheme == b'https' and self.factory.port != 443:
+@@ -361,7 +366,7 @@ class HTTPClientFactory(protocol.ClientF
+             # just in case a broken http/1.1 decides to keep connection alive
+             self.headers.setdefault(b"connection", b"close")
+         self.postdata = postdata
+-        self.method = method
++        self.method = _ensureValidMethod(method)
+ 
+         self.setURL(url)
+ 
+@@ -388,6 +393,7 @@ class HTTPClientFactory(protocol.ClientF
+         return "<%s: %s>" % (self.__class__.__name__, self.url)
+ 
+     def setURL(self, url):
++        _ensureValidURI(url.strip())
+         self.url = url
+         uri = URI.fromBytes(url)
+         if uri.scheme and uri.host:
+@@ -732,7 +738,7 @@ def _makeGetterFactory(url, factoryFacto
+ 
+     @return: The factory created by C{factoryFactory}
+     """
+-    uri = URI.fromBytes(url)
++    uri = URI.fromBytes(_ensureValidURI(url.strip()))
+     factory = factoryFactory(url, *args, **kwargs)
+     if uri.scheme == b'https':
+         from twisted.internet import ssl
+@@ -1422,6 +1428,9 @@ class _AgentBase(object):
+         Issue a new request, given the endpoint and the path sent as part of
+         the request.
+         """
++
++        method = _ensureValidMethod(method)
++
+         # Create minimal headers, if necessary:
+         if headers is None:
+             headers = Headers()
+@@ -1646,6 +1655,7 @@ class Agent(_AgentBase):
+ 
+         @see: L{twisted.web.iweb.IAgent.request}
+         """
++        uri = _ensureValidURI(uri.strip())
+         parsedURI = URI.fromBytes(uri)
+         try:
+             endpoint = self._getEndpoint(parsedURI)
+@@ -1679,6 +1689,8 @@ class ProxyAgent(_AgentBase):
+         """
+         Issue a new request via the configured proxy.
+         """
++        uri = _ensureValidURI(uri.strip())
++
+         # Cache *all* connections under the same key, since we are only
+         # connecting to a single destination, the proxy:
+         key = ("http-proxy", self._proxyEndpoint)
+--- /dev/null
++++ b/src/twisted/web/test/injectionhelpers.py
+@@ -0,0 +1,168 @@
++"""
++Helpers for URI and method injection tests.
++
++@see: U{CVE-2019-12387}
++"""
++
++import string
++
++
++UNPRINTABLE_ASCII = (
++    frozenset(range(0, 128)) -
++    frozenset(bytearray(string.printable, 'ascii'))
++)
++
++NONASCII = frozenset(range(128, 256))
++
++
++
++class MethodInjectionTestsMixin(object):
++    """
++    A mixin that runs HTTP method injection tests.  Define
++    L{MethodInjectionTestsMixin.attemptRequestWithMaliciousMethod} in
++    a L{twisted.trial.unittest.SynchronousTestCase} subclass to test
++    how HTTP client code behaves when presented with malicious HTTP
++    methods.
++
++    @see: U{CVE-2019-12387}
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt to send a request with the given method.  This should
++        synchronously raise a L{ValueError} if either is invalid.
++
++        @param method: the method (e.g. C{GET\x00})
++
++        @param uri: the URI
++
++        @type method:
++        """
++        raise NotImplementedError()
++
++
++    def test_methodWithCLRFRejected(self):
++        """
++        Issuing a request with a method that contains a carriage
++        return and line feed fails with a L{ValueError}.
++        """
++        with self.assertRaises(ValueError) as cm:
++            method = b"GET\r\nX-Injected-Header: value"
++            self.attemptRequestWithMaliciousMethod(method)
++        self.assertRegex(str(cm.exception), "^Invalid method")
++
++
++    def test_methodWithUnprintableASCIIRejected(self):
++        """
++        Issuing a request with a method that contains unprintable
++        ASCII characters fails with a L{ValueError}.
++        """
++        for c in UNPRINTABLE_ASCII:
++            method = b"GET%s" % (bytearray([c]),)
++            with self.assertRaises(ValueError) as cm:
++                self.attemptRequestWithMaliciousMethod(method)
++            self.assertRegex(str(cm.exception), "^Invalid method")
++
++
++    def test_methodWithNonASCIIRejected(self):
++        """
++        Issuing a request with a method that contains non-ASCII
++        characters fails with a L{ValueError}.
++        """
++        for c in NONASCII:
++            method = b"GET%s" % (bytearray([c]),)
++            with self.assertRaises(ValueError) as cm:
++                self.attemptRequestWithMaliciousMethod(method)
++            self.assertRegex(str(cm.exception), "^Invalid method")
++
++
++
++class URIInjectionTestsMixin(object):
++    """
++    A mixin that runs HTTP URI injection tests.  Define
++    L{MethodInjectionTestsMixin.attemptRequestWithMaliciousURI} in a
++    L{twisted.trial.unittest.SynchronousTestCase} subclass to test how
++    HTTP client code behaves when presented with malicious HTTP
++    URIs.
++    """
++
++    def attemptRequestWithMaliciousURI(self, method):
++        """
++        Attempt to send a request with the given URI.  This should
++        synchronously raise a L{ValueError} if either is invalid.
++
++        @param uri: the URI.
++
++        @type method:
++        """
++        raise NotImplementedError()
++
++
++    def test_hostWithCRLFRejected(self):
++        """
++        Issuing a request with a URI whose host contains a carriage
++        return and line feed fails with a L{ValueError}.
++        """
++        with self.assertRaises(ValueError) as cm:
++            uri = b"http://twisted\r\n.invalid/path";
++            self.attemptRequestWithMaliciousURI(uri)
++        self.assertRegex(str(cm.exception), "^Invalid URI")
++
++
++    def test_hostWithWithUnprintableASCIIRejected(self):
++        """
++        Issuing a request with a URI whose host contains unprintable
++        ASCII characters fails with a L{ValueError}.
++        """
++        for c in UNPRINTABLE_ASCII:
++            uri = b"http://twisted%s.invalid/OK"; % (bytearray([c]),)
++            with self.assertRaises(ValueError) as cm:
++                self.attemptRequestWithMaliciousURI(uri)
++            self.assertRegex(str(cm.exception), "^Invalid URI")
++
++
++    def test_hostWithNonASCIIRejected(self):
++        """
++        Issuing a request with a URI whose host contains non-ASCII
++        characters fails with a L{ValueError}.
++        """
++        for c in NONASCII:
++            uri = b"http://twisted%s.invalid/OK"; % (bytearray([c]),)
++            with self.assertRaises(ValueError) as cm:
++                self.attemptRequestWithMaliciousURI(uri)
++            self.assertRegex(str(cm.exception), "^Invalid URI")
++
++
++    def test_pathWithCRLFRejected(self):
++        """
++        Issuing a request with a URI whose path contains a carriage
++        return and line feed fails with a L{ValueError}.
++        """
++        with self.assertRaises(ValueError) as cm:
++            uri = b"http://twisted.invalid/\r\npath";
++            self.attemptRequestWithMaliciousURI(uri)
++        self.assertRegex(str(cm.exception), "^Invalid URI")
++
++
++    def test_pathWithWithUnprintableASCIIRejected(self):
++        """
++        Issuing a request with a URI whose path contains unprintable
++        ASCII characters fails with a L{ValueError}.
++        """
++        for c in UNPRINTABLE_ASCII:
++            uri = b"http://twisted.invalid/OK%s"; % (bytearray([c]),)
++            with self.assertRaises(ValueError) as cm:
++                self.attemptRequestWithMaliciousURI(uri)
++            self.assertRegex(str(cm.exception), "^Invalid URI")
++
++
++    def test_pathWithNonASCIIRejected(self):
++        """
++        Issuing a request with a URI whose path contains non-ASCII
++        characters fails with a L{ValueError}.
++        """
++        for c in NONASCII:
++            uri = b"http://twisted.invalid/OK%s"; % (bytearray([c]),)
++            with self.assertRaises(ValueError) as cm:
++                self.attemptRequestWithMaliciousURI(uri)
++            self.assertRegex(str(cm.exception), "^Invalid URI")
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -11,7 +11,7 @@ from io import BytesIO
+ 
+ from zope.interface.verify import verifyObject
+ 
+-from twisted.trial.unittest import TestCase
++from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+ from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
+ from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
+@@ -50,6 +50,10 @@ from twisted.internet.endpoints import H
+ from twisted.test.proto_helpers import AccumulatingProtocol
+ from twisted.test.iosim import IOPump, FakeTransport
+ from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++from twisted.web.test.injectionhelpers import (
++    MethodInjectionTestsMixin,
++    URIInjectionTestsMixin,
++)
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
+ 
+@@ -886,6 +890,7 @@ class AgentTests(TestCase, FakeReactorAn
+     """
+     Tests for the new HTTP client API provided by L{Agent}.
+     """
++
+     def makeAgent(self):
+         """
+         @return: a new L{twisted.web.client.Agent} instance
+@@ -1307,6 +1312,48 @@ class AgentTests(TestCase, FakeReactorAn
+ 
+ 
+ 
++class AgentMethodInjectionTests(
++        FakeReactorAndConnectMixin,
++        MethodInjectionTestsMixin,
++        SynchronousTestCase,
++):
++    """
++    Test L{client.Agent} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: see L{MethodInjectionTestsMixin}
++        """
++        agent = client.Agent(self.createReactor())
++        uri = b"http://twisted.invalid";
++        agent.request(method, uri, client.Headers(), None)
++
++
++
++class AgentURIInjectionTests(
++        FakeReactorAndConnectMixin,
++        URIInjectionTestsMixin,
++        SynchronousTestCase,
++):
++    """
++    Test L{client.Agent} against URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided method.
++
++        @param uri: see L{URIInjectionTestsMixin}
++        """
++        agent = client.Agent(self.createReactor())
++        method = b"GET"
++        agent.request(method, uri, client.Headers(), None)
++
++
++
+ class AgentHTTPSTests(TestCase, FakeReactorAndConnectMixin,
+                       IntegrationTestingMixin):
+     """
+@@ -3105,3 +3152,101 @@ class ReadBodyTests(TestCase):
+ 
+         warnings = self.flushWarnings()
+         self.assertEqual(len(warnings), 0)
++
++
++
++class RequestMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        SynchronousTestCase,
++):
++    """
++    Test L{client.Request} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: see L{MethodInjectionTestsMixin}
++        """
++        client.Request(
++            method=method,
++            uri=b"http://twisted.invalid";,
++            headers=http_headers.Headers(),
++            bodyProducer=None,
++        )
++
++
++
++class RequestWriteToMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        SynchronousTestCase,
++):
++    """
++    Test L{client.Request.writeTo} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: see L{MethodInjectionTestsMixin}
++        """
++        headers = http_headers.Headers({b"Host": [b"twisted.invalid"]})
++        req = client.Request(
++            method=b"GET",
++            uri=b"http://twisted.invalid";,
++            headers=headers,
++            bodyProducer=None,
++        )
++        req.method = method
++        req.writeTo(StringTransport())
++
++
++
++class RequestURIInjectionTests(
++        URIInjectionTestsMixin,
++        SynchronousTestCase,
++):
++    """
++    Test L{client.Request} against HTTP URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param method: see L{URIInjectionTestsMixin}
++        """
++        client.Request(
++            method=b"GET",
++            uri=uri,
++            headers=http_headers.Headers(),
++            bodyProducer=None,
++        )
++
++
++
++class RequestWriteToURIInjectionTests(
++        URIInjectionTestsMixin,
++        SynchronousTestCase,
++):
++    """
++    Test L{client.Request.writeTo} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided method.
++
++        @param method: see L{URIInjectionTestsMixin}
++        """
++        headers = http_headers.Headers({b"Host": [b"twisted.invalid"]})
++        req = client.Request(
++            method=b"GET",
++            uri=b"http://twisted.invalid";,
++            headers=headers,
++            bodyProducer=None,
++        )
++        req.uri = uri
++        req.writeTo(StringTransport())
+--- a/src/twisted/web/test/test_webclient.py
++++ b/src/twisted/web/test/test_webclient.py
+@@ -7,6 +7,7 @@ Tests for the old L{twisted.web.client}
+ 
+ from __future__ import division, absolute_import
+ 
++import io
+ import os
+ from errno import ENOSPC
+ 
+@@ -20,7 +21,8 @@ from twisted.trial import unittest, util
+ from twisted.web import server, client, error, resource
+ from twisted.web.static import Data
+ from twisted.web.util import Redirect
+-from twisted.internet import reactor, defer, interfaces
++from twisted.internet import address, reactor, defer, interfaces
++from twisted.internet.protocol import ClientFactory
+ from twisted.python.filepath import FilePath
+ from twisted.protocols.policies import WrappingFactory
+ from twisted.test.proto_helpers import (
+@@ -35,6 +37,12 @@ from twisted import test
+ from twisted.logger import (globalLogPublisher, FilteringLogObserver,
+                             LogLevelFilterPredicate, LogLevel, Logger)
+ 
++from twisted.web.test.injectionhelpers import (
++    MethodInjectionTestsMixin,
++    URIInjectionTestsMixin,
++)
++
++
+ 
+ serverPEM = FilePath(test.__file__).sibling('server.pem')
+ serverPEMPath = serverPEM.asBytesMode().path
+@@ -1519,3 +1527,306 @@ class DeprecationTests(unittest.TestCase
+         L{client.HTTPDownloader} is deprecated.
+         """
+         self._testDeprecatedClass("HTTPDownloader")
++
++
++
++class GetPageMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Test L{client.getPage} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: see L{MethodInjectionTestsMixin}
++        """
++        uri = b'http://twisted.invalid'
++        client.getPage(uri, method=method)
++
++
++
++class GetPageURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Test L{client.getPage} against URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: see L{URIInjectionTestsMixin}
++        """
++        client.getPage(uri)
++
++
++
++class DownloadPageMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Test L{client.getPage} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: see L{MethodInjectionTestsMixin}
++        """
++        uri = b'http://twisted.invalid'
++        client.downloadPage(uri, file=io.BytesIO(), method=method)
++
++
++
++class DownloadPageURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Test L{client.downloadPage} against URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: see L{URIInjectionTestsMixin}
++        """
++        client.downloadPage(uri, file=io.BytesIO())
++
++
++
++def makeHTTPPageGetterFactory(protocolClass, method, host, path):
++    """
++    Make a L{ClientFactory} that can be used with
++    L{client.HTTPPageGetter} and its subclasses.
++
++    @param protocolClass: The protocol class
++    @type protocolClass: A subclass of L{client.HTTPPageGetter}
++
++    @param method: the HTTP method
++
++    @param host: the host
++
++    @param path: The URI path
++
++    @return: A L{ClientFactory}.
++    """
++    factory = ClientFactory.forProtocol(protocolClass)
++
++    factory.method = method
++    factory.host = host
++    factory.path = path
++
++    factory.scheme = b"http"
++    factory.port = 0
++    factory.headers = {}
++    factory.agent = b"User/Agent"
++    factory.cookies = {}
++
++    return factory
++
++
++
++class HTTPPageGetterMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Test L{client.HTTPPageGetter} against HTTP method injections.
++    """
++    protocolClass = client.HTTPPageGetter
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: L{MethodInjectionTestsMixin}
++        """
++        transport = StringTransport()
++        factory = makeHTTPPageGetterFactory(
++            self.protocolClass,
++            method=method,
++            host=b"twisted.invalid",
++            path=b"/",
++        )
++        getter = factory.buildProtocol(
++            address.IPv4Address("TCP", "127.0.0.1", 0),
++        )
++        getter.makeConnection(transport)
++
++
++
++class HTTPPageGetterURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Test L{client.HTTPPageGetter} against HTTP URI injections.
++    """
++    protocolClass = client.HTTPPageGetter
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: L{URIInjectionTestsMixin}
++        """
++        transport = StringTransport()
++        # Setting the host and path to the same value is imprecise but
++        # doesn't require parsing an invalid URI.
++        factory = makeHTTPPageGetterFactory(
++            self.protocolClass,
++            method=b"GET",
++            host=uri,
++            path=uri,
++        )
++        getter = factory.buildProtocol(
++            address.IPv4Address("TCP", "127.0.0.1", 0),
++        )
++        getter.makeConnection(transport)
++
++
++
++class HTTPPageDownloaderMethodInjectionTests(
++        HTTPPageGetterMethodInjectionTests
++):
++
++    """
++    Test L{client.HTTPPageDownloader} against HTTP method injections.
++    """
++    protocolClass = client.HTTPPageDownloader
++
++
++
++class HTTPPageDownloaderURIInjectionTests(
++        HTTPPageGetterURIInjectionTests
++):
++    """
++    Test L{client.HTTPPageDownloader} against HTTP URI injections.
++    """
++    protocolClass = client.HTTPPageDownloader
++
++
++
++class HTTPClientFactoryMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Tests L{client.HTTPClientFactory} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: L{MethodInjectionTestsMixin}
++        """
++        client.HTTPClientFactory(b"https://twisted.invalid";, method)
++
++
++
++class HTTPClientFactoryURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Tests L{client.HTTPClientFactory} against HTTP URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: L{URIInjectionTestsMixin}
++        """
++        client.HTTPClientFactory(uri)
++
++
++
++class HTTPClientFactorySetURLURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Tests L{client.HTTPClientFactory.setURL} against HTTP URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: L{URIInjectionTestsMixin}
++        """
++        client.HTTPClientFactory(b"https://twisted.invalid";).setURL(uri)
++
++
++
++class HTTPDownloaderMethodInjectionTests(
++        MethodInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Tests L{client.HTTPDownloader} against HTTP method injections.
++    """
++
++    def attemptRequestWithMaliciousMethod(self, method):
++        """
++        Attempt a request with the provided method.
++
++        @param method: L{MethodInjectionTestsMixin}
++        """
++        client.HTTPDownloader(
++            b"https://twisted.invalid";,
++            io.BytesIO(),
++            method=method,
++        )
++
++
++
++class HTTPDownloaderURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Tests L{client.HTTPDownloader} against HTTP URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: L{URIInjectionTestsMixin}
++        """
++        client.HTTPDownloader(uri, io.BytesIO())
++
++
++
++class HTTPDownloaderSetURLURIInjectionTests(
++        URIInjectionTestsMixin,
++        unittest.SynchronousTestCase,
++):
++    """
++    Tests L{client.HTTPDownloader.setURL} against HTTP URI injections.
++    """
++
++    def attemptRequestWithMaliciousURI(self, uri):
++        """
++        Attempt a request with the provided URI.
++
++        @param uri: L{URIInjectionTestsMixin}
++        """
++        downloader = client.HTTPDownloader(
++            b"https://twisted.invalid";,
++            io.BytesIO(),
++        )
++        downloader.setURL(uri)
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-01.patch twisted-18.9.0/debian/patches/CVE-2019-12855-01.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-01.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-01.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,176 @@
+From 488bdd0b80cd1084359e34b8d36ae536520b1f86 Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Tue, 7 May 2019 12:26:14 -0400
+Subject: [PATCH 01/17] Use optionsForClientTLS to verify server certificate by
+ default
+
+---
+ .../words/protocols/jabber/xmlstream.py       |  2 +-
+ .../words/test/test_jabberxmlstream.py        | 61 +++++++++++++------
+ 2 files changed, 44 insertions(+), 19 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index c191e9ae219..70d9267b705 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -414,7 +414,7 @@ def onProceed(self, obj):
+         """
+ 
+         self.xmlstream.removeObserver('/failure', self.onFailure)
+-        ctx = ssl.CertificateOptions()
++        ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
+         self.xmlstream.transport.startTLS(ctx)
+         self.xmlstream.reset()
+         self.xmlstream.sendHeader()
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index 302171d7297..ccccf87372c 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -14,6 +14,7 @@
+ from twisted.internet import defer, task
+ from twisted.internet.error import ConnectionLost
+ from twisted.internet.interfaces import IProtocolFactory
++from twisted.internet._sslverify import ClientTLSOptions
+ from twisted.python import failure
+ from twisted.python.compat import unicode
+ from twisted.test import proto_helpers
+@@ -665,7 +666,7 @@ def setUp(self):
+ 
+         self.savedSSL = xmlstream.ssl
+ 
+-        self.authenticator = xmlstream.Authenticator()
++        self.authenticator = xmlstream.ConnectAuthenticator(u'example.com')
+         self.xmlstream = xmlstream.XmlStream(self.authenticator)
+         self.xmlstream.send = self.output.append
+         self.xmlstream.connectionMade()
+@@ -679,9 +680,9 @@ def tearDown(self):
+         xmlstream.ssl = self.savedSSL
+ 
+ 
+-    def testWantedSupported(self):
++    def test_wantedSupported(self):
+         """
+-        Test start when TLS is wanted and the SSL library available.
++        When TLS is wanted and SSL available, StartTLS is initiated.
+         """
+         self.xmlstream.transport = proto_helpers.StringTransport()
+         self.xmlstream.transport.startTLS = lambda ctx: self.done.append('TLS')
+@@ -690,7 +691,8 @@ def testWantedSupported(self):
+ 
+         d = self.init.start()
+         d.addCallback(self.assertEqual, xmlstream.Reset)
+-        starttls = self.output[0]
++        self.assertEqual(2, len(self.output))
++        starttls = self.output[1]
+         self.assertEqual('starttls', starttls.name)
+         self.assertEqual(NS_XMPP_TLS, starttls.uri)
+         self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
+@@ -698,40 +700,63 @@ def testWantedSupported(self):
+ 
+         return d
+ 
++
++    def test_certificateVerify(self):
++        """
++        The server certificate will be verified.
++        """
++
++        def fakeStartTLS(contextFactory):
++            self.assertIsInstance(contextFactory, ClientTLSOptions)
++            self.assertEqual(contextFactory._hostname, u"example.com")
++            self.done.append('TLS')
++
++        self.xmlstream.transport = proto_helpers.StringTransport()
++        self.xmlstream.transport.startTLS = fakeStartTLS
++        self.xmlstream.reset = lambda: self.done.append('reset')
++        self.xmlstream.sendHeader = lambda: self.done.append('header')
++
++        d = self.init.start()
++        self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
++        self.assertEqual(['TLS', 'reset', 'header'], self.done)
++        return d
++
++
+     if not xmlstream.ssl:
+         testWantedSupported.skip = "SSL not available"
++        test_certificateVerify = "SSL not available"
+ 
+ 
+-    def testWantedNotSupportedNotRequired(self):
++    def test_wantedNotSupportedNotRequired(self):
+         """
+-        Test start when TLS is wanted and the SSL library available.
++        No StartTLS is initiated when wanted, not required, SSL not available.
+         """
+         xmlstream.ssl = None
+ 
+         d = self.init.start()
+         d.addCallback(self.assertEqual, None)
+-        self.assertEqual([], self.output)
++        self.assertEqual(1, len(self.output))
+ 
+         return d
+ 
+ 
+-    def testWantedNotSupportedRequired(self):
++    def test_wantedNotSupportedRequired(self):
+         """
+-        Test start when TLS is wanted and the SSL library available.
++        TLSNotSupported is raised when TLS is required but not available.
+         """
+         xmlstream.ssl = None
+         self.init.required = True
+ 
+         d = self.init.start()
+         self.assertFailure(d, xmlstream.TLSNotSupported)
+-        self.assertEqual([], self.output)
++        self.assertEqual(1, len(self.output))
+ 
+         return d
+ 
+ 
+-    def testNotWantedRequired(self):
++    def test_notWantedRequired(self):
+         """
+-        Test start when TLS is not wanted, but required by the server.
++        TLSRequired is raised when TLS is not wanted, but required by server.
+         """
+         tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
+         tls.addElement('required')
+@@ -739,15 +764,15 @@ def testNotWantedRequired(self):
+         self.init.wanted = False
+ 
+         d = self.init.start()
+-        self.assertEqual([], self.output)
++        self.assertEqual(1, len(self.output))
+         self.assertFailure(d, xmlstream.TLSRequired)
+ 
+         return d
+ 
+ 
+-    def testNotWantedNotRequired(self):
++    def test_notWantedNotRequired(self):
+         """
+-        Test start when TLS is not wanted, but required by the server.
++        No StartTLS is initiated when not wanted and not required.
+         """
+         tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
+         self.xmlstream.features = {(tls.uri, tls.name): tls}
+@@ -755,13 +780,13 @@ def testNotWantedNotRequired(self):
+ 
+         d = self.init.start()
+         d.addCallback(self.assertEqual, None)
+-        self.assertEqual([], self.output)
++        self.assertEqual(1, len(self.output))
+         return d
+ 
+ 
+-    def testFailed(self):
++    def test_failed(self):
+         """
+-        Test failed TLS negotiation.
++        TLSFailed is raised when the server responds with a failure.
+         """
+         # Pretend that ssl is supported, it isn't actually used when the
+         # server starts out with a failure in response to our initial
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-02.patch twisted-18.9.0/debian/patches/CVE-2019-12855-02.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-02.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-02.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,25 @@
+From 0ff32b1bf115acc90d223b9ce9820063cf89003d Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Tue, 7 May 2019 15:54:33 -0400
+Subject: [PATCH 02/17] Fix client example to print disconnection reason
+
+---
+ docs/words/examples/xmpp_client.py | 3 ++-
+ 1 file changed, 2 insertions(+), 1 deletion(-)
+
+diff --git a/docs/words/examples/xmpp_client.py b/docs/words/examples/xmpp_client.py
+index cb80202c67f..4a3651009b4 100644
+--- a/docs/words/examples/xmpp_client.py
++++ b/docs/words/examples/xmpp_client.py
+@@ -53,8 +53,9 @@ def connected(self, xs):
+         xs.rawDataOutFn = self.rawDataOut
+ 
+ 
+-    def disconnected(self, xs):
++    def disconnected(self, reason):
+         print('Disconnected.')
++        print(reason)
+ 
+         self.finished.callback(None)
+ 
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-03.patch twisted-18.9.0/debian/patches/CVE-2019-12855-03.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-03.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-03.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,80 @@
+From 89954dfb18e613be583c74e22a3dd55d66e7d975 Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Tue, 7 May 2019 18:23:49 -0400
+Subject: [PATCH 03/17] Allow for custom contextFactory to TLS initializer
+
+---
+ .../words/protocols/jabber/xmlstream.py       |  6 ++++-
+ .../words/test/test_jabberxmlstream.py        | 24 +++++++++++++++++++
+ 2 files changed, 29 insertions(+), 1 deletion(-)
+
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index 70d9267b705..51a8466b16a 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -406,6 +406,7 @@ class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+ 
+     feature = (NS_XMPP_TLS, 'starttls')
+     wanted = True
++    contextFactory = None
+     _deferred = None
+ 
+     def onProceed(self, obj):
+@@ -414,7 +415,10 @@ def onProceed(self, obj):
+         """
+ 
+         self.xmlstream.removeObserver('/failure', self.onFailure)
+-        ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
++        if self.contextFactory:
++            ctx = self.contextFactory
++        else:
++            ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
+         self.xmlstream.transport.startTLS(ctx)
+         self.xmlstream.reset()
+         self.xmlstream.sendHeader()
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index ccccf87372c..863cad0f328 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -14,6 +14,7 @@
+ from twisted.internet import defer, task
+ from twisted.internet.error import ConnectionLost
+ from twisted.internet.interfaces import IProtocolFactory
++from twisted.internet.ssl import CertificateOptions
+ from twisted.internet._sslverify import ClientTLSOptions
+ from twisted.python import failure
+ from twisted.python.compat import unicode
+@@ -722,9 +723,32 @@ def fakeStartTLS(contextFactory):
+         return d
+ 
+ 
++    def test_certificateVerifyContext(self):
++        """
++        A custom contextFactory is passed through to startTLS.
++        """
++        ctx = CertificateOptions()
++        self.init.contextFactory = ctx
++
++        def fakeStartTLS(contextFactory):
++            self.assertIs(ctx, contextFactory)
++            self.done.append('TLS')
++
++        self.xmlstream.transport = proto_helpers.StringTransport()
++        self.xmlstream.transport.startTLS = fakeStartTLS
++        self.xmlstream.reset = lambda: self.done.append('reset')
++        self.xmlstream.sendHeader = lambda: self.done.append('header')
++
++        d = self.init.start()
++        self.xmlstream.dataReceived("<proceed xmlns='%s'/>" % NS_XMPP_TLS)
++        self.assertEqual(['TLS', 'reset', 'header'], self.done)
++        return d
++
++
+     if not xmlstream.ssl:
+         testWantedSupported.skip = "SSL not available"
+         test_certificateVerify = "SSL not available"
++        test_certificateVerifyContext = "SSL not available"
+ 
+ 
+     def test_wantedNotSupportedNotRequired(self):
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-04.patch twisted-18.9.0/debian/patches/CVE-2019-12855-04.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-04.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-04.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,26 @@
+From 4759e27af0ffa2e61538d5e0a66c3e57e20d3f5b Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Wed, 8 May 2019 13:19:17 -0400
+Subject: [PATCH 04/17] Add docstrings for new contextFactory attribute
+
+---
+ src/twisted/words/protocols/jabber/xmlstream.py | 5 +++++
+ 1 file changed, 5 insertions(+)
+
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index 51a8466b16a..88ad21f76a6 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -402,6 +402,11 @@ class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+ 
+     @cvar wanted: indicates if TLS negotiation is wanted.
+     @type wanted: C{bool}
++
++    @cvar contextFactory: An object which creates appropriately configured TLS
++        connections. This is passed to C{startTLS} on the transport and is
++        preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
++    @type contextFactory: L{IOpenSSLClientConnectionCreator}
+     """
+ 
+     feature = (NS_XMPP_TLS, 'starttls')
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-05.patch twisted-18.9.0/debian/patches/CVE-2019-12855-05.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-05.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-05.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,180 @@
+From fa18e8e65cf486ea9adc8e9a9a6df7e168098ce8 Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Thu, 9 May 2019 11:11:14 -0400
+Subject: [PATCH 05/17] Clean up connecting authenticators
+
+This adds an option `required` argument to the inits of initializers
+deriving from BaseFeatureInitiatingInitializer, to simplify setup.
+Additionally it changes the requiredness of two initializers used by
+XMPPAuthenticator:
+
+* Setup of TLS is now required by default. This ensures that if StartTLS
+is not advertized by the server, initialization fails instead of
+silently proceeding to authentication without encryption.
+* Binding a resource is required by default, because without it servers
+will not allow any further meaningful interaction.
+---
+ src/twisted/words/protocols/jabber/client.py  | 28 +++++--------
+ .../words/protocols/jabber/xmlstream.py       |  9 +++--
+ src/twisted/words/test/test_jabberclient.py   | 39 ++++++++++++++++++-
+ .../words/test/test_jabberxmlstream.py        |  9 +++++
+ 4 files changed, 61 insertions(+), 24 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/client.py b/src/twisted/words/protocols/jabber/client.py
+index ffe6c939d8a..566bc9ff177 100644
+--- a/src/twisted/words/protocols/jabber/client.py
++++ b/src/twisted/words/protocols/jabber/client.py
+@@ -206,14 +206,10 @@ def associateWithStream(self, xs):
+         xs.version = (0, 0)
+         xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+ 
+-        inits = [ (xmlstream.TLSInitiatingInitializer, False),
+-                  (IQAuthInitializer, True),
+-                ]
+-
+-        for initClass, required in inits:
+-            init = initClass(xs)
+-            init.required = required
+-            xs.initializers.append(init)
++        xs.initializers = [
++            xmlstream.TLSInitiatingInitializer(xs, required=False),
++            IQAuthInitializer(xs),
++        ]
+ 
+     # TODO: move registration into an Initializer?
+ 
+@@ -377,14 +373,10 @@ def associateWithStream(self, xs):
+         """
+         xmlstream.ConnectAuthenticator.associateWithStream(self, xs)
+ 
+-        xs.initializers = [CheckVersionInitializer(xs)]
+-        inits = [ (xmlstream.TLSInitiatingInitializer, False),
+-                  (sasl.SASLInitiatingInitializer, True),
+-                  (BindInitializer, False),
+-                  (SessionInitializer, False),
++        xs.initializers = [
++                CheckVersionInitializer(xs),
++                xmlstream.TLSInitiatingInitializer(xs, required=True),
++                sasl.SASLInitiatingInitializer(xs, required=True),
++                BindInitializer(xs, required=True),
++                SessionInitializer(xs, required=False),
+                 ]
+-
+-        for initClass, required in inits:
+-            init = initClass(xs)
+-            init.required = required
+-            xs.initializers.append(init)
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index 88ad21f76a6..f7512016c5a 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -316,16 +316,17 @@ class BaseFeatureInitiatingInitializer(object):
+ 
+     @cvar feature: tuple of (uri, name) of the stream feature root element.
+     @type feature: tuple of (C{str}, C{str})
++
+     @ivar required: whether the stream feature is required to be advertized
+                     by the receiving entity.
+     @type required: C{bool}
+     """
+ 
+     feature = None
+-    required = False
+ 
+-    def __init__(self, xs):
++    def __init__(self, xs, required=False):
+         self.xmlstream = xs
++        self.required = required
+ 
+ 
+     def initialize(self):
+@@ -400,10 +401,10 @@ class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+     set the C{wanted} attribute to False instead of removing it from the list
+     of initializers, so a proper exception L{TLSRequired} can be raised.
+ 
+-    @cvar wanted: indicates if TLS negotiation is wanted.
++    @ivar wanted: indicates if TLS negotiation is wanted.
+     @type wanted: C{bool}
+ 
+-    @cvar contextFactory: An object which creates appropriately configured TLS
++    @ivar contextFactory: An object which creates appropriately configured TLS
+         connections. This is passed to C{startTLS} on the transport and is
+         preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
+     @type contextFactory: L{IOpenSSLClientConnectionCreator}
+diff --git a/src/twisted/words/test/test_jabberclient.py b/src/twisted/words/test/test_jabberclient.py
+index d54f88651ad..19be60b34eb 100644
+--- a/src/twisted/words/test/test_jabberclient.py
++++ b/src/twisted/words/test/test_jabberclient.py
+@@ -379,6 +379,41 @@ def onSession(iq):
+ 
+ 
+ 
++class BasicAuthenticatorTests(unittest.TestCase):
++    """
++    Test for both XMPPAuthenticator and XMPPClientFactory.
++    """
++    def testBasic(self):
++        """
++        Test basic operations.
++
++        Setup a basicClientFactory, which sets up a BasicAuthenticator, and let
++        it produce a protocol instance. Then inspect the instance variables of
++        the authenticator and XML stream objects.
++        """
++        self.client_jid = jid.JID('user@example.com/resource')
++
++        # Get an XmlStream instance. Note that it gets initialized with the
++        # XMPPAuthenticator (that has its associateWithXmlStream called) that
++        # is in turn initialized with the arguments to the factory.
++        xs = client.basicClientFactory(self.client_jid,
++                                      'secret').buildProtocol(None)
++
++        # test authenticator's instance variables
++        self.assertEqual('example.com', xs.authenticator.otherHost)
++        self.assertEqual(self.client_jid, xs.authenticator.jid)
++        self.assertEqual('secret', xs.authenticator.password)
++
++        # test list of initializers
++        tls, auth = xs.initializers
++
++        self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
++        self.assertIsInstance(auth, client.IQAuthInitializer)
++
++        self.assertFalse(tls.required)
++
++
++
+ class XMPPAuthenticatorTests(unittest.TestCase):
+     """
+     Test for both XMPPAuthenticator and XMPPClientFactory.
+@@ -412,7 +447,7 @@ def testBasic(self):
+         self.assertIsInstance(bind, client.BindInitializer)
+         self.assertIsInstance(session, client.SessionInitializer)
+ 
+-        self.assertFalse(tls.required)
++        self.assertTrue(tls.required)
+         self.assertTrue(sasl.required)
+-        self.assertFalse(bind.required)
++        self.assertTrue(bind.required)
+         self.assertFalse(session.required)
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index 863cad0f328..6df336deb20 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -681,6 +681,15 @@ def tearDown(self):
+         xmlstream.ssl = self.savedSSL
+ 
+ 
++    def test_initRequired(self):
++        """
++        Passing required sets the instance variable.
++        """
++        self.init = xmlstream.TLSInitiatingInitializer(self.xmlstream,
++                                                       required=True)
++        self.assertTrue(self.init.required)
++
++
+     def test_wantedSupported(self):
+         """
+         When TLS is wanted and SSL available, StartTLS is initiated.
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-06.patch twisted-18.9.0/debian/patches/CVE-2019-12855-06.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-06.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-06.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,179 @@
+From cadf08f3481b689929ad471a17ce29683dc0635d Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Thu, 9 May 2019 12:05:21 -0400
+Subject: [PATCH 06/17] Provide a way to use custom certificate options for
+ XMPP clients
+
+This adds an optional `contextFactory` argument to `XMPPClientFactory`
+that is passed on to `XMPPAuthenticator`, which in turn passes it to
+`TLSInitiatingInitializer`.
+---
+ src/twisted/words/protocols/jabber/client.py  | 25 ++++++++++---
+ .../words/protocols/jabber/xmlstream.py       |  9 +++++
+ src/twisted/words/test/test_jabberclient.py   | 35 ++++++++++++++++---
+ 3 files changed, 61 insertions(+), 8 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/client.py b/src/twisted/words/protocols/jabber/client.py
+index 566bc9ff177..4b310e34f38 100644
+--- a/src/twisted/words/protocols/jabber/client.py
++++ b/src/twisted/words/protocols/jabber/client.py
+@@ -298,7 +298,7 @@ def start(self):
+ 
+ 
+ 
+-def XMPPClientFactory(jid, password):
++def XMPPClientFactory(jid, password, contextFactory=None):
+     """
+     Client factory for XMPP 1.0 (only).
+ 
+@@ -310,12 +310,20 @@ def XMPPClientFactory(jid, password):
+ 
+     @param jid: Jabber ID to connect with.
+     @type jid: L{jid.JID}
++
+     @param password: password to authenticate with.
+     @type password: L{unicode}
++
++    @param contextFactory: An object which creates appropriately configured TLS
++        connections. This is passed to C{startTLS} on the transport and is
++        preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
++        See L{xmlstream.TLSInitiatingInitializer} for details.
++    @type contextFactory: L{IOpenSSLClientConnectionCreator}
++
+     @return: XML stream factory.
+     @rtype: L{xmlstream.XmlStreamFactory}
+     """
+-    a = XMPPAuthenticator(jid, password)
++    a = XMPPAuthenticator(jid, password, contextFactory=contextFactory)
+     return xmlstream.XmlStreamFactory(a)
+ 
+ 
+@@ -350,16 +358,24 @@ class XMPPAuthenticator(xmlstream.ConnectAuthenticator):
+                resource binding step, and this is stored in this instance
+                variable.
+     @type jid: L{jid.JID}
++
+     @ivar password: password to be used during SASL authentication.
+     @type password: L{unicode}
++
++    @ivar contextFactory: An object which creates appropriately configured TLS
++        connections. This is passed to C{startTLS} on the transport and is
++        preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
++        See L{xmlstream.TLSInitiatingInitializer} for details.
++    @type contextFactory: L{IOpenSSLClientConnectionCreator}
+     """
+ 
+     namespace = 'jabber:client'
+ 
+-    def __init__(self, jid, password):
++    def __init__(self, jid, password, contextFactory=None):
+         xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+         self.jid = jid
+         self.password = password
++        self.contextFactory = contextFactory
+ 
+ 
+     def associateWithStream(self, xs):
+@@ -375,7 +391,8 @@ def associateWithStream(self, xs):
+ 
+         xs.initializers = [
+                 CheckVersionInitializer(xs),
+-                xmlstream.TLSInitiatingInitializer(xs, required=True),
++                xmlstream.TLSInitiatingInitializer(
++                    xs, required=True, contextFactory=self.contextFactory),
+                 sasl.SASLInitiatingInitializer(xs, required=True),
+                 BindInitializer(xs, required=True),
+                 SessionInitializer(xs, required=False),
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index f7512016c5a..1ed79d47726 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -407,6 +407,9 @@ class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+     @ivar contextFactory: An object which creates appropriately configured TLS
+         connections. This is passed to C{startTLS} on the transport and is
+         preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
++        If C{None}, the default is to verify the server certificate against
++        the trust roots as provided by the platform. See
++        L{twisted.internet._sslverify.platformTrust}.
+     @type contextFactory: L{IOpenSSLClientConnectionCreator}
+     """
+ 
+@@ -415,6 +418,12 @@ class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+     contextFactory = None
+     _deferred = None
+ 
++    def __init__(self, xs, required=True, contextFactory=None):
++        super(TLSInitiatingInitializer, self).__init__(
++                xs, required=required)
++        self.contextFactory = contextFactory
++
++
+     def onProceed(self, obj):
+         """
+         Proceed with TLS negotiation and reset the XML stream.
+diff --git a/src/twisted/words/test/test_jabberclient.py b/src/twisted/words/test/test_jabberclient.py
+index 19be60b34eb..2e39de72cee 100644
+--- a/src/twisted/words/test/test_jabberclient.py
++++ b/src/twisted/words/test/test_jabberclient.py
+@@ -9,7 +9,7 @@
+ 
+ from hashlib import sha1
+ 
+-from twisted.internet import defer
++from twisted.internet import defer, ssl
+ from twisted.python.compat import unicode
+ from twisted.trial import unittest
+ from twisted.words.protocols.jabber import client, error, jid, xmlstream
+@@ -381,9 +381,10 @@ def onSession(iq):
+ 
+ class BasicAuthenticatorTests(unittest.TestCase):
+     """
+-    Test for both XMPPAuthenticator and XMPPClientFactory.
++    Test for both BasicAuthenticator and basicClientFactory.
+     """
+-    def testBasic(self):
++
++    def test_basic(self):
+         """
+         Test basic operations.
+ 
+@@ -418,7 +419,8 @@ class XMPPAuthenticatorTests(unittest.TestCase):
+     """
+     Test for both XMPPAuthenticator and XMPPClientFactory.
+     """
+-    def testBasic(self):
++
++    def test_basic(self):
+         """
+         Test basic operations.
+ 
+@@ -451,3 +453,28 @@ def testBasic(self):
+         self.assertTrue(sasl.required)
+         self.assertTrue(bind.required)
+         self.assertFalse(session.required)
++
++
++    def test_tlsContextFactory(self):
++        """
++        Test basic operations.
++
++        Setup an XMPPClientFactory, which sets up an XMPPAuthenticator, and let
++        it produce a protocol instance. Then inspect the instance variables of
++        the authenticator and XML stream objects.
++        """
++        self.client_jid = jid.JID('user@example.com/resource')
++
++        # Get an XmlStream instance. Note that it gets initialized with the
++        # XMPPAuthenticator (that has its associateWithXmlStream called) that
++        # is in turn initialized with the arguments to the factory.
++        contextFactory = ssl.CertificateOptions()
++        factory = client.XMPPClientFactory(self.client_jid, 'secret',
++                                           contextFactory=contextFactory)
++        xs = factory.buildProtocol(None)
++
++        # test list of initializers
++        version, tls, sasl, bind, session = xs.initializers
++
++        self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
++        self.assertIs(contextFactory, tls.contextFactory)
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-07.patch twisted-18.9.0/debian/patches/CVE-2019-12855-07.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-07.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-07.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,31 @@
+From 5ed194c0514a04500b3190b0ecbad0cce8b9b82d Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Thu, 9 May 2019 12:12:32 -0400
+Subject: [PATCH 07/17] Adjust tests to TLSInitiatingInitializer being required
+ by default
+
+---
+ src/twisted/words/test/test_jabberxmlstream.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index 6df336deb20..2b8dcd9516e 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -765,6 +765,7 @@ def test_wantedNotSupportedNotRequired(self):
+         No StartTLS is initiated when wanted, not required, SSL not available.
+         """
+         xmlstream.ssl = None
++        self.init.required = False
+ 
+         d = self.init.start()
+         d.addCallback(self.assertEqual, None)
+@@ -810,6 +811,7 @@ def test_notWantedNotRequired(self):
+         tls = domish.Element(('urn:ietf:params:xml:ns:xmpp-tls', 'starttls'))
+         self.xmlstream.features = {(tls.uri, tls.name): tls}
+         self.init.wanted = False
++        self.init.required = False
+ 
+         d = self.init.start()
+         d.addCallback(self.assertEqual, None)
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-09.patch twisted-18.9.0/debian/patches/CVE-2019-12855-09.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-09.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-09.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,23 @@
+From 0a93949f91ea22cfc5453c326e36e927c8da1015 Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Mon, 27 May 2019 13:53:31 +0200
+Subject: [PATCH 09/17] Fix skipping renamed test when SSL is not available
+
+---
+ src/twisted/words/test/test_jabberxmlstream.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index 2b8dcd9516e..d9f4962ec0c 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -755,7 +755,7 @@ def fakeStartTLS(contextFactory):
+ 
+ 
+     if not xmlstream.ssl:
+-        testWantedSupported.skip = "SSL not available"
++        test_wantedSupported.skip = "SSL not available"
+         test_certificateVerify = "SSL not available"
+         test_certificateVerifyContext = "SSL not available"
+ 
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-10.patch twisted-18.9.0/debian/patches/CVE-2019-12855-10.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-10.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-10.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,105 @@
+From 751ac6f754146e5b61ab65d2995be2a9534bd41d Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Mon, 27 May 2019 14:48:26 +0200
+Subject: [PATCH 10/17] Skip TLS tests if OpenSSL is not available
+
+---
+ src/twisted/words/test/test_jabberclient.py   | 12 +++++++++-
+ .../words/test/test_jabberxmlstream.py        | 22 ++++++++++++-------
+ 2 files changed, 25 insertions(+), 9 deletions(-)
+
+diff --git a/src/twisted/words/test/test_jabberclient.py b/src/twisted/words/test/test_jabberclient.py
+index 2e39de72cee..8afb92951f7 100644
+--- a/src/twisted/words/test/test_jabberclient.py
++++ b/src/twisted/words/test/test_jabberclient.py
+@@ -9,13 +9,21 @@
+ 
+ from hashlib import sha1
+ 
+-from twisted.internet import defer, ssl
++from twisted.internet import defer
+ from twisted.python.compat import unicode
+ from twisted.trial import unittest
+ from twisted.words.protocols.jabber import client, error, jid, xmlstream
+ from twisted.words.protocols.jabber.sasl import SASLInitiatingInitializer
+ from twisted.words.xish import utility
+ 
++try:
++   from twisted.internet import ssl
++except ImportError:
++    ssl = None
++    skipWhenNoSSL = "SSL not available"
++else:
++    skipWhenNoSSL = None
++
+ IQ_AUTH_GET = '/iq[@type="get"]/query[@xmlns="jabber:iq:auth"]'
+ IQ_AUTH_SET = '/iq[@type="set"]/query[@xmlns="jabber:iq:auth"]'
+ NS_BIND = 'urn:ietf:params:xml:ns:xmpp-bind'
+@@ -478,3 +486,5 @@ def test_tlsContextFactory(self):
+ 
+         self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
+         self.assertIs(contextFactory, tls.contextFactory)
++
++    test_tlsContextFactory.skip = skipWhenNoSSL
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index d9f4962ec0c..aad0305ef99 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -14,8 +14,6 @@
+ from twisted.internet import defer, task
+ from twisted.internet.error import ConnectionLost
+ from twisted.internet.interfaces import IProtocolFactory
+-from twisted.internet.ssl import CertificateOptions
+-from twisted.internet._sslverify import ClientTLSOptions
+ from twisted.python import failure
+ from twisted.python.compat import unicode
+ from twisted.test import proto_helpers
+@@ -23,7 +21,15 @@
+ from twisted.words.xish import domish
+ from twisted.words.protocols.jabber import error, ijabber, jid, xmlstream
+ 
+-
++try:
++   from twisted.internet import ssl
++except ImportError:
++    ssl = None
++    skipWhenNoSSL = "SSL not available"
++else:
++    skipWhenNoSSL = None
++    from twisted.internet.ssl import CertificateOptions
++    from twisted.internet._sslverify import ClientTLSOptions
+ 
+ NS_XMPP_TLS = 'urn:ietf:params:xml:ns:xmpp-tls'
+ 
+@@ -710,6 +716,8 @@ def test_wantedSupported(self):
+ 
+         return d
+ 
++    test_wantedSupported.skip = skipWhenNoSSL
++
+ 
+     def test_certificateVerify(self):
+         """
+@@ -731,6 +739,8 @@ def fakeStartTLS(contextFactory):
+         self.assertEqual(['TLS', 'reset', 'header'], self.done)
+         return d
+ 
++    test_certificateVerify.skip = skipWhenNoSSL
++
+ 
+     def test_certificateVerifyContext(self):
+         """
+@@ -753,11 +763,7 @@ def fakeStartTLS(contextFactory):
+         self.assertEqual(['TLS', 'reset', 'header'], self.done)
+         return d
+ 
+-
+-    if not xmlstream.ssl:
+-        test_wantedSupported.skip = "SSL not available"
+-        test_certificateVerify = "SSL not available"
+-        test_certificateVerifyContext = "SSL not available"
++    test_certificateVerifyContext.skip = skipWhenNoSSL
+ 
+ 
+     def test_wantedNotSupportedNotRequired(self):
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-11.patch twisted-18.9.0/debian/patches/CVE-2019-12855-11.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-11.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-11.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,46 @@
+From 672a6338dea08a17cbe18af3d47bdb14fcd0d84b Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Mon, 27 May 2019 15:33:20 +0200
+Subject: [PATCH 11/17] Fix indents
+
+---
+ src/twisted/words/test/test_jabberclient.py    | 4 ++--
+ src/twisted/words/test/test_jabberxmlstream.py | 2 +-
+ 2 files changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/twisted/words/test/test_jabberclient.py b/src/twisted/words/test/test_jabberclient.py
+index 8afb92951f7..7c31bed8656 100644
+--- a/src/twisted/words/test/test_jabberclient.py
++++ b/src/twisted/words/test/test_jabberclient.py
+@@ -17,7 +17,7 @@
+ from twisted.words.xish import utility
+ 
+ try:
+-   from twisted.internet import ssl
++    from twisted.internet import ssl
+ except ImportError:
+     ssl = None
+     skipWhenNoSSL = "SSL not available"
+@@ -406,7 +406,7 @@ def test_basic(self):
+         # XMPPAuthenticator (that has its associateWithXmlStream called) that
+         # is in turn initialized with the arguments to the factory.
+         xs = client.basicClientFactory(self.client_jid,
+-                                      'secret').buildProtocol(None)
++                                       'secret').buildProtocol(None)
+ 
+         # test authenticator's instance variables
+         self.assertEqual('example.com', xs.authenticator.otherHost)
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index aad0305ef99..7b384645a2c 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -22,7 +22,7 @@
+ from twisted.words.protocols.jabber import error, ijabber, jid, xmlstream
+ 
+ try:
+-   from twisted.internet import ssl
++    from twisted.internet import ssl
+ except ImportError:
+     ssl = None
+     skipWhenNoSSL = "SSL not available"
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-12.patch twisted-18.9.0/debian/patches/CVE-2019-12855-12.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-12.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-12.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,34 @@
+From a649757186c12d2b4f4a8e215b4d36ba26bd331f Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Tue, 28 May 2019 16:53:22 +0200
+Subject: [PATCH 12/17] Better docstring for BasicAuthenticatorTests
+
+---
+ src/twisted/words/test/test_jabberclient.py | 13 ++++++++-----
+ 1 file changed, 8 insertions(+), 5 deletions(-)
+
+diff --git a/src/twisted/words/test/test_jabberclient.py b/src/twisted/words/test/test_jabberclient.py
+index 7c31bed8656..1403131baf6 100644
+--- a/src/twisted/words/test/test_jabberclient.py
++++ b/src/twisted/words/test/test_jabberclient.py
+@@ -394,11 +394,14 @@ class BasicAuthenticatorTests(unittest.TestCase):
+ 
+     def test_basic(self):
+         """
+-        Test basic operations.
+-
+-        Setup a basicClientFactory, which sets up a BasicAuthenticator, and let
+-        it produce a protocol instance. Then inspect the instance variables of
+-        the authenticator and XML stream objects.
++        Authenticator and stream are properly constructed by the factory.
++
++        The L{xmlstream.XmlStream} protocol created by the factory has the new
++        L{client.BasicAuthenticator} instance in its C{authenticator}
++        attribute.  It is set up with C{jid} and C{password} as passed to the
++        factory, C{otherHost} taken from the client JID. The stream futher has
++        two initializers, for TLS and authentication, of which the first has
++        its C{required} attribute set to C{True}.
+         """
+         self.client_jid = jid.JID('user@example.com/resource')
+ 
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-13.patch twisted-18.9.0/debian/patches/CVE-2019-12855-13.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-13.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-13.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,211 @@
+From ea2d28f7035cdbc56063a0672acef426086875ff Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Sun, 16 Jun 2019 18:41:49 +0200
+Subject: [PATCH 13/17] Rename contextFactory to configurationForTLS, make
+ private vars
+
+---
+ src/twisted/words/newsfragments/9561.feature  |  2 +-
+ src/twisted/words/protocols/jabber/client.py  | 37 +++++++++++--------
+ .../words/protocols/jabber/xmlstream.py       | 28 +++++++-------
+ src/twisted/words/test/test_jabberclient.py   | 26 +++++++------
+ .../words/test/test_jabberxmlstream.py        |  3 ++
+ 5 files changed, 55 insertions(+), 41 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/client.py b/src/twisted/words/protocols/jabber/client.py
+index 4b310e34f38..db4cbfccf21 100644
+--- a/src/twisted/words/protocols/jabber/client.py
++++ b/src/twisted/words/protocols/jabber/client.py
+@@ -298,7 +298,7 @@ def start(self):
+ 
+ 
+ 
+-def XMPPClientFactory(jid, password, contextFactory=None):
++def XMPPClientFactory(jid, password, configurationForTLS=None):
+     """
+     Client factory for XMPP 1.0 (only).
+ 
+@@ -314,16 +314,18 @@ def XMPPClientFactory(jid, password, contextFactory=None):
+     @param password: password to authenticate with.
+     @type password: L{unicode}
+ 
+-    @param contextFactory: An object which creates appropriately configured TLS
+-        connections. This is passed to C{startTLS} on the transport and is
+-        preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
+-        See L{xmlstream.TLSInitiatingInitializer} for details.
+-    @type contextFactory: L{IOpenSSLClientConnectionCreator}
++    @param configurationForTLS: An object which creates appropriately
++        configured TLS connections. This is passed to C{startTLS} on the
++        transport and is preferably created using
++        L{twisted.internet.ssl.optionsForClientTLS}.  See
++        L{xmlstream.TLSInitiatingInitializer} for details.
++    @type configurationForTLS: L{IOpenSSLClientConnectionCreator}
+ 
+     @return: XML stream factory.
+     @rtype: L{xmlstream.XmlStreamFactory}
+     """
+-    a = XMPPAuthenticator(jid, password, contextFactory=contextFactory)
++    a = XMPPAuthenticator(jid, password,
++                          configurationForTLS=configurationForTLS)
+     return xmlstream.XmlStreamFactory(a)
+ 
+ 
+@@ -361,21 +363,23 @@ class XMPPAuthenticator(xmlstream.ConnectAuthenticator):
+ 
+     @ivar password: password to be used during SASL authentication.
+     @type password: L{unicode}
+-
+-    @ivar contextFactory: An object which creates appropriately configured TLS
+-        connections. This is passed to C{startTLS} on the transport and is
+-        preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
+-        See L{xmlstream.TLSInitiatingInitializer} for details.
+-    @type contextFactory: L{IOpenSSLClientConnectionCreator}
+     """
+ 
+     namespace = 'jabber:client'
+ 
+-    def __init__(self, jid, password, contextFactory=None):
++    def __init__(self, jid, password, configurationForTLS=None):
++        """
++        @param configurationForTLS: An object which creates appropriately
++            configured TLS connections. This is passed to C{startTLS} on the
++            transport and is preferably created using
++            L{twisted.internet.ssl.optionsForClientTLS}. See
++            L{xmlstream.TLSInitiatingInitializer} for details.
++        @type configurationForTLS: L{IOpenSSLClientConnectionCreator}
++        """
+         xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+         self.jid = jid
+         self.password = password
+-        self.contextFactory = contextFactory
++        self._configurationForTLS = configurationForTLS
+ 
+ 
+     def associateWithStream(self, xs):
+@@ -392,7 +396,8 @@ def associateWithStream(self, xs):
+         xs.initializers = [
+                 CheckVersionInitializer(xs),
+                 xmlstream.TLSInitiatingInitializer(
+-                    xs, required=True, contextFactory=self.contextFactory),
++                    xs, required=True,
++                    configurationForTLS=self._configurationForTLS),
+                 sasl.SASLInitiatingInitializer(xs, required=True),
+                 BindInitializer(xs, required=True),
+                 SessionInitializer(xs, required=False),
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index 1ed79d47726..135d71295df 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -403,25 +403,27 @@ class TLSInitiatingInitializer(BaseFeatureInitiatingInitializer):
+ 
+     @ivar wanted: indicates if TLS negotiation is wanted.
+     @type wanted: C{bool}
+-
+-    @ivar contextFactory: An object which creates appropriately configured TLS
+-        connections. This is passed to C{startTLS} on the transport and is
+-        preferably created using L{twisted.internet.ssl.optionsForClientTLS}.
+-        If C{None}, the default is to verify the server certificate against
+-        the trust roots as provided by the platform. See
+-        L{twisted.internet._sslverify.platformTrust}.
+-    @type contextFactory: L{IOpenSSLClientConnectionCreator}
+     """
+ 
+     feature = (NS_XMPP_TLS, 'starttls')
+     wanted = True
+-    contextFactory = None
+     _deferred = None
++    _configurationForTLS = None
+ 
+-    def __init__(self, xs, required=True, contextFactory=None):
++    def __init__(self, xs, required=True, configurationForTLS=None):
++        """
++        @param configurationForTLS: An object which creates appropriately
++            configured TLS connections. This is passed to C{startTLS} on the
++            transport and is preferably created using
++            L{twisted.internet.ssl.optionsForClientTLS}.  If C{None}, the
++            default is to verify the server certificate against the trust roots
++            as provided by the platform. See
++            L{twisted.internet._sslverify.platformTrust}.
++        @type configurationForTLS: L{IOpenSSLClientConnectionCreator}
++        """
+         super(TLSInitiatingInitializer, self).__init__(
+                 xs, required=required)
+-        self.contextFactory = contextFactory
++        self._configurationForTLS = configurationForTLS
+ 
+ 
+     def onProceed(self, obj):
+@@ -430,8 +432,8 @@ def onProceed(self, obj):
+         """
+ 
+         self.xmlstream.removeObserver('/failure', self.onFailure)
+-        if self.contextFactory:
+-            ctx = self.contextFactory
++        if self._configurationForTLS:
++            ctx = self._configurationForTLS
+         else:
+             ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
+         self.xmlstream.transport.startTLS(ctx)
+diff --git a/src/twisted/words/test/test_jabberclient.py b/src/twisted/words/test/test_jabberclient.py
+index 1403131baf6..4f5c8092419 100644
+--- a/src/twisted/words/test/test_jabberclient.py
++++ b/src/twisted/words/test/test_jabberclient.py
+@@ -466,28 +466,32 @@ def test_basic(self):
+         self.assertFalse(session.required)
+ 
+ 
+-    def test_tlsContextFactory(self):
++    def test_tlsConfiguration(self):
+         """
+-        Test basic operations.
+-
+-        Setup an XMPPClientFactory, which sets up an XMPPAuthenticator, and let
+-        it produce a protocol instance. Then inspect the instance variables of
+-        the authenticator and XML stream objects.
++        A TLS configuration is passed to the TLS initializer.
+         """
++        configs = []
++
++        def init(self, xs, required=True, configurationForTLS=None):
++            configs.append(configurationForTLS)
++
+         self.client_jid = jid.JID('user@example.com/resource')
+ 
+         # Get an XmlStream instance. Note that it gets initialized with the
+         # XMPPAuthenticator (that has its associateWithXmlStream called) that
+         # is in turn initialized with the arguments to the factory.
+-        contextFactory = ssl.CertificateOptions()
+-        factory = client.XMPPClientFactory(self.client_jid, 'secret',
+-                                           contextFactory=contextFactory)
++        configurationForTLS = ssl.CertificateOptions()
++        factory = client.XMPPClientFactory(
++            self.client_jid, 'secret',
++            configurationForTLS=configurationForTLS)
++        self.patch(xmlstream.TLSInitiatingInitializer, "__init__", init)
+         xs = factory.buildProtocol(None)
+ 
+         # test list of initializers
+         version, tls, sasl, bind, session = xs.initializers
+ 
+         self.assertIsInstance(tls, xmlstream.TLSInitiatingInitializer)
+-        self.assertIs(contextFactory, tls.contextFactory)
++        self.assertIs(configurationForTLS, configs[0])
++
+ 
+-    test_tlsContextFactory.skip = skipWhenNoSSL
++    test_tlsConfiguration.skip = skipWhenNoSSL
+diff --git a/src/twisted/words/test/test_jabberxmlstream.py b/src/twisted/words/test/test_jabberxmlstream.py
+index 7b384645a2c..85f6d195d4a 100644
+--- a/src/twisted/words/test/test_jabberxmlstream.py
++++ b/src/twisted/words/test/test_jabberxmlstream.py
+@@ -747,6 +747,9 @@ def test_certificateVerifyContext(self):
+         A custom contextFactory is passed through to startTLS.
+         """
+         ctx = CertificateOptions()
++        self.init = xmlstream.TLSInitiatingInitializer(
++            self.xmlstream, configurationForTLS=ctx)
++
+         self.init.contextFactory = ctx
+ 
+         def fakeStartTLS(contextFactory):
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-14.patch twisted-18.9.0/debian/patches/CVE-2019-12855-14.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-14.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-14.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,40 @@
+From 05556b6ca14a49e4c7f3b5e8ede83137b869926e Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Sun, 16 Jun 2019 19:02:52 +0200
+Subject: [PATCH 14/17] Move check for configurationTLS being None to __init__
+
+---
+ src/twisted/words/protocols/jabber/xmlstream.py | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index 135d71295df..dd4bd8f1932 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -423,7 +423,11 @@ def __init__(self, xs, required=True, configurationForTLS=None):
+         """
+         super(TLSInitiatingInitializer, self).__init__(
+                 xs, required=required)
+-        self._configurationForTLS = configurationForTLS
++        if configurationForTLS:
++            self._configurationForTLS = configurationForTLS
++        else:
++            self._configurationForTLS = ssl.optionsForClientTLS(
++                self.xmlstream.authenticator.otherHost)
+ 
+ 
+     def onProceed(self, obj):
+@@ -432,11 +436,7 @@ def onProceed(self, obj):
+         """
+ 
+         self.xmlstream.removeObserver('/failure', self.onFailure)
+-        if self._configurationForTLS:
+-            ctx = self._configurationForTLS
+-        else:
+-            ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
+-        self.xmlstream.transport.startTLS(ctx)
++        self.xmlstream.transport.startTLS(self._configurationForTLS)
+         self.xmlstream.reset()
+         self.xmlstream.sendHeader()
+         self._deferred.callback(Reset)
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-15.patch twisted-18.9.0/debian/patches/CVE-2019-12855-15.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-15.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-15.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,59 @@
+From 7caf8ac8795492e346e8f52633ff6d343a07edde Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Sun, 16 Jun 2019 19:11:35 +0200
+Subject: [PATCH 15/17] Document configurationForTLS being None directly
+
+---
+ src/twisted/words/protocols/jabber/client.py    | 16 ++++++++++------
+ src/twisted/words/protocols/jabber/xmlstream.py |  3 ++-
+ 2 files changed, 12 insertions(+), 7 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/client.py b/src/twisted/words/protocols/jabber/client.py
+index db4cbfccf21..8f197cdafe1 100644
+--- a/src/twisted/words/protocols/jabber/client.py
++++ b/src/twisted/words/protocols/jabber/client.py
+@@ -317,9 +317,10 @@ def XMPPClientFactory(jid, password, configurationForTLS=None):
+     @param configurationForTLS: An object which creates appropriately
+         configured TLS connections. This is passed to C{startTLS} on the
+         transport and is preferably created using
+-        L{twisted.internet.ssl.optionsForClientTLS}.  See
+-        L{xmlstream.TLSInitiatingInitializer} for details.
+-    @type configurationForTLS: L{IOpenSSLClientConnectionCreator}
++        L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the default is
++        to verify the server certificate against the trust roots as provided by
++        the platform. See L{twisted.internet._sslverify.platformTrust}.
++    @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or C{None}
+ 
+     @return: XML stream factory.
+     @rtype: L{xmlstream.XmlStreamFactory}
+@@ -372,9 +373,12 @@ def __init__(self, jid, password, configurationForTLS=None):
+         @param configurationForTLS: An object which creates appropriately
+             configured TLS connections. This is passed to C{startTLS} on the
+             transport and is preferably created using
+-            L{twisted.internet.ssl.optionsForClientTLS}. See
+-            L{xmlstream.TLSInitiatingInitializer} for details.
+-        @type configurationForTLS: L{IOpenSSLClientConnectionCreator}
++            L{twisted.internet.ssl.optionsForClientTLS}. If C{None}, the
++            default is to verify the server certificate against the trust roots
++            as provided by the platform. See
++            L{twisted.internet._sslverify.platformTrust}.
++        @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or
++            C{None}
+         """
+         xmlstream.ConnectAuthenticator.__init__(self, jid.host)
+         self.jid = jid
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index dd4bd8f1932..905402c5360 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -419,7 +419,8 @@ def __init__(self, xs, required=True, configurationForTLS=None):
+             default is to verify the server certificate against the trust roots
+             as provided by the platform. See
+             L{twisted.internet._sslverify.platformTrust}.
+-        @type configurationForTLS: L{IOpenSSLClientConnectionCreator}
++        @type configurationForTLS: L{IOpenSSLClientConnectionCreator} or
++            C{None}
+         """
+         super(TLSInitiatingInitializer, self).__init__(
+                 xs, required=required)
+
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-12855-17.patch twisted-18.9.0/debian/patches/CVE-2019-12855-17.patch
--- twisted-18.9.0/debian/patches/CVE-2019-12855-17.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-12855-17.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,41 @@
+From abbf0fd52c13b1fb5e1429189a3fcc48565870a5 Mon Sep 17 00:00:00 2001
+From: Ralph Meijer <ralphm@ik.nu>
+Date: Sun, 16 Jun 2019 19:50:33 +0200
+Subject: [PATCH 17/17] Revert "Move check for configurationTLS being None to
+ __init__"
+
+This reverts commit 05556b6ca14a49e4c7f3b5e8ede83137b869926e.
+---
+ src/twisted/words/protocols/jabber/xmlstream.py | 12 ++++++------
+ 1 file changed, 6 insertions(+), 6 deletions(-)
+
+diff --git a/src/twisted/words/protocols/jabber/xmlstream.py b/src/twisted/words/protocols/jabber/xmlstream.py
+index 905402c5360..20948c6d3be 100644
+--- a/src/twisted/words/protocols/jabber/xmlstream.py
++++ b/src/twisted/words/protocols/jabber/xmlstream.py
+@@ -424,11 +424,7 @@ def __init__(self, xs, required=True, configurationForTLS=None):
+         """
+         super(TLSInitiatingInitializer, self).__init__(
+                 xs, required=required)
+-        if configurationForTLS:
+-            self._configurationForTLS = configurationForTLS
+-        else:
+-            self._configurationForTLS = ssl.optionsForClientTLS(
+-                self.xmlstream.authenticator.otherHost)
++        self._configurationForTLS = configurationForTLS
+ 
+ 
+     def onProceed(self, obj):
+@@ -437,7 +433,11 @@ def onProceed(self, obj):
+         """
+ 
+         self.xmlstream.removeObserver('/failure', self.onFailure)
+-        self.xmlstream.transport.startTLS(self._configurationForTLS)
++        if self._configurationForTLS:
++            ctx = self._configurationForTLS
++        else:
++            ctx = ssl.optionsForClientTLS(self.xmlstream.otherEntity.host)
++        self.xmlstream.transport.startTLS(ctx)
+         self.xmlstream.reset()
+         self.xmlstream.sendHeader()
+         self._deferred.callback(Reset)
diff -Nru twisted-18.9.0/debian/patches/CVE-2019-951x.patch twisted-18.9.0/debian/patches/CVE-2019-951x.patch
--- twisted-18.9.0/debian/patches/CVE-2019-951x.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2019-951x.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,627 @@
+Backport of:
+
+From 1595d9adc21c580065d1d6036c9611c411990816 Mon Sep 17 00:00:00 2001
+From: Mark Williams <mrw@enotuniq.org>
+Date: Thu, 8 Aug 2019 19:18:20 -0700
+Subject: [PATCH] Buffer outbound control frames and timeout invalid clients.
+
+A HTTP/2 server can be effectively DoSed by having a remote peer stop
+reading from a connection while continuing to send frames that trigger
+automatic control frame emission. This patch addresses that by ensuring
+that rather than automatically write all control frames into the
+transport, we will buffer them in the HTTP/2 connection object, ensuring
+that we have visibility into the size of that buffer, and thus can abort
+the connection if it grows too large.
+
+An HTTP/2 server can also be DoSed by a client that sends only invalid
+frames (e.g., a RESET_STREAM frame when no streams have been created.)
+This patches addresses that by only resetting H2Connection's timeout
+when the underlying h2.connection.H2Connection has parsed at least one
+valid frame.
+---
+ src/twisted/web/_http2.py          | 130 ++++++++++----
+ src/twisted/web/error.py           |   8 +
+ src/twisted/web/http.py            |   8 +-
+ src/twisted/web/test/test_http.py  |  13 +-
+ src/twisted/web/test/test_http2.py | 262 ++++++++++++++++++++++++++++-
+ 5 files changed, 387 insertions(+), 34 deletions(-)
+
+--- a/src/twisted/web/_http2.py
++++ b/src/twisted/web/_http2.py
+@@ -41,6 +41,7 @@ from twisted.internet.protocol import Pr
+ from twisted.logger import Logger
+ from twisted.protocols.policies import TimeoutMixin
+ from twisted.python.failure import Failure
++from twisted.web.error import ExcessiveBufferingError
+ 
+ 
+ # This API is currently considered private.
+@@ -138,6 +139,12 @@ class H2Connection(Protocol, TimeoutMixi
+         self._streamCleanupCallbacks = {}
+         self._stillProducing = True
+ 
++        # Limit the number of buffered control frame (e.g. PING and
++        # SETTINGS) bytes.
++        self._maxBufferedControlFrameBytes = 1024 * 17
++        self._bufferedControlFrames = deque()
++        self._bufferedControlFrameBytes = 0
++
+         if reactor is None:
+             from twisted.internet import reactor
+         self._reactor = reactor
+@@ -165,18 +172,19 @@ class H2Connection(Protocol, TimeoutMixi
+         @param data: The data received from the transport.
+         @type data: L{bytes}
+         """
+-        self.resetTimeout()
+-
+         try:
+             events = self.conn.receive_data(data)
+         except h2.exceptions.ProtocolError:
+-            # A remote protocol error terminates the connection.
+-            dataToSend = self.conn.data_to_send()
+-            self.transport.write(dataToSend)
+-            self.transport.loseConnection()
+-            self.connectionLost(Failure())
++            stillActive = self._tryToWriteControlData()
++            if stillActive:
++                self.transport.loseConnection()
++                self.connectionLost(Failure(), _cancelTimeouts=False)
+             return
+ 
++        # Only reset the timeout if we've received an actual H2
++        # protocol message
++        self.resetTimeout()
++
+         for event in events:
+             if isinstance(event, h2.events.RequestReceived):
+                 self._requestReceived(event)
+@@ -192,11 +200,12 @@ class H2Connection(Protocol, TimeoutMixi
+                 self._handlePriorityUpdate(event)
+             elif isinstance(event, h2.events.ConnectionTerminated):
+                 self.transport.loseConnection()
+-                self.connectionLost(ConnectionLost("Remote peer sent GOAWAY"))
++                self.connectionLost(
++                    ConnectionLost("Remote peer sent GOAWAY"),
++                    _cancelTimeouts=False,
++                )
+ 
+-        dataToSend = self.conn.data_to_send()
+-        if dataToSend:
+-            self.transport.write(dataToSend)
++        self._tryToWriteControlData()
+ 
+ 
+     def timeoutConnection(self):
+@@ -259,15 +268,23 @@ class H2Connection(Protocol, TimeoutMixi
+         self.transport.abortConnection()
+ 
+ 
+-    def connectionLost(self, reason):
++    def connectionLost(self, reason, _cancelTimeouts=True):
+         """
+         Called when the transport connection is lost.
+ 
+-        Informs all outstanding response handlers that the connection has been
+-        lost, and cleans up all internal state.
++        Informs all outstanding response handlers that the connection
++        has been lost, and cleans up all internal state.
++
++        @param reason: See L{IProtocol.connectionLost}
++
++        @param _cancelTimeouts: Propagate the C{reason} to this
++            connection's streams but don't cancel any timers, so that
++            peers who never read the data we've written are eventually
++            timed out.
+         """
+         self._stillProducing = False
+-        self.setTimeout(None)
++        if _cancelTimeouts:
++            self.setTimeout(None)
+ 
+         for stream in self.streams.values():
+             stream.connectionLost(reason)
+@@ -276,7 +293,7 @@ class H2Connection(Protocol, TimeoutMixi
+             self._requestDone(streamID)
+ 
+         # If we were going to force-close the transport, we don't have to now.
+-        if self._abortingCall is not None:
++        if _cancelTimeouts and self._abortingCall is not None:
+             self._abortingCall.cancel()
+             self._abortingCall = None
+ 
+@@ -324,7 +341,8 @@ class H2Connection(Protocol, TimeoutMixi
+     #
+     # Note that all of this only applies to *data*. Headers and other control
+     # frames deliberately skip this processing as they are not subject to flow
+-    # control or priority constraints.
++    # control or priority constraints. Instead, they are stored in their own buffer
++    # which is used primarily to detect excessive buffering.
+     def stopProducing(self):
+         """
+         Stop producing data.
+@@ -343,6 +361,8 @@ class H2Connection(Protocol, TimeoutMixi
+         for the time being, and to stop until resumeProducing() is called.
+         """
+         self._consumerBlocked = Deferred()
++        # Ensure pending control data (if any) are sent first.
++        self._consumerBlocked.addCallback(self._flushBufferedControlData)
+ 
+ 
+     def resumeProducing(self):
+@@ -568,7 +588,7 @@ class H2Connection(Protocol, TimeoutMixi
+             # when a connection is lost, so that's what we do too.
+             return
+         else:
+-            self.transport.write(self.conn.data_to_send())
++            self._tryToWriteControlData()
+ 
+ 
+     def writeDataToStream(self, streamID, data):
+@@ -622,8 +642,9 @@ class H2Connection(Protocol, TimeoutMixi
+         @type streamID: L{int}
+         """
+         self.conn.reset_stream(streamID)
+-        self.transport.write(self.conn.data_to_send())
+-        self._requestDone(streamID)
++        stillActive = self._tryToWriteControlData()
++        if stillActive:
++            self._requestDone(streamID)
+ 
+ 
+     def _requestDone(self, streamID):
+@@ -739,9 +760,7 @@ class H2Connection(Protocol, TimeoutMixi
+         @type increment: L{int}
+         """
+         self.conn.acknowledge_received_data(increment, streamID)
+-        data = self.conn.data_to_send()
+-        if data:
+-            self.transport.write(data)
++        self._tryToWriteControlData()
+ 
+ 
+     def _isSecure(self):
+@@ -766,7 +785,7 @@ class H2Connection(Protocol, TimeoutMixi
+         """
+         headers = [(b':status', b'100')]
+         self.conn.send_headers(headers=headers, stream_id=streamID)
+-        self.transport.write(self.conn.data_to_send())
++        self._tryToWriteControlData()
+ 
+ 
+     def _respondToBadRequestAndDisconnect(self, streamID):
+@@ -791,11 +810,11 @@ class H2Connection(Protocol, TimeoutMixi
+             stream_id=streamID,
+             end_stream=True
+         )
+-        self.transport.write(self.conn.data_to_send())
+-
+-        stream = self.streams[streamID]
+-        stream.connectionLost(ConnectionLost("Invalid request"))
+-        self._requestDone(streamID)
++        stillActive = self._tryToWriteControlData()
++        if stillActive:
++            stream = self.streams[streamID]
++            stream.connectionLost(ConnectionLost("Invalid request"))
++            self._requestDone(streamID)
+ 
+ 
+     def _streamIsActive(self, streamID):
+@@ -811,6 +830,59 @@ class H2Connection(Protocol, TimeoutMixi
+         """
+         return streamID in self.streams
+ 
++    def _tryToWriteControlData(self):
++        """
++        Checks whether the connection is blocked on flow control and,
++        if it isn't, writes any buffered control data.
++
++        @return: L{True} if the connection is still active and
++            L{False} if it was aborted because too many bytes have
++            been written but not consumed by the other end.
++        """
++        bufferedBytes = self.conn.data_to_send()
++        if not bufferedBytes:
++            return True
++
++        if self._consumerBlocked is None and not self._bufferedControlFrames:
++            # The consumer isn't blocked, and we don't have any buffered frames:
++            # write this directly.
++            self.transport.write(bufferedBytes)
++            return True
++        else:
++            # Either the consumer is blocked or we have buffered frames. If the
++            # consumer is blocked, we'll write this when we unblock. If we have
++            # buffered frames, we have presumably been re-entered from
++            # transport.write, and so to avoid reordering issues we'll buffer anyway.
++            self._bufferedControlFrames.append(bufferedBytes)
++            self._bufferedControlFrameBytes += len(bufferedBytes)
++
++            if self._bufferedControlFrameBytes >= self._maxBufferedControlFrameBytes:
++                self._log.error(
++                    "Maximum number of control frame bytes buffered: "
++                    "{bufferedControlFrameBytes} > = {maxBufferedControlFrameBytes}. "
++                    "Aborting connection to client: {client} ",
++                    bufferedControlFrameBytes=self._bufferedControlFrameBytes,
++                    maxBufferedControlFrameBytes=self._maxBufferedControlFrameBytes,
++                    client=self.transport.getPeer(),
++                )
++                # We've exceeded a reasonable buffer size for max buffered control frames.
++                # This is a denial of service risk, so we're going to drop this connection.
++                self.transport.abortConnection()
++                self.connectionLost(ExcessiveBufferingError())
++                return False
++            return True
++
++    def _flushBufferedControlData(self, *args):
++        """
++        Called when the connection is marked writable again after being marked unwritable.
++        Attempts to flush buffered control data if there is any.
++        """
++        # To respect backpressure here we send each write in order, paying attention to whether
++        # we got blocked
++        while self._consumerBlocked is None and self._bufferedControlFrames:
++            nextWrite = self._bufferedControlFrames.popleft()
++            self._bufferedControlFrameBytes -= len(nextWrite)
++            self.transport.write(nextWrite)
+ 
+ 
+ @implementer(ITransport, IConsumer, IPushProducer)
+--- a/src/twisted/web/error.py
++++ b/src/twisted/web/error.py
+@@ -300,6 +300,14 @@ class UnsupportedType(Exception):
+     """
+ 
+ 
++class ExcessiveBufferingError(Exception):
++    """
++    The HTTP/2 protocol has been forced to buffer an excessive amount of
++    outbound data, and has therefore closed the connection and dropped all
++    outbound data.
++    """
++
++
+ 
+ class FlattenerError(Exception):
+     """
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2892,7 +2892,8 @@ class _GenericHTTPChannelProtocol(proxyF
+                 # We need to make sure that the HTTPChannel is unregistered
+                 # from the transport so that the H2Connection can register
+                 # itself if possible.
+-                self._channel._networkProducer.unregisterProducer()
++                networkProducer = self._channel._networkProducer
++                networkProducer.unregisterProducer()
+ 
+                 transport = self._channel.transport
+                 self._channel = H2Connection()
+@@ -2902,6 +2903,11 @@ class _GenericHTTPChannelProtocol(proxyF
+                 self._channel.timeOut = self._timeOut
+                 self._channel.callLater = self._callLater
+                 self._channel.makeConnection(transport)
++
++                # Register the H2Connection as the transport's
++                # producer, so that the transport can apply back
++                # pressure.
++                networkProducer.registerProducer(self._channel, True)
+             else:
+                 # Only HTTP/2 and HTTP/1.1 are supported right now.
+                 assert negotiatedProtocol == b'http/1.1', \
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -896,15 +896,22 @@ class GenericHTTPChannelTests(unittest.T
+         genericProtocol.requestFactory = DummyHTTPHandlerProxy
+         genericProtocol.makeConnection(transport)
+ 
++        originalChannel = genericProtocol._channel
++
+         # We expect the transport has a underlying channel registered as
+         # a producer.
+-        self.assertIs(transport.producer, genericProtocol._channel)
++        self.assertIs(transport.producer, originalChannel)
+ 
+         # Force the upgrade.
+         genericProtocol.dataReceived(b'P')
+ 
+-        # The transport should now have no producer.
+-        self.assertIs(transport.producer, None)
++        # The transport should not have the original channel as its
++        # producer...
++        self.assertIsNot(transport.producer, originalChannel)
++
++        # ...it should have the new H2 channel as its producer
++        self.assertIs(transport.producer, genericProtocol._channel)
++
+     if not http.H2_ENABLED:
+         test_unregistersProducer.skip = "HTTP/2 support not present"
+ 
+--- a/src/twisted/web/test/test_http2.py
++++ b/src/twisted/web/test/test_http2.py
+@@ -14,7 +14,7 @@ from zope.interface import providedBy, d
+ from twisted.internet import defer, reactor, task, error
+ from twisted.python import failure
+ from twisted.python.compat import iterbytes
+-from twisted.test.proto_helpers import StringTransport
++from twisted.test.proto_helpers import StringTransport, MemoryReactorClock
+ from twisted.test.test_internet import DummyProducer
+ from twisted.trial import unittest
+ from twisted.web import http
+@@ -1651,6 +1651,194 @@ class HTTP2ServerTests(unittest.TestCase
+         return d
+ 
+ 
++    def test_fast400WithCircuitBreaker(self):
++        """
++        A HTTP/2 stream that has had _respondToBadRequestAndDisconnect
++        called on it does not write control frame data if its
++        transport is paused and its control frame limit has been
++        reached.
++        """
++        # Set the connection up.
++        memoryReactor = MemoryReactorClock()
++        connection = H2Connection(memoryReactor)
++        connection.callLater = memoryReactor.callLater
++        # Use the DelayedHTTPHandler to prevent the connection from
++        # writing any response bytes after receiving a request that
++        # establishes the stream.
++        connection.requestFactory = DelayedHTTPHandler
++
++        streamID = 1
++
++        frameFactory = FrameFactory()
++        transport = StringTransport()
++
++        # Establish the connection
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++        # Establish the stream.
++        connection.dataReceived(
++            buildRequestBytes(
++                self.getRequestHeaders, [], frameFactory, streamID=streamID)
++        )
++
++        # Pause the connection and limit the number of outbound bytes
++        # to 0, so that attempting to send the 400 aborts the
++        # connection.
++        connection.pauseProducing()
++        connection._maxBufferedControlFrameBytes = 0
++
++        connection._respondToBadRequestAndDisconnect(streamID)
++
++        self.assertTrue(transport.disconnected)
++
++
++    def test_bufferingAutomaticFrameData(self):
++        """
++        If a the L{H2Connection} has been paused by the transport, it will
++        not write automatic frame data triggered by writes.
++        """
++        # Set the connection up.
++        connection = H2Connection()
++        connection.requestFactory = DummyHTTPHandlerProxy
++        frameFactory = FrameFactory()
++        transport = StringTransport()
++
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++
++        # Now we're going to pause the producer.
++        connection.pauseProducing()
++
++        # Now we're going to send a bunch of empty SETTINGS frames. This
++        # should not cause writes.
++        for _ in range(0, 100):
++            connection.dataReceived(frameFactory.buildSettingsFrame({}).serialize())
++
++        frames = framesFromBytes(transport.value())
++        self.assertEqual(len(frames), 1)
++
++        # Re-enable the transport.
++        connection.resumeProducing()
++        frames = framesFromBytes(transport.value())
++        self.assertEqual(len(frames), 101)
++
++
++    def test_bufferingAutomaticFrameDataWithCircuitBreaker(self):
++        """
++        If the L{H2Connection} has been paused by the transport, it will
++        not write automatic frame data triggered by writes. If this buffer
++        gets too large, the connection will be dropped.
++        """
++        # Set the connection up.
++        connection = H2Connection()
++        connection.requestFactory = DummyHTTPHandlerProxy
++        frameFactory = FrameFactory()
++        transport = StringTransport()
++
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++
++        # Now we're going to pause the producer.
++        connection.pauseProducing()
++
++        # Now we're going to limit the outstanding buffered bytes to
++        # 100 bytes.
++        connection._maxBufferedControlFrameBytes = 100
++
++        # Now we're going to send 11 empty SETTINGS frames. This
++        # should not cause writes, or a close.
++        self.assertFalse(transport.disconnecting)
++        for _ in range(0, 11):
++            connection.dataReceived(frameFactory.buildSettingsFrame({}).serialize())
++        self.assertFalse(transport.disconnecting)
++
++        # Send a last settings frame, which will push us over the buffer limit.
++        connection.dataReceived(frameFactory.buildSettingsFrame({}).serialize())
++        self.assertTrue(transport.disconnected)
++
++
++    def test_bufferingContinuesIfProducerIsPausedOnWrite(self):
++        """
++        If the L{H2Connection} has buffered control frames, is unpaused, and then
++        paused while unbuffering, it persists the buffer and stops trying to write.
++        """
++        class AutoPausingStringTransport(StringTransport):
++            def write(self, *args, **kwargs):
++                StringTransport.write(self, *args, **kwargs)
++                self.producer.pauseProducing()
++
++        # Set the connection up.
++        connection = H2Connection()
++        connection.requestFactory = DummyHTTPHandlerProxy
++        frameFactory = FrameFactory()
++        transport = AutoPausingStringTransport()
++        transport.registerProducer(connection, True)
++
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++
++        # The connection should already be paused.
++        self.assertIsNotNone(connection._consumerBlocked)
++        frames = framesFromBytes(transport.value())
++        self.assertEqual(len(frames), 1)
++        self.assertEqual(connection._bufferedControlFrameBytes, 0)
++
++        # Now we're going to send 11 empty SETTINGS frames. This should produce
++        # no output, but some buffered settings ACKs.
++        for _ in range(0, 11):
++            connection.dataReceived(frameFactory.buildSettingsFrame({}).serialize())
++
++        frames = framesFromBytes(transport.value())
++        self.assertEqual(len(frames), 1)
++        self.assertEqual(connection._bufferedControlFrameBytes, 9 * 11)
++
++        # Ok, now we're going to unpause the producer. This should write only one of the
++        # SETTINGS ACKs, as the connection gets repaused.
++        connection.resumeProducing()
++
++        frames = framesFromBytes(transport.value())
++        self.assertEqual(len(frames), 2)
++        self.assertEqual(connection._bufferedControlFrameBytes, 9 * 10)
++
++
++    def test_circuitBreakerAbortsAfterProtocolError(self):
++        """
++        A client that triggers a L{h2.exceptions.ProtocolError} over a
++        paused connection that's reached its buffered control frame
++        limit causes that connection to be aborted.
++        """
++        memoryReactor = MemoryReactorClock()
++        connection = H2Connection(memoryReactor)
++        connection.callLater = memoryReactor.callLater
++
++        frameFactory = FrameFactory()
++        transport = StringTransport()
++
++        # Establish the connection.
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++
++        # Pause it and limit the number of outbound bytes to 0, so
++        # that a ProtocolError aborts the connection
++        connection.pauseProducing()
++        connection._maxBufferedControlFrameBytes = 0
++
++        # Trigger a ProtocolError with a data frame that refers to an
++        # unknown stream.
++        invalidData = frameFactory.buildDataFrame(
++            data=b'yo', streamID=0xF0
++        ).serialize()
++
++        # The frame should have aborted the connection.
++        connection.dataReceived(invalidData)
++        self.assertTrue(transport.disconnected)
++
++
+ 
+ class H2FlowControlTests(unittest.TestCase, HTTP2TestHelpers):
+     """
+@@ -2364,6 +2552,50 @@ class H2FlowControlTests(unittest.TestCa
+         self.assertFalse(dataFrames)
+ 
+ 
++    def test_abortRequestWithCircuitBreaker(self):
++        """
++        Aborting a request associated with a paused connection that's
++        reached its buffered control frame limit causes that
++        connection to be aborted.
++        """
++        memoryReactor = MemoryReactorClock()
++        connection = H2Connection(memoryReactor)
++        connection.callLater = memoryReactor.callLater
++        connection.requestFactory = DummyHTTPHandlerProxy
++
++        frameFactory = FrameFactory()
++        transport = StringTransport()
++
++        # Establish the connection.
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++
++        # Send a headers frame for a stream
++        streamID = 1
++        headersFrameData = frameFactory.buildHeadersFrame(
++            headers=self.postRequestHeaders, streamID=streamID
++        ).serialize()
++        connection.dataReceived(headersFrameData)
++
++        # Pause it and limit the number of outbound bytes to 1, so
++        # that a ProtocolError aborts the connection
++        connection.pauseProducing()
++        connection._maxBufferedControlFrameBytes = 0
++
++        # Remove anything sent by the preceding frames.
++        transport.clear()
++
++        # Abort the request.
++        connection.abortRequest(streamID)
++
++        # No RST_STREAM frame was sent...
++        self.assertFalse(transport.value())
++        # ...and the transport was disconnected (abortConnection was
++        # called)
++        self.assertTrue(transport.disconnected)
++
++
+ 
+ class HTTP2TransportChecking(unittest.TestCase, HTTP2TestHelpers):
+     getRequestHeaders = [
+@@ -2901,3 +3133,31 @@ class HTTP2TimeoutTests(unittest.TestCas
+         # transports, including TCP and TLS. We don't have anything we can
+         # assert on here: this just must not explode.
+         conn.connectionLost(error.ConnectionDone)
++
++
++    def test_timeOutClientThatSendsOnlyInvalidFrames(self):
++        """
++        A client that sends only invalid frames is eventually timed out.
++        """
++        memoryReactor = MemoryReactorClock()
++
++        connection = H2Connection(memoryReactor)
++        connection.callLater = memoryReactor.callLater
++        connection.timeOut = 60
++
++        frameFactory = FrameFactory()
++        transport = StringTransport()
++
++        clientConnectionPreface = frameFactory.clientConnectionPreface()
++        connection.makeConnection(transport)
++        connection.dataReceived(clientConnectionPreface)
++
++        # Send data until both the loseConnection and abortConnection
++       # timeouts have elapsed.
++        for _ in range(connection.timeOut + connection.abortTimeout):
++            connection.dataReceived(frameFactory.buildRstStreamFrame(1).serialize())
++            memoryReactor.advance(1)
++
++        # Invalid frames don't reset any timeouts, so the above has
++        # forcibly disconnected us via abortConnection.
++        self.assertTrue(transport.disconnected)
diff -Nru twisted-18.9.0/debian/patches/CVE-2020-1010x.patch twisted-18.9.0/debian/patches/CVE-2020-1010x.patch
--- twisted-18.9.0/debian/patches/CVE-2020-1010x.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2020-1010x.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,248 @@
+From 4a7d22e490bb8ff836892cc99a1f54b85ccb0281 Mon Sep 17 00:00:00 2001
+From: Mark Williams <mrw@enotuniq.org>
+Date: Sun, 16 Feb 2020 19:00:10 -0800
+Subject: [PATCH] Fix several request smuggling attacks.
+
+1. Requests with multiple Content-Length headers were allowed (thanks
+to Jake Miller from Bishop Fox and ZeddYu Lu) and now fail with a 400;
+
+2. Requests with a Content-Length header and a Transfer-Encoding
+header honored the first header (thanks to Jake Miller from Bishop
+Fox) and now fail with a 400;
+
+3. Requests whose Transfer-Encoding header had a value other than
+"chunked" and "identity" (thanks to ZeddYu Lu) were allowed and now fail
+with a 400.
+---
+ src/twisted/web/http.py                   |  64 +++++++---
+ src/twisted/web/newsfragments/9770.bugfix |   1 +
+ src/twisted/web/test/test_http.py         | 137 ++++++++++++++++++++++
+ 3 files changed, 187 insertions(+), 15 deletions(-)
+ create mode 100644 src/twisted/web/newsfragments/9770.bugfix
+
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2115,6 +2115,51 @@ class HTTPChannel(basic.LineReceiver, po
+         self.allContentReceived()
+         self._dataBuffer.append(data)
+ 
++    def _maybeChooseTransferDecoder(self, header, data):
++        """
++        If the provided header is C{content-length} or
++        C{transfer-encoding}, choose the appropriate decoder if any.
++
++        Returns L{True} if the request can proceed and L{False} if not.
++        """
++
++        def fail():
++            self._respondToBadRequestAndDisconnect()
++            self.length = None
++
++        # Can this header determine the length?
++        if header == b'content-length':
++            try:
++                length = int(data)
++            except ValueError:
++                fail()
++                return False
++            newTransferDecoder = _IdentityTransferDecoder(
++                length, self.requests[-1].handleContentChunk, self._finishRequestBody)
++        elif header == b'transfer-encoding':
++            # XXX Rather poorly tested code block, apparently only exercised by
++            # test_chunkedEncoding
++            if data.lower() == b'chunked':
++                length = None
++                newTransferDecoder = _ChunkedTransferDecoder(
++                    self.requests[-1].handleContentChunk, self._finishRequestBody)
++            elif data.lower() == b'identity':
++                return True
++            else:
++                fail()
++                return False
++        else:
++            # It's not a length related header, so exit
++            return True
++
++        if self._transferDecoder is not None:
++            fail()
++            return False
++        else:
++            self.length = length
++            self._transferDecoder = newTransferDecoder
++            return True
++
+ 
+     def headerReceived(self, line):
+         """
+@@ -2136,21 +2181,10 @@ class HTTPChannel(basic.LineReceiver, po
+ 
+         header = header.lower()
+         data = data.strip()
+-        if header == b'content-length':
+-            try:
+-                self.length = int(data)
+-            except ValueError:
+-                self._respondToBadRequestAndDisconnect()
+-                self.length = None
+-                return False
+-            self._transferDecoder = _IdentityTransferDecoder(
+-                self.length, self.requests[-1].handleContentChunk, self._finishRequestBody)
+-        elif header == b'transfer-encoding' and data.lower() == b'chunked':
+-            # XXX Rather poorly tested code block, apparently only exercised by
+-            # test_chunkedEncoding
+-            self.length = None
+-            self._transferDecoder = _ChunkedTransferDecoder(
+-                self.requests[-1].handleContentChunk, self._finishRequestBody)
++
++        if not self._maybeChooseTransferDecoder(header, data):
++            return False
++
+         reqHeaders = self.requests[-1].requestHeaders
+         values = reqHeaders.getRawHeaders(header)
+         if values is not None:
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -2018,6 +2018,143 @@ Hello,
+         self.flushLoggedErrors(AttributeError)
+ 
+ 
++    def assertDisconnectingBadRequest(self, request):
++        """
++        Assert that the given request bytes fail with a 400 bad
++        request without calling L{Request.process}.
++
++        @param request: A raw HTTP request
++        @type request: L{bytes}
++        """
++        class FailedRequest(http.Request):
++            processed = False
++            def process(self):
++                FailedRequest.processed = True
++
++        channel = self.runRequest(request, FailedRequest, success=False)
++        self.assertFalse(FailedRequest.processed, "Request.process called")
++        self.assertEqual(
++            channel.transport.value(),
++            b"HTTP/1.1 400 Bad Request\r\n\r\n")
++        self.assertTrue(channel.transport.disconnecting)
++
++
++    def test_duplicateContentLengths(self):
++        """
++        A request which includes multiple C{content-length} headers
++        fails with a 400 response without calling L{Request.process}.
++        """
++        self.assertRequestRejected([
++            b'GET /a HTTP/1.1',
++            b'Content-Length: 56',
++            b'Content-Length: 0',
++            b'Host: host.invalid',
++            b'',
++            b'',
++        ])
++
++
++    def test_duplicateContentLengthsWithPipelinedRequests(self):
++        """
++        Two pipelined requests, the first of which includes multiple
++        C{content-length} headers, trigger a 400 response without
++        calling L{Request.process}.
++        """
++        self.assertRequestRejected([
++            b'GET /a HTTP/1.1',
++            b'Content-Length: 56',
++            b'Content-Length: 0',
++            b'Host: host.invalid',
++            b'',
++            b'',
++            b'GET /a HTTP/1.1',
++            b'Host: host.invalid',
++            b'',
++            b'',
++        ])
++
++
++    def test_contentLengthAndTransferEncoding(self):
++        """
++        A request that includes both C{content-length} and
++        C{transfer-encoding} headers fails with a 400 response without
++        calling L{Request.process}.
++        """
++        self.assertRequestRejected([
++            b'GET /a HTTP/1.1',
++            b'Transfer-Encoding: chunked',
++            b'Content-Length: 0',
++            b'Host: host.invalid',
++            b'',
++            b'',
++        ])
++
++
++    def test_contentLengthAndTransferEncodingWithPipelinedRequests(self):
++        """
++        Two pipelined requests, the first of which includes both
++        C{content-length} and C{transfer-encoding} headers, triggers a
++        400 response without calling L{Request.process}.
++        """
++        self.assertRequestRejected([
++            b'GET /a HTTP/1.1',
++            b'Transfer-Encoding: chunked',
++            b'Content-Length: 0',
++            b'Host: host.invalid',
++            b'',
++            b'',
++            b'GET /a HTTP/1.1',
++            b'Host: host.invalid',
++            b'',
++            b'',
++        ])
++
++
++    def test_unknownTransferEncoding(self):
++        """
++        A request whose C{transfer-encoding} header includes a value
++        other than C{chunked} or C{identity} fails with a 400 response
++        without calling L{Request.process}.
++        """
++        self.assertRequestRejected([
++            b'GET /a HTTP/1.1',
++            b'Transfer-Encoding: unknown',
++            b'Host: host.invalid',
++            b'',
++            b'',
++        ])
++
++
++    def test_transferEncodingIdentity(self):
++        """
++        A request with a valid C{content-length} and a
++        C{transfer-encoding} whose value is C{identity} succeeds.
++        """
++        body = []
++
++        class SuccessfulRequest(http.Request):
++            processed = False
++            def process(self):
++                body.append(self.content.read())
++                self.setHeader(b'content-length', b'0')
++                self.finish()
++
++        request = b'''\
++GET / HTTP/1.1
++Host: host.invalid
++Content-Length: 2
++Transfer-Encoding: identity
++
++ok
++'''
++        channel = self.runRequest(request, SuccessfulRequest, False)
++        self.assertEqual(body, [b'ok'])
++        self.assertEqual(
++            channel.transport.value(),
++            b'HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n',
++        )
++
++
+ 
+ class QueryArgumentsTests(unittest.TestCase):
+     def testParseqs(self):
diff -Nru twisted-18.9.0/debian/patches/CVE-2020-1010x-pre1.patch twisted-18.9.0/debian/patches/CVE-2020-1010x-pre1.patch
--- twisted-18.9.0/debian/patches/CVE-2020-1010x-pre1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2020-1010x-pre1.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,135 @@
+Backport of:
+
+From d2f6dd9b3766509f40c980aac67ca8475da67c6f Mon Sep 17 00:00:00 2001
+From: Tom Most <twm@freecog.net>
+Date: Mon, 3 Jun 2019 22:03:22 -0700
+Subject: [PATCH] Refactor to reduce duplication
+
+---
+ src/twisted/web/test/test_http.py | 116 +++++++++++-------------------
+ 1 file changed, 42 insertions(+), 74 deletions(-)
+
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1370,7 +1370,8 @@ class ParsingTests(unittest.TestCase):
+         """
+         Execute a web request based on plain text content.
+ 
+-        @param httpRequest: Content for the request which is processed.
++        @param httpRequest: Content for the request which is processed. Each
++            L{"\n"} will be replaced with L{"\r\n"}.
+         @type httpRequest: C{bytes}
+ 
+         @param requestFactory: 2-argument callable returning a Request.
+@@ -1409,6 +1410,32 @@ class ParsingTests(unittest.TestCase):
+         return channel
+ 
+ 
++    def assertRequestRejected(self, requestLines):
++        """
++        Execute a HTTP request and assert that it is rejected with a 400 Bad
++        Response and disconnection.
++
++        @param requestLines: Plain text lines of the request. These lines will
++            be joined with newlines to form the HTTP request that is processed.
++        @type requestLines: C{list} of C{bytes}
++        """
++        httpRequest = b"\n".join(requestLines)
++        processed = []
++
++        class MyRequest(http.Request):
++            def process(self):
++                processed.append(self)
++                self.finish()
++
++        channel = self.runRequest(httpRequest, MyRequest, success=False)
++        self.assertEqual(
++            channel.transport.value(),
++            b"HTTP/1.1 400 Bad Request\r\n\r\n",
++        )
++        self.assertTrue(channel.transport.disconnecting)
++        self.assertEqual(processed, [])
++
++
+     def test_invalidNonAsciiMethod(self):
+         """
+         When client sends invalid HTTP method containing
+@@ -1478,45 +1505,24 @@ class ParsingTests(unittest.TestCase):
+ 
+     def test_tooManyHeaders(self):
+         """
+-        L{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the
++        C{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the
+         number of headers received per request.
+         """
+-        processed = []
+-        class MyRequest(http.Request):
+-            def process(self):
+-                processed.append(self)
+-
+         requestLines = [b"GET / HTTP/1.0"]
+         for i in range(http.HTTPChannel.maxHeaders + 2):
+             requestLines.append(networkString("%s: foo" % (i,)))
+         requestLines.extend([b"", b""])
+ 
+-        channel = self.runRequest(b"\n".join(requestLines), MyRequest, 0)
+-        self.assertEqual(processed, [])
+-        self.assertEqual(
+-            channel.transport.value(),
+-            b"HTTP/1.1 400 Bad Request\r\n\r\n")
++        self.assertRequestRejected(requestLines)
+ 
+ 
+     def test_invalidContentLengthHeader(self):
+         """
+-        If a Content-Length header with a non-integer value is received, a 400
+-        (Bad Request) response is sent to the client and the connection is
+-        closed.
++        If a I{Content-Length} header with a non-integer value is received,
++        a 400 (Bad Request) response is sent to the client and the connection
++        is closed.
+         """
+-        processed = []
+-        class MyRequest(http.Request):
+-            def process(self):
+-                processed.append(self)
+-                self.finish()
+-
+-        requestLines = [b"GET / HTTP/1.0", b"Content-Length: x", b"", b""]
+-        channel = self.runRequest(b"\n".join(requestLines), MyRequest, 0)
+-        self.assertEqual(
+-            channel.transport.value(),
+-            b"HTTP/1.1 400 Bad Request\r\n\r\n")
+-        self.assertTrue(channel.transport.disconnecting)
+-        self.assertEqual(processed, [])
++        self.assertRequestRejected([b"GET / HTTP/1.0", b"Content-Length: x", b"", b""])
+ 
+ 
+     def test_invalidHeaderNoColon(self):
+@@ -1524,24 +1530,12 @@ class ParsingTests(unittest.TestCase):
+         If a header without colon is received a 400 (Bad Request) response
+         is sent to the client and the connection is closed.
+         """
+-        processed = []
+-        class MyRequest(http.Request):
+-            def process(self):
+-                processed.append(self)
+-                self.finish()
+-
+-        requestLines = [b"GET / HTTP/1.0", b"HeaderName ", b"", b""]
+-        channel = self.runRequest(b"\n".join(requestLines), MyRequest, 0)
+-        self.assertEqual(
+-            channel.transport.value(),
+-            b"HTTP/1.1 400 Bad Request\r\n\r\n")
+-        self.assertTrue(channel.transport.disconnecting)
+-        self.assertEqual(processed, [])
++        self.assertRequestRejected([b"GET / HTTP/1.0", b"HeaderName ", b"", b""])
+ 
+ 
+     def test_headerLimitPerRequest(self):
+         """
+-        L{HTTPChannel} enforces the limit of C{HTTPChannel.maxHeaders} per
++        C{HTTPChannel} enforces the limit of C{HTTPChannel.maxHeaders} per
+         request so that headers received in an earlier request do not count
+         towards the limit when processing a later request.
+         """
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-10.patch twisted-18.9.0/debian/patches/CVE-2022-21712-10.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-10.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-10.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,29 @@
+From 0c44b4806a27d258baf13d6f714f06eddb28da5a Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 15:31:51 -0800
+Subject: [PATCH] correct docstring to suggest the right order
+
+---
+ src/twisted/web/iweb.py | 10 +++++-----
+ 1 file changed, 5 insertions(+), 5 deletions(-)
+
+--- a/src/twisted/web/iweb.py
++++ b/src/twisted/web/iweb.py
+@@ -682,12 +682,12 @@ class IAgent(Interface):
+     obtained by combining a number of (hypothetical) implementations::
+ 
+         baseAgent = Agent(reactor)
+-        redirect = BrowserLikeRedirectAgent(baseAgent, limit=10)
++        decode = ContentDecoderAgent(baseAgent, [(b"gzip", GzipDecoder())])
++        cookie = CookieAgent(decode, diskStore.cookie)
+         authenticate = AuthenticateAgent(
+-            redirect, [diskStore.credentials, GtkAuthInterface()])
+-        cookie = CookieAgent(authenticate, diskStore.cookie)
+-        decode = ContentDecoderAgent(cookie, [(b"gzip", GzipDecoder())])
+-        cache = CacheAgent(decode, diskStore.cache)
++            cookie, [diskStore.credentials, GtkAuthInterface()])
++        cache = CacheAgent(authenticate, diskStore.cache)
++        redirect = BrowserLikeRedirectAgent(cache, limit=10)
+ 
+         doSomeRequests(cache)
+     """
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-1.patch twisted-18.9.0/debian/patches/CVE-2022-21712-1.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-1.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,262 @@
+[PATCH 1/10]: Backport of the following upstream patch
+From eda4f1e2ec9988a142de244f1a2b285939718c03 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 12:57:49 -0800
+Subject: [PATCH] failing test for header data leak
+
+---
+ src/twisted/web/test/test_agent.py | 154 +++++++++++++++++++++++------
+ 1 file changed, 123 insertions(+), 31 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -5,12 +5,18 @@
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
+ 
+-import zlib
+-
+ from io import BytesIO
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import (
++    AccumulatingProtocol,
++    EventLoggingObserver,
++    MemoryReactorClock,
++    StringTransport,
++)
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
++from unittest import skipIf, SkipTest
+ 
+-from zope.interface.verify import verifyObject
+-
++import zlib
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+ from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
+@@ -20,8 +26,6 @@ from twisted.internet import defer, task
+ from twisted.python.failure import Failure
+ from twisted.python.compat import cookielib, intToBytes
+ from twisted.python.components import proxyForInterface
+-from twisted.test.proto_helpers import (StringTransport, MemoryReactorClock,
+-                                        EventLoggingObserver)
+ from twisted.internet.task import Clock
+ from twisted.internet.error import ConnectionRefusedError, ConnectionDone
+ from twisted.internet.error import ConnectionLost
+@@ -30,32 +34,42 @@ from twisted.internet.defer import Defer
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+ from twisted.internet.address import IPv4Address, IPv6Address
+ 
+-from twisted.web.client import (FileBodyProducer, Request, HTTPConnectionPool,
+-                                ResponseDone, _HTTP11ClientFactory, URI)
+-
++from twisted.web.client import (
++    BrowserLikePolicyForHTTPS,
++    FileBodyProducer,
++    HTTPConnectionPool,
++    Request,
++    ResponseDone,
++    URI,
++    _HTTP11ClientFactory,
++)
+ from twisted.web.iweb import (
+-    UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse, IAgentEndpointFactory,
+-    )
++    IAgent,
++    IAgentEndpointFactory,
++    IBodyProducer,
++    IPolicyForHTTPS,
++    IRequest,
++    IResponse,
++    UNKNOWN_LENGTH,
++)
++
+ from twisted.web.http_headers import Headers
+ from twisted.web._newclient import HTTP11ClientProtocol, Response
+ 
+ from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
+-from zope.interface.declarations import implementer
+-from twisted.web.iweb import IPolicyForHTTPS
+ from twisted.python.deprecate import getDeprecationWarningString
+ from incremental import Version
+-from twisted.web.client import BrowserLikePolicyForHTTPS
+ from twisted.internet.test.test_endpoints import deterministicResolvingReactor
+ from twisted.internet.endpoints import HostnameEndpoint
+-from twisted.test.proto_helpers import AccumulatingProtocol
+-from twisted.test.iosim import IOPump, FakeTransport
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+ from twisted.web.test.injectionhelpers import (
+     MethodInjectionTestsMixin,
+     URIInjectionTestsMixin,
+ )
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
++
+ 
+ try:
+     from twisted.internet import ssl
+@@ -2606,8 +2620,21 @@ class ProxyAgentTests(TestCase, FakeReac
+         self.assertEqual(agent._pool.connected, True)
+ 
+ 
++SENSITIVE_HEADERS = [
++    b"authorization",
++    b"cookie",
++    b"cookie2",
++    b"proxy-authorization",
++    b"www-authenticate",
++]
+ 
+-class _RedirectAgentTestsMixin(object):
++if False:  # TYPE_CHECKING
++    testMixinClass = TestCase
++else:
++    testMixinClass = object
++
++
++class _RedirectAgentTestsMixin(testMixinClass):
+     """
+     Test cases mixin for L{RedirectAgentTests} and
+     L{BrowserLikeRedirectAgentTests}.
+@@ -2631,33 +2658,52 @@ class _RedirectAgentTestsMixin(object):
+         self.assertIdentical(result.previousResponse, None)
+ 
+ 
+-    def _testRedirectDefault(self, code):
++    def _testRedirectDefault(
++        self,
++        code,
++        crossScheme = False,
++        crossDomain = False,
++        requestHeaders = None,
++    ):
+         """
+         When getting a redirect, L{client.RedirectAgent} follows the URL
+         specified in the L{Location} header field and make a new request.
+ 
+         @param code: HTTP status code.
+         """
+-        self.agent.request(b'GET', b'http://example.com/foo')
++        startDomain = b"example.com"
++        startScheme = b"https" if ssl is not None else b"http"
++        startPort = 80 if startScheme == b"http" else 443
++        self.agent.request(
++            b"GET", startScheme + b"://" + startDomain + b"/foo", headers=requestHeaders
++        )
+ 
+         host, port = self.reactor.tcpClients.pop()[:2]
+         self.assertEqual(EXAMPLE_COM_IP, host)
+-        self.assertEqual(80, port)
++        self.assertEqual(startPort, port)
+ 
+         req, res = self.protocol.requests.pop()
+ 
+-        # If possible (i.e.: SSL support is present), run the test with a
++        # If possible (i.e.: TLS support is present), run the test with a
+         # cross-scheme redirect to verify that the scheme is honored; if not,
+         # let's just make sure it works at all.
+-        if ssl is None:
+-            scheme = b'http'
+-            expectedPort = 80
+-        else:
+-            scheme = b'https'
+-            expectedPort = 443
+ 
++        targetScheme = startScheme
++        targetDomain = startDomain
++        targetPort = startPort
++
++        if crossDomain:
++            if ssl is None:
++                raise SkipTest(
++                    "Cross-scheme redirects can't be tested without TLS support."
++                )
++            targetScheme = b"https" if startScheme == b"http" else b"https"
++            targetPort = 443 if startPort == 80 else 443
++
++        targetDomain = b"example.net" if crossDomain else startDomain
+         headers = http_headers.Headers(
+-            {b'location': [scheme + b'://example.com/bar']})
++            {b"location": [targetScheme + b"://" + targetDomain + b"/bar"]}
++        )        
+         response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
+         res.callback(response)
+ 
+@@ -2666,8 +2712,9 @@ class _RedirectAgentTestsMixin(object):
+         self.assertEqual(b'/bar', req2.uri)
+ 
+         host, port = self.reactor.tcpClients.pop()[:2]
+-        self.assertEqual(EXAMPLE_COM_IP, host)
+-        self.assertEqual(expectedPort, port)
++        self.assertEqual(EXAMPLE_NET_IP if crossDomain else EXAMPLE_COM_IP, host)
++        self.assertEqual(targetPort, port)
++        return req2
+ 
+ 
+     def test_redirect301(self):
+@@ -2677,6 +2724,15 @@ class _RedirectAgentTestsMixin(object):
+         self._testRedirectDefault(301)
+ 
+ 
++    def test_redirect301Scheme(self):
++        """
++        L{client.RedirectAgent} follows cross-scheme redirects.
++        """
++        self._testRedirectDefault(
++            301,
++            crossScheme=True,
++        )
++
+     def test_redirect302(self):
+         """
+         L{client.RedirectAgent} follows redirects on status code 302.
+@@ -2691,6 +2747,48 @@ class _RedirectAgentTestsMixin(object):
+         self._testRedirectDefault(307)
+ 
+ 
++    def test_headerSecurity(self):
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting.
++        """
++        sensitiveHeaderValues = {
++            b"authorization": [b"sensitive-authnz"],
++            b"cookie": [b"sensitive-cookie-data"],
++            b"cookie2": [b"sensitive-cookie2-data"],
++            b"proxy-authorization": [b"sensitive-proxy-auth"],
++            b"wWw-auThentiCate": [b"sensitive-authn"],
++        }
++        otherHeaderValues = {b"x-random-header": [b"x-random-value"]}
++        soh = sensitiveHeaderValues.copy()
++        soh.update(otherHeaderValues)
++        allHeaders = Headers(soh)
++        redirected = self._testRedirectDefault(301, requestHeaders=allHeaders)
++
++        def normHeaders(headers):
++            return {k.lower(): v for (k, v) in headers.getAllRawHeaders()}
++
++        sameOriginHeaders = normHeaders(redirected.headers)
++        t = {b"host": [b"example.com"]}
++        t.update(normHeaders(allHeaders))
++        self.assertEquals(
++            sameOriginHeaders,
++            t,
++        )
++
++        redirectedElsewhere = self._testRedirectDefault(
++            301,
++            crossDomain=True,
++            requestHeaders=Headers(soh),
++        )
++        otherOriginHeaders = normHeaders(redirectedElsewhere.headers)
++        t = {b"host": [b"example.com"]}
++        t.update(normHeaders(Headers(otherHeaderValues)))
++        self.assertEquals(
++            otherOriginHeaders,
++            t,
++        )
++
++
+     def _testRedirectToGet(self, code, method):
+         """
+         L{client.RedirectAgent} changes the method to I{GET} when getting
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-2.patch twisted-18.9.0/debian/patches/CVE-2022-21712-2.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-2.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-2.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,21 @@
+[PATCH 2/10]: Backport of the following upstream patch
+From ecc2ae81c831e58cf1725dfe2e5b6d2951c884a0 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 13:00:02 -0800
+Subject: [PATCH] assert on correct host
+
+---
+ src/twisted/web/test/test_agent.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -2781,7 +2781,7 @@
+             requestHeaders=Headers(soh),
+         )
+         otherOriginHeaders = normHeaders(redirectedElsewhere.headers)
+-        t = {b"host": [b"example.com"]}
++        t = {b"host": [b"example.net"]}
+         t.update(normHeaders(Headers(otherHeaderValues)))
+         self.assertEquals(
+             otherOriginHeaders,
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-3.patch twisted-18.9.0/debian/patches/CVE-2022-21712-3.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-3.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-3.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,235 @@
+From c1923d24b6a2752ea5d5686851427e0ec5757543 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:04:27 -0800
+Subject: [PATCH] tests for domain/port/scheme and fix
+
+---
+ src/twisted/web/client.py          | 61 +++++++++++++++++++++---------
+ src/twisted/web/test/test_agent.py | 52 ++++++++++++++++++++-----
+ 2 files changed, 86 insertions(+), 27 deletions(-)
+
+--- a/src/twisted/web/client.py
++++ b/src/twisted/web/client.py
+@@ -24,16 +24,12 @@
+ import zlib
+ from functools import wraps
+ 
+-from zope.interface import implementer
+-
+ from twisted.python.compat import _PY3, networkString
+ from twisted.python.compat import nativeString, intToBytes, unicode, itervalues
+-from twisted.python.deprecate import deprecatedModuleAttribute, deprecated
++from twisted.python.deprecate import deprecated, deprecatedModuleAttribute, getDeprecationWarningString
+ from twisted.python.failure import Failure
+ from incremental import Version
+ 
+-from twisted.web.iweb import IPolicyForHTTPS, IAgentEndpointFactory
+-from twisted.python.deprecate import getDeprecationWarningString
+ from twisted.web import http
+ from twisted.internet import defer, protocol, task, reactor
+ from twisted.internet.abstract import isIPv6Address
+@@ -42,7 +38,9 @@
+ from twisted.python.util import InsensitiveDict
+ from twisted.python.components import proxyForInterface
+ from twisted.web import error
+-from twisted.web.iweb import UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse
++from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IResponse, UNKNOWN_LENGTH
++from zope.interface import implementer
++
+ from twisted.web.http_headers import Headers
+ from twisted.logger import Logger
+ 
+@@ -2012,6 +2010,14 @@
+         return response
+ 
+ 
++_canonicalHeaderName = Headers()._canonicalNameCaps
++_defaultSensitiveHeaders = frozenset([
++    b"Authorization",
++    b"Cookie",
++    b"Cookie2",
++    b"Proxy-Authorization",
++    b"WWW-Authenticate",
++])
+ 
+ @implementer(IAgent)
+ class RedirectAgent(object):
+@@ -2027,6 +2033,11 @@
+     @param redirectLimit: The maximum number of times the agent is allowed to
+         follow redirects before failing with a L{error.InfiniteRedirection}.
+ 
++    @param sensitiveHeaderNames: An iterable of C{bytes} enumerating the names
++        of headers that must not be transmitted when redirecting to a different
++        origins.  These will be consulted in addition to the protocol-specified
++        set of headers that contain sensitive information.
++
+     @cvar _redirectResponses: A L{list} of HTTP status codes to be redirected
+         for I{GET} and I{HEAD} methods.
+ 
+@@ -2041,9 +2052,17 @@
+     _seeOtherResponses = [http.SEE_OTHER]
+ 
+ 
+-    def __init__(self, agent, redirectLimit=20):
++    def __init__(
++        self,
++        agent,
++        redirectLimit = 20,
++        sensitiveHeaderNames = (),
++    ):
+         self._agent = agent
+         self._redirectLimit = redirectLimit
++        sensitive = set(_canonicalHeaderName(each) for each in sensitiveHeaderNames)
++        sensitive.update(_defaultSensitiveHeaders)
++        self._sensitiveHeaderNames = sensitive
+ 
+ 
+     def request(self, method, uri, headers=None, bodyProducer=None):
+@@ -2090,6 +2109,22 @@
+                 response.code, b'No location header field', uri)
+             raise ResponseFailed([Failure(err)], response)
+         location = self._resolveLocation(uri, locationHeaders[0])
++        if headers:
++            parsedURI = URI.fromBytes(uri)
++            parsedLocation = URI.fromBytes(location)
++            sameOrigin = (
++                (parsedURI.scheme == parsedLocation.scheme)
++                and (parsedURI.host == parsedLocation.host)
++                and (parsedURI.port == parsedLocation.port)
++            )
++            if not sameOrigin:
++                headers = Headers(
++                    {
++                        rawName: rawValue
++                        for rawName, rawValue in headers.getAllRawHeaders()
++                        if rawName not in self._sensitiveHeaderNames
++                    }
++                )
+         deferred = self._agent.request(method, location, headers)
+         def _chainResponse(newResponse):
+             newResponse.setPreviousResponse(response)
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -2663,6 +2663,7 @@
+         code,
+         crossScheme = False,
+         crossDomain = False,
++        crossPort = False,
+         requestHeaders = None,
+     ):
+         """
+@@ -2692,17 +2693,22 @@
+         targetDomain = startDomain
+         targetPort = startPort
+ 
+-        if crossDomain:
++        if crossScheme:            
+             if ssl is None:
+                 raise SkipTest(
+                     "Cross-scheme redirects can't be tested without TLS support."
+                 )
+-            targetScheme = b"https" if startScheme == b"http" else b"https"
+-            targetPort = 443 if startPort == 80 else 443
+-
++            targetScheme = b"https" if startScheme == b"http" else b"http"
++            targetPort = 443 if startPort == 80 else 80
++ 
++        portSyntax = b''
++        if crossPort:
++            targetPort = 8443
++            portSyntax = b':8443'
+         targetDomain = b"example.net" if crossDomain else startDomain
++        locationValue = targetScheme + b"://" + targetDomain + portSyntax + b"/bar"
+         headers = http_headers.Headers(
+-            {b"location": [targetScheme + b"://" + targetDomain + b"/bar"]}
++            {b"location": [locationValue]}
+         )        
+         response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
+         res.callback(response)
+@@ -2747,9 +2753,10 @@
+         self._testRedirectDefault(307)
+ 
+ 
+-    def test_headerSecurity(self):
++    def _sensitiveHeadersTest(self, expectedHostHeader = b"example.com", **crossKwargs):
+         """
+-        L{client.RedirectAgent} scrubs sensitive headers when redirecting.
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing origins.
+         """
+         sensitiveHeaderValues = {
+             b"authorization": [b"sensitive-authnz"],
+@@ -2757,6 +2764,7 @@
+             b"cookie2": [b"sensitive-cookie2-data"],
+             b"proxy-authorization": [b"sensitive-proxy-auth"],
+             b"wWw-auThentiCate": [b"sensitive-authn"],
++            b"x-custom-sensitive": [b"sensitive-custom"],
+         }
+         otherHeaderValues = {b"x-random-header": [b"x-random-value"]}
+         soh = sensitiveHeaderValues.copy()
+@@ -2777,17 +2785,37 @@
+ 
+         redirectedElsewhere = self._testRedirectDefault(
+             301,
+-            crossDomain=True,
+             requestHeaders=Headers(soh),
++            **crossKwargs
+         )
+         otherOriginHeaders = normHeaders(redirectedElsewhere.headers)
+-        t = {b"host": [b"example.net"]}
++        t = {b"host": [expectedHostHeader]}
+         t.update(normHeaders(Headers(otherHeaderValues)))
+         self.assertEquals(
+             otherOriginHeaders,
+             t,
+         )
+ 
++    def test_crossDomainHeaders(self):
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing domains.
++        """
++        self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b'example.net')
++
++    def test_crossPortHeaders(self):
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing ports.
++        """
++        self._sensitiveHeadersTest(crossPort=True, expectedHostHeader=b'example.com:8443')
++
++    def test_crossSchemeHeaders(self):
++        """
++        L{client.RedirectAgent} scrubs sensitive headers when redirecting
++        between differing schemes.
++        """
++        self._sensitiveHeadersTest(crossScheme=True)
+ 
+     def _testRedirectToGet(self, code, method):
+         """
+@@ -3014,8 +3042,10 @@
+         @return: a new L{twisted.web.client.RedirectAgent}
+         """
+         return client.RedirectAgent(
+-            self.buildAgentForWrapperTest(self.reactor))
+-
++            self.buildAgentForWrapperTest(self.reactor),
++            sensitiveHeaderNames=[b"X-Custom-sensitive"],
++        )
++         
+ 
+     def setUp(self):
+         self.reactor = self.createReactor()
+@@ -3053,8 +3083,10 @@
+         @return: a new L{twisted.web.client.BrowserLikeRedirectAgent}
+         """
+         return client.BrowserLikeRedirectAgent(
+-            self.buildAgentForWrapperTest(self.reactor))
+-
++            self.buildAgentForWrapperTest(self.reactor),
++            sensitiveHeaderNames=[b"x-Custom-sensitive"],
++        )
++            
+ 
+     def setUp(self):
+         self.reactor = self.createReactor()
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-4.patch twisted-18.9.0/debian/patches/CVE-2022-21712-4.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-4.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-4.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,15 @@
+From 562dcff9c7dd846857932443a0a2935f74c2a2de Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:11:46 -0800
+Subject: [PATCH] topfile
+
+---
+ src/twisted/topfiles/10294.bugfix | 1 +
+ 1 file changed, 1 insertion(+)
+ create mode 100644 src/twisted/topfiles/10294.bugfix
+
+--- /dev/null
++++ b/src/twisted/topfiles/10294.bugfix
+@@ -0,0 +1 @@
++twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin.
+\ No newline at end of file
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-5.patch twisted-18.9.0/debian/patches/CVE-2022-21712-5.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-5.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-5.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,218 @@
+From 4ae6732a30893178891ee3a383d8a737e0ec21eb Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:16:18 -0800
+Subject: [PATCH] reblackening
+
+---
+ src/twisted/web/client.py          | 41 +++++++++++++++--------
+ src/twisted/web/test/test_agent.py | 52 ++++++++++++++++--------------
+ 2 files changed, 55 insertions(+), 38 deletions(-)
+
+--- a/src/twisted/web/client.py
++++ b/src/twisted/web/client.py
+@@ -10,6 +10,7 @@
+ 
+ import os
+ import warnings
++import zlib
+ 
+ try:
+     from urlparse import urlunparse, urljoin, urldefrag
+@@ -21,12 +22,17 @@
+         result = _urlunparse(tuple([p.decode("charmap") for p in parts]))
+         return result.encode("charmap")
+ 
+-import zlib
++from zope.interface import implementer
++
+ from functools import wraps
+ 
+ from twisted.python.compat import _PY3, networkString
+ from twisted.python.compat import nativeString, intToBytes, unicode, itervalues
+-from twisted.python.deprecate import deprecated, deprecatedModuleAttribute, getDeprecationWarningString
++from twisted.python.deprecate import (
++    deprecated,
++    deprecatedModuleAttribute,
++    getDeprecationWarningString,
++)
+ from twisted.python.failure import Failure
+ from incremental import Version
+ 
+@@ -38,12 +44,17 @@
+ from twisted.python.util import InsensitiveDict
+ from twisted.python.components import proxyForInterface
+ from twisted.web import error
+-from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IResponse, UNKNOWN_LENGTH
+-from zope.interface import implementer
++from twisted.web.iweb import (
++    UNKNOWN_LENGTH,
++    IAgent,
++    IAgentEndpointFactory,
++    IBodyProducer,
++    IPolicyForHTTPS,
++    IResponse,
++)
+ 
+ from twisted.web.http_headers import Headers
+ from twisted.logger import Logger
+-
+ from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
+ 
+ 
+@@ -2011,13 +2022,16 @@
+ 
+ 
+ _canonicalHeaderName = Headers()._canonicalNameCaps
+-_defaultSensitiveHeaders = frozenset([
+-    b"Authorization",
+-    b"Cookie",
+-    b"Cookie2",
+-    b"Proxy-Authorization",
+-    b"WWW-Authenticate",
+-])
++_defaultSensitiveHeaders = frozenset(
++    [
++        b"Authorization",
++        b"Cookie",
++        b"Cookie2",
++        b"Proxy-Authorization",
++        b"WWW-Authenticate",
++    ]
++)
++
+ 
+ @implementer(IAgent)
+ class RedirectAgent(object):
+@@ -2060,7 +2074,7 @@
+     ):
+         self._agent = agent
+         self._redirectLimit = redirectLimit
+-        sensitive = set(_canonicalHeaderName(each) for each in sensitiveHeaderNames)
++        sensitive = {_canonicalHeaderName(each) for each in sensitiveHeaderNames}
+         sensitive.update(_defaultSensitiveHeaders)
+         self._sensitiveHeaderNames = sensitive
+ 
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -4,8 +4,20 @@
+ """
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
+-
++import zlib
+ from io import BytesIO
++from unittest import SkipTest, skipIf
++
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
++
++from twisted.trial.unittest import TestCase, SynchronousTestCase
++from twisted.web import client, error, http_headers
++from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
++from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
++from twisted.web._newclient import PotentialDataLoss
++from twisted.internet import defer, task
++from twisted.python.failure import Failure
+ from twisted.test.iosim import FakeTransport, IOPump
+ from twisted.test.proto_helpers import (
+     AccumulatingProtocol,
+@@ -14,16 +27,7 @@
+     StringTransport,
+ )
+ from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+-from unittest import skipIf, SkipTest
+ 
+-import zlib
+-from twisted.trial.unittest import TestCase, SynchronousTestCase
+-from twisted.web import client, error, http_headers
+-from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
+-from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
+-from twisted.web._newclient import PotentialDataLoss
+-from twisted.internet import defer, task
+-from twisted.python.failure import Failure
+ from twisted.python.compat import cookielib, intToBytes
+ from twisted.python.components import proxyForInterface
+ from twisted.internet.task import Clock
+@@ -35,22 +39,22 @@
+ from twisted.internet.address import IPv4Address, IPv6Address
+ 
+ from twisted.web.client import (
++    URI,
+     BrowserLikePolicyForHTTPS,
+     FileBodyProducer,
+     HTTPConnectionPool,
+     Request,
+     ResponseDone,
+-    URI,
+     _HTTP11ClientFactory,
+ )
+ from twisted.web.iweb import (
++    UNKNOWN_LENGTH,
+     IAgent,
+     IAgentEndpointFactory,
+     IBodyProducer,
+     IPolicyForHTTPS,
+     IRequest,
+     IResponse,
+-    UNKNOWN_LENGTH,
+ )
+ 
+ from twisted.web.http_headers import Headers
+@@ -67,8 +71,6 @@
+ )
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
+-from zope.interface.declarations import implementer
+-from zope.interface.verify import verifyObject
+ 
+ 
+ try:
+@@ -2701,15 +2703,13 @@
+             targetScheme = b"https" if startScheme == b"http" else b"http"
+             targetPort = 443 if startPort == 80 else 80
+  
+-        portSyntax = b''
++        portSyntax = b""
+         if crossPort:
+             targetPort = 8443
+-            portSyntax = b':8443'
++            portSyntax = b":8443"
+         targetDomain = b"example.net" if crossDomain else startDomain
+         locationValue = targetScheme + b"://" + targetDomain + portSyntax + b"/bar"
+-        headers = http_headers.Headers(
+-            {b"location": [locationValue]}
+-        )        
++        headers = http_headers.Headers({b"location": [locationValue]})
+         response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
+         res.callback(response)
+ 
+@@ -2753,7 +2753,9 @@
+         self._testRedirectDefault(307)
+ 
+ 
+-    def _sensitiveHeadersTest(self, expectedHostHeader = b"example.com", **crossKwargs):
++    def _sensitiveHeadersTest(
++        self, expectedHostHeader = b"example.com", **crossKwargs
++    ):
+         """
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+         between differing origins.
+@@ -2801,14 +2803,16 @@
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+         between differing domains.
+         """
+-        self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b'example.net')
++        self._sensitiveHeadersTest(crossDomain=True, expectedHostHeader=b"example.net")
+ 
+     def test_crossPortHeaders(self):
+         """
+         L{client.RedirectAgent} scrubs sensitive headers when redirecting
+         between differing ports.
+         """
+-        self._sensitiveHeadersTest(crossPort=True, expectedHostHeader=b'example.com:8443')
++        self._sensitiveHeadersTest(
++            crossPort=True, expectedHostHeader=b"example.com:8443"
++        )
+ 
+     def test_crossSchemeHeaders(self):
+         """
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-6.patch twisted-18.9.0/debian/patches/CVE-2022-21712-6.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-6.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-6.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,20 @@
+From 8a4b1f3e0e7d2316944064960d9dcb4556525845 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:16:48 -0800
+Subject: [PATCH] oops
+
+---
+ src/twisted/{topfiles => newsfragments}/10294.bugfix | 0
+ 1 file changed, 0 insertions(+), 0 deletions(-)
+ rename src/twisted/{topfiles => newsfragments}/10294.bugfix (100%)
+
+--- /dev/null
++++ b/src/twisted/newsfragments/10294.bugfix
+@@ -0,0 +1 @@
++twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin.
+\ No newline at end of file
+--- a/src/twisted/topfiles/10294.bugfix
++++ /dev/null
+@@ -1 +0,0 @@
+-twisted.web.client.RedirectAgent and twisted.web.client.BrowserLikeRedirectAgent now properly remove sensitive headers when redirecting to a different origin.
+\ No newline at end of file
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-7.patch twisted-18.9.0/debian/patches/CVE-2022-21712-7.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-7.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-7.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,142 @@
+From 9d53c0f8b38340fe5ca3fa1a7df49347c2dea18a Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:37:22 -0800
+Subject: [PATCH] tell mypy enough that it can actually find bugs
+
+---
+ src/twisted/web/test/test_agent.py | 88 +++++++++---------------------
+ 1 file changed, 27 insertions(+), 61 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -4,60 +4,28 @@
+ """
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
+-import zlib
+ from io import BytesIO
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import AccumulatingProtocol, EventLoggingObserver, MemoryReactorClock, StringTransport
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+ from unittest import SkipTest, skipIf
+ 
+-from zope.interface.declarations import implementer
+-from zope.interface.verify import verifyObject
+-
++import zlib
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+-from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
+-from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
+-from twisted.web._newclient import PotentialDataLoss
+ from twisted.internet import defer, task
+ from twisted.python.failure import Failure
+-from twisted.test.iosim import FakeTransport, IOPump
+-from twisted.test.proto_helpers import (
+-    AccumulatingProtocol,
+-    EventLoggingObserver,
+-    MemoryReactorClock,
+-    StringTransport,
+-)
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+ 
+ from twisted.python.compat import cookielib, intToBytes
+ from twisted.python.components import proxyForInterface
+ from twisted.internet.task import Clock
+-from twisted.internet.error import ConnectionRefusedError, ConnectionDone
+-from twisted.internet.error import ConnectionLost
++from twisted.internet.error import ConnectionDone, ConnectionLost, ConnectionRefusedError
+ from twisted.internet.protocol import Protocol, Factory
+ from twisted.internet.defer import Deferred, succeed, CancelledError
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+ from twisted.internet.address import IPv4Address, IPv6Address
+ 
+-from twisted.web.client import (
+-    URI,
+-    BrowserLikePolicyForHTTPS,
+-    FileBodyProducer,
+-    HTTPConnectionPool,
+-    Request,
+-    ResponseDone,
+-    _HTTP11ClientFactory,
+-)
+-from twisted.web.iweb import (
+-    UNKNOWN_LENGTH,
+-    IAgent,
+-    IAgentEndpointFactory,
+-    IBodyProducer,
+-    IPolicyForHTTPS,
+-    IRequest,
+-    IResponse,
+-)
+-
+ from twisted.web.http_headers import Headers
+-from twisted.web._newclient import HTTP11ClientProtocol, Response
+ 
+ from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
+ from twisted.python.deprecate import getDeprecationWarningString
+@@ -68,9 +36,26 @@
+     MethodInjectionTestsMixin,
+     URIInjectionTestsMixin,
+ )
++
++from twisted.web._newclient import HTTP11ClientProtocol, PotentialDataLoss, RequestNotSent, RequestTransmissionFailed, Response, ResponseFailed, ResponseNeverReceived
++from twisted.web.client import BrowserLikePolicyForHTTPS, FileBodyProducer, HTTPConnectionPool, Request, ResponseDone, URI, _HTTP11ClientFactory
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
+ 
++from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IRequest, IResponse, UNKNOWN_LENGTH
++from twisted.web.test.injectionhelpers import MethodInjectionTestsMixin, URIInjectionTestsMixin
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
++
++# Creatively lie to mypy about the nature of inheritance, since dealing with
++# expectations of a mixin class is basically impossible (don't use mixins).
++if False:  # TYPE_CHECKING
++    testMixinClass = TestCase
++    runtimeTestCase = object
++else:
++    testMixinClass = object
++    runtimeTestCase = TestCase
++
+ 
+ try:
+     from twisted.internet import ssl
+@@ -2629,11 +2614,6 @@
+     b"www-authenticate",
+ ]
+ 
+-if False:  # TYPE_CHECKING
+-    testMixinClass = TestCase
+-else:
+-    testMixinClass = object
+-
+ 
+ class _RedirectAgentTestsMixin(testMixinClass):
+     """
+@@ -3035,8 +3015,9 @@
+ 
+ 
+ 
+-class RedirectAgentTests(TestCase, FakeReactorAndConnectMixin,
+-                         _RedirectAgentTestsMixin, AgentTestsMixin):
++class RedirectAgentTests(
++    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase,
++):
+     """
+     Tests for L{client.RedirectAgent}.
+     """
+@@ -3074,10 +3055,9 @@
+ 
+ 
+ 
+-class BrowserLikeRedirectAgentTests(TestCase,
+-                                    FakeReactorAndConnectMixin,
+-                                    _RedirectAgentTestsMixin,
+-                                    AgentTestsMixin):
++class BrowserLikeRedirectAgentTests(
++    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase
++):
+     """
+     Tests for L{client.BrowserLikeRedirectAgent}.
+     """
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-8.patch twisted-18.9.0/debian/patches/CVE-2022-21712-8.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-8.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-8.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,124 @@
+From adadd2c6a49a9b5f9b41a1f125004b68449d2fb5 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:40:20 -0800
+Subject: [PATCH] really need to fix my editor to agree with our black / isort
+ config
+
+---
+ src/twisted/web/test/test_agent.py | 72 ++++++++++++++++++++++++------
+ 1 file changed, 58 insertions(+), 14 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -4,22 +4,34 @@
+ """
+ Tests for L{twisted.web.client.Agent} and related new client APIs.
+ """
++import zlib
+ from io import BytesIO
+-from twisted.test.iosim import FakeTransport, IOPump
+-from twisted.test.proto_helpers import AccumulatingProtocol, EventLoggingObserver, MemoryReactorClock, StringTransport
+-from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+ from unittest import SkipTest, skipIf
+ 
+-import zlib
++from zope.interface.declarations import implementer
++from zope.interface.verify import verifyObject
++
+ from twisted.trial.unittest import TestCase, SynchronousTestCase
+ from twisted.web import client, error, http_headers
+ from twisted.internet import defer, task
+ from twisted.python.failure import Failure
++from twisted.test.iosim import FakeTransport, IOPump
++from twisted.test.proto_helpers import (
++    AccumulatingProtocol,
++    EventLoggingObserver,
++    MemoryReactorClock,
++    StringTransport,
++)
++from twisted.test.test_sslverify import certificatesForAuthorityAndServer
+ 
+ from twisted.python.compat import cookielib, intToBytes
+ from twisted.python.components import proxyForInterface
+ from twisted.internet.task import Clock
+-from twisted.internet.error import ConnectionDone, ConnectionLost, ConnectionRefusedError
++from twisted.internet.error import (
++    ConnectionDone,
++    ConnectionLost,
++    ConnectionRefusedError,
++)
+ from twisted.internet.protocol import Protocol, Factory
+ from twisted.internet.defer import Deferred, succeed, CancelledError
+ from twisted.internet.endpoints import TCP4ClientEndpoint
+@@ -37,15 +49,41 @@
+     URIInjectionTestsMixin,
+ )
+ 
+-from twisted.web._newclient import HTTP11ClientProtocol, PotentialDataLoss, RequestNotSent, RequestTransmissionFailed, Response, ResponseFailed, ResponseNeverReceived
+-from twisted.web.client import BrowserLikePolicyForHTTPS, FileBodyProducer, HTTPConnectionPool, Request, ResponseDone, URI, _HTTP11ClientFactory
++from twisted.web._newclient import (
++    HTTP11ClientProtocol,
++    PotentialDataLoss,
++    RequestNotSent,
++    RequestTransmissionFailed,
++    Response,
++    ResponseFailed,
++    ResponseNeverReceived,
++)
++from twisted.web.client import (
++    URI,
++    BrowserLikePolicyForHTTPS,
++    FileBodyProducer,
++    HTTPConnectionPool,
++    Request,
++    ResponseDone,
++    _HTTP11ClientFactory,
++)
++
+ from twisted.web.error import SchemeNotSupported
+ from twisted.logger import globalLogPublisher
+ 
+-from twisted.web.iweb import IAgent, IAgentEndpointFactory, IBodyProducer, IPolicyForHTTPS, IRequest, IResponse, UNKNOWN_LENGTH
+-from twisted.web.test.injectionhelpers import MethodInjectionTestsMixin, URIInjectionTestsMixin
+-from zope.interface.declarations import implementer
+-from zope.interface.verify import verifyObject
++from twisted.web.iweb import (
++    UNKNOWN_LENGTH,
++    IAgent,
++    IAgentEndpointFactory,
++    IBodyProducer,
++    IPolicyForHTTPS,
++    IRequest,
++    IResponse,
++)
++from twisted.web.test.injectionhelpers import (
++    MethodInjectionTestsMixin,
++    URIInjectionTestsMixin,
++)
+ 
+ # Creatively lie to mypy about the nature of inheritance, since dealing with
+ # expectations of a mixin class is basically impossible (don't use mixins).
+@@ -3016,7 +3054,10 @@
+ 
+ 
+ class RedirectAgentTests(
+-    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase,
++    FakeReactorAndConnectMixin,
++    _RedirectAgentTestsMixin,
++    AgentTestsMixin,
++    runtimeTestCase,
+ ):
+     """
+     Tests for L{client.RedirectAgent}.
+@@ -3056,7 +3097,10 @@
+ 
+ 
+ class BrowserLikeRedirectAgentTests(
+-    FakeReactorAndConnectMixin, _RedirectAgentTestsMixin, AgentTestsMixin, runtimeTestCase
++    FakeReactorAndConnectMixin,
++    _RedirectAgentTestsMixin,
++    AgentTestsMixin,
++    runtimeTestCase,
+ ):
+     """
+     Tests for L{client.BrowserLikeRedirectAgent}.
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21712-9.patch twisted-18.9.0/debian/patches/CVE-2022-21712-9.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21712-9.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21712-9.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,19 @@
+From 4eda9eabbba8ba2ccc45daad7208ff3db25ac348 Mon Sep 17 00:00:00 2001
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 23 Jan 2022 14:55:10 -0800
+Subject: [PATCH] lint fix
+
+---
+ src/twisted/web/test/test_agent.py | 3 +--
+ 1 file changed, 1 insertion(+), 2 deletions(-)
+
+--- a/src/twisted/web/test/test_agent.py
++++ b/src/twisted/web/test/test_agent.py
+@@ -77,7 +77,6 @@
+     IAgentEndpointFactory,
+     IBodyProducer,
+     IPolicyForHTTPS,
+-    IRequest,
+     IResponse,
+ )
+ from twisted.web.test.injectionhelpers import (
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21716-1.patch twisted-18.9.0/debian/patches/CVE-2022-21716-1.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21716-1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21716-1.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,66 @@
+From de90dfe1519e996dd150de751c670f8e03daa089 Mon Sep 17 00:00:00 2001
+From: Adi Roiban <adi.roiban@chevah.com>
+Date: Mon, 24 Jan 2022 19:09:04 +0000
+Subject: [PATCH] Initial fix for Twisted version string DoS.
+
+---
+ src/twisted/conch/ssh/transport.py       |  9 +++++++++
+ src/twisted/conch/test/test_transport.py | 22 ++++++++++++++++++++++
+ src/twisted/newsfragments/10284.bugfix   |  2 ++
+ 3 files changed, 33 insertions(+)
+ create mode 100644 src/twisted/newsfragments/10284.bugfix
+
+--- a/src/twisted/conch/ssh/transport.py
++++ b/src/twisted/conch/ssh/transport.py
+@@ -724,6 +724,15 @@ class SSHTransportBase(protocol.Protocol
+         """
+         self.buf = self.buf + data
+         if not self.gotVersion:
++
++            if len(self.buf) > 4096:
++                self.sendDisconnect(
++                    DISCONNECT_CONNECTION_LOST,
++                    b"Peer version string longer than 4KB. "
++                    b"Preventing a deny of service attack.",
++                )
++                return
++
+             if self.buf.find(b'\n', self.buf.find(b'SSH-')) == -1:
+                 return
+ 
+--- a/src/twisted/conch/test/test_transport.py
++++ b/src/twisted/conch/test/test_transport.py
+@@ -480,6 +480,28 @@ class BaseSSHTransportTests(BaseSSHTrans
+         self.assertRegex(softwareVersion, softwareVersionRegex)
+ 
+ 
++    def test_dataReceiveVersionNotSentMemoryDOS(self):
++        """
++        When the peer is not sending its SSH version but keeps sending data,
++        the connection is disconnected after 4KB to prevent buffering to
++        much and running our of memory.
++        """
++        sut = MockTransportBase()
++        sut.makeConnection(self.transport)
++
++        sut.dataReceived(b"SSH-bla-bla-bla")
++
++        sut.dataReceived(b"more-bla-bla-bla" * 100)
++
++        self.assertFalse(self.transport.disconnecting)
++
++        sut.dataReceived(b"more-bla-bla-bla" * 1000)
++
++        # Once a lot of data is received without an SSH version string,
++        # the transport is disconnected.
++        self.assertTrue(self.transport.disconnecting)
++        self.assertIn(b"Preventing a deny of service attack", self.transport.value())
++
+     def test_sendPacketPlain(self):
+         """
+         Test that plain (unencrypted, uncompressed) packets are sent
+--- /dev/null
++++ b/src/twisted/newsfragments/10284.bugfix
+@@ -0,0 +1,2 @@
++twisted.conch.ssh.transport.SSHTransportBase now disconnects the remote peer if the
++SSH version string is not sent in the first 4096 bytes.
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21716-2.patch twisted-18.9.0/debian/patches/CVE-2022-21716-2.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21716-2.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21716-2.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,49 @@
+From 9b98116372f5d211ddb9f68916d4ae73bf3c8da7 Mon Sep 17 00:00:00 2001
+From: Adi Roiban <adi.roiban@chevah.com>
+Date: Mon, 24 Jan 2022 23:53:54 +0000
+Subject: [PATCH] Update after review.
+
+---
+ src/twisted/conch/ssh/transport.py       |  2 +-
+ src/twisted/conch/test/test_transport.py | 14 +++++++-------
+ 2 files changed, 8 insertions(+), 8 deletions(-)
+
+--- a/src/twisted/conch/ssh/transport.py
++++ b/src/twisted/conch/ssh/transport.py
+@@ -729,7 +729,7 @@ class SSHTransportBase(protocol.Protocol
+                 self.sendDisconnect(
+                     DISCONNECT_CONNECTION_LOST,
+                     b"Peer version string longer than 4KB. "
+-                    b"Preventing a deny of service attack.",
++                    b"Preventing a denial of service attack.",
+                 )
+                 return
+ 
+--- a/src/twisted/conch/test/test_transport.py
++++ b/src/twisted/conch/test/test_transport.py
+@@ -489,18 +489,18 @@ class BaseSSHTransportTests(BaseSSHTrans
+         sut = MockTransportBase()
+         sut.makeConnection(self.transport)
+ 
+-        sut.dataReceived(b"SSH-bla-bla-bla")
+-
+-        sut.dataReceived(b"more-bla-bla-bla" * 100)
+-
++        # Data can be received over multiple chunks.
++        sut.dataReceived(b"SSH-2-Server-Identifier")
++        sut.dataReceived(b"1234567890" * 406)
++        sut.dataReceived(b"1235678")
+         self.assertFalse(self.transport.disconnecting)
+ 
+-        sut.dataReceived(b"more-bla-bla-bla" * 1000)
+-
++        # Here we are going over the limit.
++        sut.dataReceived(b"1234567")
+         # Once a lot of data is received without an SSH version string,
+         # the transport is disconnected.
+         self.assertTrue(self.transport.disconnecting)
+-        self.assertIn(b"Preventing a deny of service attack", self.transport.value())
++        self.assertIn(b"Preventing a denial of service attack", self.transport.value())
+ 
+     def test_sendPacketPlain(self):
+         """
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-21716-3.patch twisted-18.9.0/debian/patches/CVE-2022-21716-3.patch
--- twisted-18.9.0/debian/patches/CVE-2022-21716-3.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-21716-3.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,20 @@
+From a4523b444760f07e609636264a61a2a07ca0bde5 Mon Sep 17 00:00:00 2001
+From: Adi Roiban <adi.roiban@chevah.com>
+Date: Tue, 8 Feb 2022 14:01:10 +0000
+Subject: [PATCH] Fix typo.
+
+---
+ src/twisted/conch/test/test_transport.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+--- a/src/twisted/conch/test/test_transport.py
++++ b/src/twisted/conch/test/test_transport.py
+@@ -483,7 +483,7 @@ class BaseSSHTransportTests(BaseSSHTrans
+     def test_dataReceiveVersionNotSentMemoryDOS(self):
+         """
+         When the peer is not sending its SSH version but keeps sending data,
+-        the connection is disconnected after 4KB to prevent buffering to
++        the connection is disconnected after 4KB to prevent buffering too
+         much and running our of memory.
+         """
+         sut = MockTransportBase()
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-1.patch twisted-18.9.0/debian/patches/CVE-2022-24801-1.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-1.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-1.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,129 @@
+From: Tom Most <twm@freecog.net>
+Date: Sat, 5 Mar 2022 23:26:55 -0800
+Subject: Some tests for GHSA-c2jg-hw38-jrqq
+
+---
+ src/twisted/web/test/test_http.py | 103 ++++++++++++++++++++++++++++++++++++++
+ 1 file changed, 103 insertions(+)
+
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index f2a8b3e..0334d32 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1503,6 +1503,57 @@ class ParsingTests(unittest.TestCase):
+             request.requestHeaders.getRawHeaders(b'bAz'), [b'Quux', b'quux'])
+ 
+ 
++    def test_headerStripWhitespace(self):
++        """
++        Leading and trailing space and tab characters are stripped from
++        headers. Other forms of whitespace are preserved.
++
++        See RFC 7230 section 3.2.3 and 3.2.4.
++        """
++        processed = []
++
++        class MyRequest(http.Request):
++            def process(self):
++                processed.append(self)
++                self.finish()
++
++        requestLines = [
++            b"GET / HTTP/1.0",
++            b"spaces:   spaces were stripped   ",
++            b"tabs: \t\ttabs were stripped\t\t",
++            b"spaces-and-tabs: \t \t spaces and tabs were stripped\t \t",
++            b"line-tab:   \v vertical tab was preserved\v\t",
++            b"form-feed: \f form feed was preserved \f  ",
++            b"",
++            b"",
++        ]
++
++        self.runRequest(b"\n".join(requestLines), MyRequest, 0)
++        [request] = processed
++        # All leading and trailing whitespace is stripped from the
++        # header-value.
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"spaces"),
++            [b"spaces were stripped"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"tabs"),
++            [b"tabs were stripped"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"spaces-and-tabs"),
++            [b"spaces and tabs were stripped"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"line-tab"),
++            [b"\v vertical tab was preserved\v"],
++        )
++        self.assertEqual(
++            request.requestHeaders.getRawHeaders(b"form-feed"),
++            [b"\f form feed was preserved \f"],
++        )
++
++
+     def test_tooManyHeaders(self):
+         """
+         C{HTTPChannel} enforces a limit of C{HTTPChannel.maxHeaders} on the
+@@ -2054,6 +2105,58 @@ Hello,
+         ])
+ 
+ 
++    def test_contentLengthMalformed(self):
++        """
++        A request with a non-integer C{Content-Length} header fails with a 400
++        response without calling L{Request.process}.
++        """
++        self.assertRequestRejected(
++            [
++                b"GET /a HTTP/1.1",
++                b"Content-Length: MORE THAN NINE THOUSAND!",
++                b"Host: host.invalid",
++                b"",
++                b"",
++                b"x" * 9001,
++            ]
++        )
++
++    def test_contentLengthTooPositive(self):
++        """
++        A request with a C{Content-Length} header that begins with a L{+} fails
++        with a 400 response without calling L{Request.process}.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        self.assertRequestRejected(
++            [
++                b"GET /a HTTP/1.1",
++                b"Content-Length: +100",
++                b"Host: host.invalid",
++                b"",
++                b"",
++                b"x" * 100,
++            ]
++        )
++
++    def test_contentLengthNegative(self):
++        """
++        A request with a C{Content-Length} header that is negative fails with
++        a 400 response without calling L{Request.process}.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        self.assertRequestRejected(
++            [
++                b"GET /a HTTP/1.1",
++                b"Content-Length: -100",
++                b"Host: host.invalid",
++                b"",
++                b"",
++                b"x" * 200,
++            ]
++        )
++
+     def test_duplicateContentLengthsWithPipelinedRequests(self):
+         """
+         Two pipelined requests, the first of which includes multiple
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-2.patch twisted-18.9.0/debian/patches/CVE-2022-24801-2.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-2.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-2.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,21 @@
+From: Tom Most <twm@freecog.net>
+Date: Mon, 7 Mar 2022 00:02:55 -0800
+Subject: Replace obs-fold with a single space
+
+---
+ src/twisted/web/http.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index 8326139..a4f1491 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2101,7 +2101,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+                 self.setRawMode()
+         elif line[0] in b' \t':
+             # Continuation of a multi line header.
+-            self.__header = self.__header + '\n' + line
++            self.__header += b" " + line.lstrip(b" \t")
+         # Regular header line.
+         # Processing of header line is delayed to allow accumulating multi
+         # line headers.
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-3.patch twisted-18.9.0/debian/patches/CVE-2022-24801-3.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-3.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-3.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,21 @@
+From: Tom Most <twm@freecog.net>
+Date: Mon, 7 Mar 2022 00:03:50 -0800
+Subject: Strip only spaces and tabs from header values
+
+---
+ src/twisted/web/http.py | 2 +-
+ 1 file changed, 1 insertion(+), 1 deletion(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index a4f1491..e3ca5fa 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2180,7 +2180,7 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+             return False
+ 
+         header = header.lower()
+-        data = data.strip()
++        data = data.strip(b" \t")
+ 
+         if not self._maybeChooseTransferDecoder(header, data):
+             return False
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-4.patch twisted-18.9.0/debian/patches/CVE-2022-24801-4.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-4.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-4.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,21 @@
+From: Tom Most <twm@freecog.net>
+Date: Mon, 7 Mar 2022 00:32:14 -0800
+Subject: Reject non-digit Content-Length
+
+---
+ src/twisted/web/http.py | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index e3ca5fa..4febd1e 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -2129,6 +2129,8 @@ class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
+ 
+         # Can this header determine the length?
+         if header == b'content-length':
++            if not data.isdigit():
++                return fail()
+             try:
+                 length = int(data)
+             except ValueError:
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-5.patch twisted-18.9.0/debian/patches/CVE-2022-24801-5.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-5.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-5.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,69 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 13 Mar 2022 23:19:39 -0700
+Subject: Test for malformed chunk size and extensions
+
+---
+ src/twisted/web/test/test_http.py | 36 ++++++++++++++++++++++++++++++++++++
+ 1 file changed, 36 insertions(+)
+
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index 0334d32..155d694 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1230,6 +1230,23 @@ class ChunkedTransferEncodingTests(unittest.TestCase):
+         self.assertEqual(L, [b'abc'])
+ 
+ 
++    def test_extensionsMalformed(self):
++        """
++        L{_ChunkedTransferDecoder.dataReceived} raises
++        L{_MalformedChunkedDataError} when the chunk extension fields contain
++        invalid characters.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        for b in [*range(0, 0x09), *range(0x10, 0x21), *range(0x74, 0x80)]:
++            data = b"3; " + bytes((b,)) + b"\r\nabc\r\n"
++            p = http._ChunkedTransferDecoder(
++                lambda b: None,  # pragma: nocov
++                lambda b: None,  # pragma: nocov
++            )
++            self.assertRaises(http._MalformedChunkedDataError, p.dataReceived, data)
++
++
+     def test_finish(self):
+         """
+         L{_ChunkedTransferDecoder.dataReceived} interprets a zero-length
+@@ -1307,6 +1324,23 @@ class ChunkedTransferEncodingTests(unittest.TestCase):
+         self.assertEqual(successes, [True])
+ 
+ 
++    def test_malformedChunkSizeHex(self):
++        """
++        L{_ChunkedTransferDecoder.dataReceived} raises
++        L{_MalformedChunkedDataError} when the chunk size is prefixed with
++        "0x", as if it were a Python integer literal.
++
++        This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
++        """
++        p = http._ChunkedTransferDecoder(
++            lambda b: None,  # pragma: nocov
++            lambda b: None,  # pragma: nocov
++        )
++        self.assertRaises(
++            http._MalformedChunkedDataError, p.dataReceived, b"0x3\r\nabc\r\n"
++        )
++
++
+ 
+ class ChunkingTests(unittest.TestCase, ResponseTestMixin):
+ 
+@@ -1318,6 +1352,8 @@ class ChunkingTests(unittest.TestCase, ResponseTestMixin):
+             chunked = b''.join(http.toChunk(s))
+             self.assertEqual((s, b''), http.fromChunk(chunked))
+         self.assertRaises(ValueError, http.fromChunk, b'-5\r\nmalformed!\r\n')
++        self.assertRaises(ValueError, http.fromChunk, b"0xa\r\nmalformed!\r\n")
++        self.assertRaises(ValueError, http.fromChunk, b"0XA\r\nmalformed!\r\n")
+ 
+     def testConcatenatedChunks(self):
+         chunked = b''.join([b''.join(http.toChunk(t)) for t in self.strings])
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-6.patch twisted-18.9.0/debian/patches/CVE-2022-24801-6.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-6.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-6.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,112 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 13 Mar 2022 23:51:52 -0700
+Subject: Reject malformed chunk sizes
+
+---
+ src/twisted/web/http.py           | 30 +++++++++++++++++++++++++++--
+ src/twisted/web/test/test_http.py | 40 +++++++++++++++++++++++++++++++++++++++
+ 2 files changed, 68 insertions(+), 2 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index 4febd1e..e2f74a6 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -338,6 +338,32 @@ def toChunk(data):
+ 
+ 
+ 
++def _ishexdigits(b):
++    """
++    Is the string case-insensitively hexidecimal?
++
++    It must be composed of one or more characters in the ranges a-f, A-F
++    and 0-9.
++    """
++    for c in b:
++        if c not in b'0123456789abcdefABCDEF':
++            return False
++    return bool(b)
++
++
++def _hexint(b):
++    """
++    Decode a hexadecimal integer.
++
++    Unlike L{int(b, 16)}, this raises L{ValueError} when the integer has
++    a prefix like C{b'0x'}, C{b'+'}, or C{b'-'}, which is desirable when
++    parsing network protocols.
++    """
++    if not _ishexdigits(b):
++        raise ValueError(b)
++    return int(b, 16)
++
++
+ def fromChunk(data):
+     """
+     Convert chunk to string.
+@@ -350,7 +376,7 @@ def fromChunk(data):
+         byte string.
+     """
+     prefix, rest = data.split(b'\r\n', 1)
+-    length = int(prefix, 16)
++    length = _hexint(prefix)
+     if length < 0:
+         raise ValueError("Chunk length must be >= 0, not %d" % (length,))
+     if rest[length:length + 2] != b'\r\n':
+@@ -1771,7 +1797,7 @@ class _ChunkedTransferDecoder(object):
+             line, rest = data.split(b'\r\n', 1)
+             parts = line.split(b';')
+             try:
+-                self.length = int(parts[0], 16)
++                self.length = _hexint(parts[0])
+             except ValueError:
+                 raise _MalformedChunkedDataError(
+                     "Chunk-size must be an integer.")
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index 155d694..5161acb 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -4095,3 +4095,43 @@ class ChannelProductionTests(unittest.TestCase):
+         clock.advance(1)
+         self.assertIs(transport.producer, None)
+         self.assertIs(transport.streaming, None)
++
++
++class HexHelperTests(unittest.SynchronousTestCase):
++    """
++    Test the L{http._hexint} and L{http._ishexdigits} helper functions.
++    """
++
++    badStrings = (b"", b"0x1234", b"feds", b"-123" b"+123")
++
++    def test_isHex(self):
++        """
++        L{_ishexdigits()} returns L{True} for nonempy bytestrings containing
++        hexadecimal digits.
++        """
++        for s in (b"10", b"abcdef", b"AB1234", b"fed", b"123467890"):
++            self.assertIs(True, http._ishexdigits(s))
++
++    def test_decodes(self):
++        """
++        L{_hexint()} returns the integer equivalent of the input.
++        """
++        self.assertEqual(10, http._hexint(b"a"))
++        self.assertEqual(0x10, http._hexint(b"10"))
++        self.assertEqual(0xABCD123, http._hexint(b"abCD123"))
++
++    def test_isNotHex(self):
++        """
++        L{_ishexdigits()} returns L{False} for bytestrings that don't contain
++        hexadecimal digits, including the empty string.
++        """
++        for s in self.badStrings:
++            self.assertIs(False, http._ishexdigits(s))
++
++    def test_decodeNotHex(self):
++        """
++        L{_hexint()} raises L{ValueError} for bytestrings that can't
++        be decoded.
++        """
++        for s in self.badStrings:
++            self.assertRaises(ValueError, http._hexint, s)
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-7.patch twisted-18.9.0/debian/patches/CVE-2022-24801-7.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-7.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-7.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,22 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 13 Mar 2022 23:55:26 -0700
+Subject: We should deprecate http.fromChunk
+
+---
+ src/twisted/web/http.py | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index e2f74a6..99641ff 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -368,6 +368,9 @@ def fromChunk(data):
+     """
+     Convert chunk to string.
+ 
++    Note that this function is not specification compliant: it doesn't handle
++    chunk extensions.
++
+     @type data: C{bytes}
+ 
+     @return: tuple of (result, remaining) - both C{bytes}.
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-8.patch twisted-18.9.0/debian/patches/CVE-2022-24801-8.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-8.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-8.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,100 @@
+From: Tom Most <twm@freecog.net>
+Date: Sun, 27 Mar 2022 22:17:30 -0700
+Subject: Correct chunk extension byte validation
+
+Go back to the RFC to figure out the correct allowed ranges.
+---
+ src/twisted/web/http.py           | 46 ++++++++++++++++++++++++++++++++++++++-
+ src/twisted/web/test/test_http.py |  8 ++++++-
+ 2 files changed, 52 insertions(+), 2 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index 99641ff..c992deb 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -346,7 +346,7 @@ def _ishexdigits(b):
+     and 0-9.
+     """
+     for c in b:
+-        if c not in b'0123456789abcdefABCDEF':
++        if c not in b"0123456789abcdefABCDEF":
+             return False
+     return bool(b)
+ 
+@@ -1748,6 +1748,46 @@ class _IdentityTransferDecoder(object):
+             raise _DataLoss()
+ 
+ 
++_chunkExtChars = (
++    b"\t !\"#$%&'()*+,-./0123456789:;<=>?@"
++    b"ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_`"
++    b"abcdefghijklmnopqrstuvwxyz{|}~"
++    b"\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f"
++    b"\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f"
++    b"\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf"
++    b"\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf"
++    b"\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf"
++    b"\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf"
++    b"\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef"
++    b"\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"
++)
++"""
++Characters that are valid in a chunk extension.
++
++See RFC 7230 section 4.1.1:
++
++     chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
++
++     chunk-ext-name = token
++     chunk-ext-val  = token / quoted-string
++
++Section 3.2.6:
++
++     token          = 1*tchar
++
++     tchar          = "!" / "#" / "$" / "%" / "&" / "'" / "*"
++                    / "+" / "-" / "." / "^" / "_" / "`" / "|" / "~"
++                    / DIGIT / ALPHA
++                    ; any VCHAR, except delimiters
++
++     quoted-string  = DQUOTE *( qdtext / quoted-pair ) DQUOTE
++     qdtext         = HTAB / SP /%x21 / %x23-5B / %x5D-7E / obs-text
++     obs-text       = %x80-FF
++
++We don't check if chunk extensions are well-formed beyond validating that they
++don't contain characters outside this range.
++"""
++
+ 
+ class _ChunkedTransferDecoder(object):
+     """
+@@ -1804,6 +1844,10 @@ class _ChunkedTransferDecoder(object):
+             except ValueError:
+                 raise _MalformedChunkedDataError(
+                     "Chunk-size must be an integer.")
++            if len(parts) > 1 and parts[1].translate(None, _chunkExtChars) != b"":
++                raise _MalformedChunkedDataError(
++                    "Invalid characters in chunk extensions: %r." % parts[1]
++                )
+             if self.length == 0:
+                 self.state = 'TRAILER'
+             else:
+diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py
+index 5161acb..21d2713 100644
+--- a/src/twisted/web/test/test_http.py
++++ b/src/twisted/web/test/test_http.py
+@@ -1238,7 +1238,13 @@ class ChunkedTransferEncodingTests(unittest.TestCase):
+ 
+         This is a potential request smuggling vector: see GHSA-c2jg-hw38-jrqq.
+         """
+-        for b in [*range(0, 0x09), *range(0x10, 0x21), *range(0x74, 0x80)]:
++        invalidControl = (
++            b"\x00\x01\x02\x03\x04\x05\x06\x07\x08\n\x0b\x0c\r\x0e\x0f"
++            b"\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f"
++        )
++        invalidDelimiter = b"\\"
++        invalidDel = b"\x7f"
++        for b in invalidControl + invalidDelimiter + invalidDel:
+             data = b"3; " + bytes((b,)) + b"\r\nabc\r\n"
+             p = http._ChunkedTransferDecoder(
+                 lambda b: None,  # pragma: nocov
diff -Nru twisted-18.9.0/debian/patches/CVE-2022-24801-9.patch twisted-18.9.0/debian/patches/CVE-2022-24801-9.patch
--- twisted-18.9.0/debian/patches/CVE-2022-24801-9.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/CVE-2022-24801-9.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,38 @@
+From: Tom Most <twm@freecog.net>
+Date: Fri, 1 Apr 2022 20:47:59 -0700
+Subject: Address review feedback
+
+---
+ src/twisted/web/http.py | 6 +++---
+ 1 file changed, 3 insertions(+), 3 deletions(-)
+
+diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py
+index c992deb..1a4e7cb 100644
+--- a/src/twisted/web/http.py
++++ b/src/twisted/web/http.py
+@@ -348,7 +348,7 @@ def _ishexdigits(b):
+     for c in b:
+         if c not in b"0123456789abcdefABCDEF":
+             return False
+-    return bool(b)
++    return b != b""
+ 
+ 
+ def _hexint(b):
+@@ -1764,14 +1764,14 @@ _chunkExtChars = (
+ """
+ Characters that are valid in a chunk extension.
+ 
+-See RFC 7230 section 4.1.1:
++See RFC 7230 section 4.1.1::
+ 
+      chunk-ext      = *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
+ 
+      chunk-ext-name = token
+      chunk-ext-val  = token / quoted-string
+ 
+-Section 3.2.6:
++And section 3.2.6::
+ 
+      token          = 1*tchar
+ 
diff -Nru twisted-18.9.0/debian/patches/series twisted-18.9.0/debian/patches/series
--- twisted-18.9.0/debian/patches/series	2018-12-07 06:23:30.000000000 -0400
+++ twisted-18.9.0/debian/patches/series	2022-05-05 10:01:06.000000000 -0400
@@ -13,3 +13,45 @@
 0013-Drop-test_givesMeaningfulErrorMessageIfNoCipherMatch.patch
 0014-OpenSSL-may-not-use-ECDH-by-default-thus-drop-this-t.patch
 0015-Fix-tests-to-expect-new-web-request-logging-format.patch
+CVE-2019-12387.patch
+CVE-2019-12855-01.patch
+CVE-2019-12855-02.patch
+CVE-2019-12855-03.patch
+CVE-2019-12855-04.patch
+CVE-2019-12855-05.patch
+CVE-2019-12855-06.patch
+CVE-2019-12855-07.patch
+CVE-2019-12855-09.patch
+CVE-2019-12855-10.patch
+CVE-2019-12855-11.patch
+CVE-2019-12855-12.patch
+CVE-2019-12855-13.patch
+CVE-2019-12855-14.patch
+CVE-2019-12855-15.patch
+CVE-2019-12855-17.patch
+CVE-2019-951x.patch
+CVE-2020-1010x-pre1.patch
+CVE-2020-1010x.patch
+CVE-2022-21712-1.patch
+CVE-2022-21712-2.patch
+CVE-2022-21712-3.patch
+CVE-2022-21712-4.patch
+CVE-2022-21712-5.patch
+CVE-2022-21712-6.patch
+CVE-2022-21712-7.patch
+CVE-2022-21712-8.patch
+CVE-2022-21712-9.patch
+CVE-2022-21712-10.patch
+CVE-2022-21716-1.patch
+CVE-2022-21716-2.patch
+CVE-2022-21716-3.patch
+CVE-2022-24801-1.patch
+CVE-2022-24801-2.patch
+CVE-2022-24801-3.patch
+CVE-2022-24801-4.patch
+CVE-2022-24801-5.patch
+CVE-2022-24801-6.patch
+CVE-2022-24801-7.patch
+CVE-2022-24801-8.patch
+CVE-2022-24801-9.patch
+Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch
diff -Nru twisted-18.9.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch twisted-18.9.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch
--- twisted-18.9.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch	1969-12-31 20:00:00.000000000 -0400
+++ twisted-18.9.0/debian/patches/Tests-remove-spurious-test-for-illegal-whitespace-in-xmlns.patch	2022-05-05 10:01:06.000000000 -0400
@@ -0,0 +1,37 @@
+From: Glyph <glyph@twistedmatrix.com>
+Date: Sun, 27 Mar 2022 18:03:49 -0700
+Subject: remove spurious test for illegal whitespace in xmlns
+
+Origin: upstream, https://github.com/twisted/twisted/commit/6b4bbf9040abd8e5c8feae026c4a6483c7f72506
+---
+ src/twisted/newsfragments/10318.misc  |  0
+ src/twisted/words/test/test_domish.py | 12 ------------
+ 2 files changed, 12 deletions(-)
+ create mode 100644 src/twisted/newsfragments/10318.misc
+
+diff --git a/src/twisted/newsfragments/10318.misc b/src/twisted/newsfragments/10318.misc
+new file mode 100644
+index 0000000..e69de29
+diff --git a/src/twisted/words/test/test_domish.py b/src/twisted/words/test/test_domish.py
+index a8f8fa7..cecf61c 100644
+--- a/src/twisted/words/test/test_domish.py
++++ b/src/twisted/words/test/test_domish.py
+@@ -338,18 +338,6 @@ class DomishStreamTestsMixin:
+         self.assertEqual(self.elements[0].child2.uri, '')
+ 
+ 
+-    def test_namespaceWithWhitespace(self):
+-        """
+-        Whitespace in an xmlns value is preserved in the resulting node's C{uri}
+-        attribute.
+-        """
+-        xml = b"<root xmlns:foo=' bar baz '><foo:bar foo:baz='quux'/></root>"
+-        self.stream.parse(xml)
+-        self.assertEqual(self.elements[0].uri, " bar baz ")
+-        self.assertEqual(
+-            self.elements[0].attributes, {(" bar baz ", "baz"): "quux"})
+-
+-
+     def testChildPrefix(self):
+         xml = b"<root xmlns='testns' xmlns:foo='testns2'><foo:child/></root>"
+ 

Reply to: