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

Bug#1010615: marked as done (buster-pu: package twisted/18.9.0-3+deb10u1)



Your message dated Sat, 10 Sep 2022 13:40:55 +0100
with message-id <2cfc9645343bdb910fe19c07bddfec2c428346a3.camel@adam-barratt.org.uk>
and subject line Closing requests for updates included in 10.13
has caused the Debian Bug report #1010615,
regarding buster-pu: package twisted/18.9.0-3+deb10u1
to be marked as done.

This means that you claim that the problem has been dealt with.
If this is not the case it is now your responsibility to reopen the
Bug report if necessary, and/or fix the problem forthwith.

(NB: If you are a system administrator and have no idea what this
message is talking about, this may indicate a serious mail system
misconfiguration somewhere. Please contact owner@bugs.debian.org
immediately.)


-- 
1010615: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1010615
Debian Bug Tracking System
Contact owner@bugs.debian.org with problems
--- Begin Message ---
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>"
+ 

--- End Message ---
--- Begin Message ---
Package: release.debian.org
Version: 10.13

Hi,

Each of the updates referenced in these bugs was included in today's
10.13 point release.

Regards,

Adam

--- End Message ---

Reply to: