VYPR
Moderate severityNVD Advisory· Published Jun 10, 2019· Updated Aug 4, 2024

CVE-2019-12387

CVE-2019-12387

Description

In Twisted before 19.2.1, twisted.web did not validate or sanitize URIs or HTTP methods, allowing an attacker to inject invalid characters such as CRLF.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Twisted before 19.2.1 fails to sanitize URIs and HTTP methods, enabling CRLF injection attacks.

Vulnerability

Summary

In Twisted versions before 19.2.1, the twisted.web component does not properly validate or sanitize URIs and HTTP methods. This allows an attacker to inject CRLF (carriage return line feed) characters into HTTP requests [2][3].

Exploitation

An attacker can send a crafted HTTP request containing CRLF sequences in the URI or HTTP method field. Since the server does not filter these characters, the injected CRLF can break the HTTP protocol, allowing the attacker to insert arbitrary headers or modify the response body. The attack is remotely exploitable without authentication, requiring only network access to the Twisted web server [3].

Impact

Successful exploitation enables HTTP response splitting, which can lead to cache poisoning, cross-site scripting, and other attacks. An attacker could potentially poison web caches, redirect users to malicious sites, or perform session fixation [2][3].

Mitigation

The issue is fixed in Twisted version 19.2.1. Users should upgrade to this or a later version. Major distributions such as Ubuntu have released security updates (USN-4308-1, USN-4308-2) to address this vulnerability [2][4].

AI Insight generated on May 22, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
twistedPyPI
< 19.2.119.2.1

Affected products

12

Patches

1
6c61fc4503ae

Prevent CRLF injections described in CVE-2019-12387

https://github.com/twisted/twistedMark WilliamsJun 5, 2019via ghsa
6 files changed · +725 11
  • src/twisted/web/client.py+17 5 modified
    @@ -47,6 +47,9 @@ def urlunparse(parts):
     from twisted.web.http_headers import Headers
     from twisted.logger import Logger
     
    +from twisted.web._newclient import _ensureValidURI, _ensureValidMethod
    +
    +
     
     class PartialDownloadError(error.Error):
         """
    @@ -78,11 +81,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:
    @@ -362,7 +367,7 @@ def __init__(self, url, method=b'GET', postdata=None, headers=None,
                 # 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)
     
    @@ -389,6 +394,7 @@ def __repr__(self):
             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:
    @@ -733,7 +739,7 @@ def _makeGetterFactory(url, factoryFactory, contextFactory=None,
     
         @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
    @@ -1493,6 +1499,9 @@ def _requestWithEndpoint(self, key, endpoint, method, parsedURI,
             if not isinstance(method, bytes):
                 raise TypeError('method={!r} is {}, but must be bytes'.format(
                         method, type(method)))
    +
    +        method = _ensureValidMethod(method)
    +
             # Create minimal headers, if necessary:
             if headers is None:
                 headers = Headers()
    @@ -1717,6 +1726,7 @@ def request(self, method, uri, headers=None, bodyProducer=None):
     
             @see: L{twisted.web.iweb.IAgent.request}
             """
    +        uri = _ensureValidURI(uri.strip())
             parsedURI = URI.fromBytes(uri)
             try:
                 endpoint = self._getEndpoint(parsedURI)
    @@ -1750,6 +1760,8 @@ def request(self, method, uri, headers=None, bodyProducer=None):
             """
             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)
    
  • src/twisted/web/_newclient.py+81 4 modified
    @@ -29,6 +29,8 @@
     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 @@ def connectionLost(self, reason):
     
     
     
    +_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 @@ def __init__(self, method, uri, headers, bodyProducer, persistent=False):
                 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 @@ def _writeHeaders(self, transport, TEorCL):
             # 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:
    
  • src/twisted/web/newsfragments/9647.bugfix+1 0 added
    @@ -0,0 +1 @@
    +All HTTP clients in twisted.web.client now raise a ValueError when called with a method and/or URL that contain invalid characters.  This mitigates CVE-2019-12387.  Thanks to Alex Brasetvik for reporting this vulnerability.
    \ No newline at end of file
    
  • src/twisted/web/test/injectionhelpers.py+168 0 added
    @@ -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")
    
  • src/twisted/web/test/test_agent.py+146 1 modified
    @@ -11,7 +11,7 @@
     
     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
    @@ -51,6 +51,10 @@
     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
     
    @@ -897,6 +901,7 @@ class AgentTests(TestCase, FakeReactorAndConnectMixin, AgentTestsMixin,
         """
         Tests for the new HTTP client API provided by L{Agent}.
         """
    +
         def makeAgent(self):
             """
             @return: a new L{twisted.web.client.Agent} instance
    @@ -1327,6 +1332,48 @@ def test_endpointFactoryPool(self):
     
     
     
    +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):
         """
    @@ -3202,3 +3249,101 @@ def test_changeCacheSize(self):
             self.assertEquals(5, len(policy._cache))
     
             self.assertIn(hostn, policy._cache)
    +
    +
    +
    +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())
    
  • src/twisted/web/test/test_webclient.py+312 1 modified
    @@ -7,6 +7,7 @@
     
     from __future__ import division, absolute_import
     
    +import io
     import os
     from errno import ENOSPC
     
    @@ -20,7 +21,8 @@
     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.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 @@ def test_httpDownloaderDeprecated(self):
             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)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

16

News mentions

0

No linked articles in our index yet.