diff --git a/CVE-2019-12387.patch b/CVE-2019-12387.patch new file mode 100644 index 0000000000000000000000000000000000000000..5eec116764e2acaaaf6952cfc66e1a37fa52bbd3 --- /dev/null +++ b/CVE-2019-12387.patch @@ -0,0 +1,907 @@ +diff --git a/src/twisted/web/_newclient.py b/src/twisted/web/_newclient.py +index 370f47d..74a8a6c 100644 +--- a/src/twisted/web/_newclient.py ++++ b/src/twisted/web/_newclient.py +@@ -29,6 +29,8 @@ Various other classes in this module support this usage: + 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: +diff --git a/src/twisted/web/client.py b/src/twisted/web/client.py +index 02eb6e9..a1554d3 100644 +--- a/src/twisted/web/client.py ++++ b/src/twisted/web/client.py +@@ -46,6 +46,9 @@ from twisted.web.iweb import UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse + 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.ClientFactory): + # 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.ClientFactory): + 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, 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 +@@ -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) +diff --git a/src/twisted/web/newsfragments/9647.bugfix b/src/twisted/web/newsfragments/9647.bugfix +new file mode 100644 +index 0000000..b76916c +--- /dev/null ++++ b/src/twisted/web/newsfragments/9647.bugfix +@@ -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 +diff --git a/src/twisted/web/test/injectionhelpers.py b/src/twisted/web/test/injectionhelpers.py +new file mode 100644 +index 0000000..ffeb862 +--- /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") +diff --git a/src/twisted/web/test/test_agent.py b/src/twisted/web/test/test_agent.py +index 7a7669b..9b57512 100644 +--- 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 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 + +@@ -886,6 +890,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 +@@ -1307,6 +1312,48 @@ class AgentTests(TestCase, FakeReactorAndConnectMixin, AgentTestsMixin, + + + ++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,100 @@ 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()) +diff --git a/src/twisted/web/test/test_webclient.py b/src/twisted/web/test/test_webclient.py +index 41cff54..680e027 100644 +--- 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} APIs, C{getPage} and friends. + + 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 --git a/Twisted-18.9.0.tar.bz2 b/Twisted-18.9.0.tar.bz2 new file mode 100644 index 0000000000000000000000000000000000000000..e76adf1c9931a81ea7c4684c456c4c5e6f542c35 Binary files /dev/null and b/Twisted-18.9.0.tar.bz2 differ diff --git a/python-twisted.spec b/python-twisted.spec new file mode 100644 index 0000000000000000000000000000000000000000..cdff3b5f4fe236cb2abe8c9c949beea35e3c0e40 --- /dev/null +++ b/python-twisted.spec @@ -0,0 +1,174 @@ +Name: python-twisted +Version: 18.9.0 +Release: 3 +Summary: An event-driven networking engine written in Python +License: MIT +URL: http://twistedmatrix.com/ +Source0: https://files.pythonhosted.org/packages/source/T/Twisted/Twisted-%{version}.tar.bz2 + +# https://github.com/twisted/twisted/commit/6c61fc4503ae39ab8ecee52d10f10ee2c371d7e2 +Patch0000: CVE-2019-12387.patch + +%description +Twisted is an event-based framework for internet applications, +supporting Python 2.7 and Python 3.5+. It includes modules for many +different purposes, including the following: + + * twisted.web: HTTP clients and servers, HTML templating, and a WSGI server + * twisted.conch: SSHv2 and Telnet clients and servers and terminal emulators + * twisted.words: Clients and servers for IRC, XMPP, and other IM protocols + * twisted.mail: IMAPv4, POP3, SMTP clients and servers + * twisted.positioning: Tools for communicating with NMEA-compatible GPS receivers + * twisted.names: DNS client and tools for making your own DNS servers + * twisted.trial: A unit testing framework that integrates well with Twisted-based code. + +Twisted supports all major system event loops -- select (all platforms), +poll (most POSIX platforms), epoll (Linux), kqueue (FreeBSD, macOS), +IOCP (Windows), and various GUI event loops (GTK+2/3, Qt, wxWidgets). +Third-party reactors can plug into Twisted, and provide support for +additional event loops. + +%package -n python2-twisted +Summary: An event-driven networking engine written in Python + +%{?python_provide:%python_provide python2-twisted} + +BuildRequires: python2-devel >= 2.6 python2dist(appdirs) >= 1.4.0 +BuildRequires: python2dist(automat) >= 0.3.0 python2dist(constantly) >= 15.1 +BuildRequires: python2dist(cryptography) >= 1.5 python2dist(incremental) >= 16.10.1 +BuildRequires: (python2dist(h2) >= 3.0 with python2dist(h2) < 4.0) +BuildRequires: python2dist(hyperlink) >= 17.1.1 python2dist(idna) >= 0.6 +BuildRequires: (python2dist(priority) >= 1.1.0 with python2dist(priority) < 2.0) +BuildRequires: python2dist(pyasn1) python2dist(pyopenssl) >= 16.0.0 +BuildRequires: python2dist(pyserial) >= 3.0 python2dist(service-identity) +BuildRequires: python2dist(setuptools) python2dist(zope.interface) >= 4.4.2 +BuildRequires: python2dist(pyhamcrest) + +Recommends: python2dist(service-identity) + +%description -n python2-twisted +Twisted is an event-based framework for internet applications, +supporting Python 2.7 and Python 3.5+. It includes modules for many +different purposes, including the following: + + * twisted.web: HTTP clients and servers, HTML templating, and a WSGI server + * twisted.conch: SSHv2 and Telnet clients and servers and terminal emulators + * twisted.words: Clients and servers for IRC, XMPP, and other IM protocols + * twisted.mail: IMAPv4, POP3, SMTP clients and servers + * twisted.positioning: Tools for communicating with NMEA-compatible GPS receivers + * twisted.names: DNS client and tools for making your own DNS servers + * twisted.trial: A unit testing framework that integrates well with Twisted-based code. + +Twisted supports all major system event loops -- select (all platforms), +poll (most POSIX platforms), epoll (Linux), kqueue (FreeBSD, macOS), +IOCP (Windows), and various GUI event loops (GTK+2/3, Qt, wxWidgets). +Third-party reactors can plug into Twisted, and provide support for +additional event loops. + + +%package -n python3-twisted +Summary: An event-driven networking engine written in Python + +%{?python_provide:%python_provide python3-twisted} + +BuildRequires: python3-devel >= 3.3 python3dist(appdirs) >= 1.4.0 +BuildRequires: python3dist(automat) >= 0.3.0 python3dist(constantly) >= 15.1 +BuildRequires: python3dist(cryptography) >= 1.5 python3dist(hyperlink) >= 17.1.1 +BuildRequires: (python3dist(h2) >= 3.0 with python3dist(h2) < 4.0) +BuildRequires: python3dist(idna) >= 0.6 python3dist(incremental) >= 16.10.1 +BuildRequires: (python3dist(priority) >= 1.1.0 with python3dist(priority) < 2.0) +BuildRequires: python3dist(pyasn1) python3dist(pyopenssl) >= 16.0.0 +BuildRequires: python3dist(pyserial) >= 3.0 python3dist(service-identity) +BuildRequires: python3dist(setuptools) python3dist(sphinx) >= 1.3.1 +BuildRequires: python3dist(zope.interface) >= 4.4.2 python3dist(pyhamcrest) + +Recommends: python3dist(service-identity) + +%description -n python3-twisted +Twisted is an event-based framework for internet applications, +supporting Python 2.7 and Python 3.5+. It includes modules for many +different purposes, including the following: + + * twisted.web: HTTP clients and servers, HTML templating, and a WSGI server + * twisted.conch: SSHv2 and Telnet clients and servers and terminal emulators + * twisted.words: Clients and servers for IRC, XMPP, and other IM protocols + * twisted.mail: IMAPv4, POP3, SMTP clients and servers + * twisted.positioning: Tools for communicating with NMEA-compatible GPS receivers + * twisted.names: DNS client and tools for making your own DNS servers + * twisted.trial: A unit testing framework that integrates well with Twisted-based code. + +Twisted supports all major system event loops -- select (all platforms), +poll (most POSIX platforms), epoll (Linux), kqueue (FreeBSD, macOS), +IOCP (Windows), and various GUI event loops (GTK+2/3, Qt, wxWidgets). +Third-party reactors can plug into Twisted, and provide support for +additional event loops. + +%package help +Summary: Documents for python-twisted +BuildArch: noarch + +%description help +The python-twisted-help package contains related documents. + +%prep +%autosetup -n Twisted-%{version} -p1 + +%build +%py2_build +%py3_build + +PYTHONPATH=${PWD}/src/ sphinx-build-3 -a docs html +rm -rf html/.doctrees +rm -rf html/.buildinfo + +%install +%py2_install +mv %{buildroot}%{_bindir}/trial %{buildroot}%{_bindir}/trial-%{python2_version} +mv %{buildroot}%{_bindir}/twistd %{buildroot}%{_bindir}/twistd-%{python2_version} +ln -s ./trial-%{python2_version} %{buildroot}%{_bindir}/trial-2 +ln -s ./twistd-%{python2_version} %{buildroot}%{_bindir}/twistd-2 +chmod 755 %{buildroot}%{python2_sitearch}/twisted/python/_sendmsg.so +chmod 755 %{buildroot}%{python2_sitearch}/twisted/test/raiser.so +chmod +x %{buildroot}%{python2_sitearch}/twisted/mail/test/pop3testserver.py +chmod +x %{buildroot}%{python2_sitearch}/twisted/trial/test/scripttest.py + +%py3_install +mv %{buildroot}%{_bindir}/trial %{buildroot}%{_bindir}/trial-%{python3_version} +mv %{buildroot}%{_bindir}/twistd %{buildroot}%{_bindir}/twistd-%{python3_version} +ln -s ./trial-%{python3_version} %{buildroot}%{_bindir}/trial-3 +ln -s ./twistd-%{python3_version} %{buildroot}%{_bindir}/twistd-3 +ln -s ./trial-%{python3_version} %{buildroot}%{_bindir}/trial +ln -s ./twistd-%{python3_version} %{buildroot}%{_bindir}/twistd +chmod +x %{buildroot}%{python3_sitearch}/twisted/mail/test/pop3testserver.py +chmod +x %{buildroot}%{python3_sitearch}/twisted/trial/test/scripttest.py + +pathfix.py -pn -i %{__python2} %{buildroot}%{python2_sitearch} +pathfix.py -pn -i %{__python3} %{buildroot}%{python3_sitearch} +install -d %{buildroot}%{_mandir}/man1/ +cp -a docs/conch/man/*.1 %{buildroot}%{_mandir}/man1/ +cp -a docs/core/man/*.1 %{buildroot}%{_mandir}/man1/ +cp -a docs/mail/man/*.1 %{buildroot}%{_mandir}/man1/ + +%check +PATH=%{buildroot}%{_bindir}:$PATH PYTHONPATH=%{buildroot}%{python2_sitearch} %{buildroot}%{_bindir}/trial-2 twisted ||: +PATH=%{buildroot}%{_bindir}:$PATH PYTHONPATH=%{buildroot}%{python3_sitearch} %{buildroot}%{_bindir}/trial-3 twisted ||: + +%files -n python2-twisted +%doc CONTRIBUTING NEWS.rst README.rst LICENSE +%{_bindir}/{trial-2*,twistd-2*} +%{python2_sitearch}/twisted +%{python2_sitearch}/Twisted-%{version}-py?.?.egg-info + +%files -n python3-twisted +%doc CONTRIBUTING NEWS.rst README.rst html LICENSE +%{_bindir}/{trial-3*,twistd-3*} +%{python3_sitearch}/twisted +%{python3_sitearch}/Twisted-%{version}-py?.?.egg-info +%{_bindir}/{cftp,ckeygen,conch,mailmail,pyhtmlizer,tkconch,trial,twist,twistd} + +%files help +%{_mandir}/man1/{cftp.1*,ckeygen.1*,conch.1*,mailmail.1*,pyhtmlizer.1*,tkconch.1*,trial.1*,twistd.1*} + +%changelog +* Tue Feb 25 2020 Jiangping Hu - 18.9.0-3 +- Package init