From d8fd31b8c0576c4e32decfc907293175d131a2f3 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Micha=C5=82=20G=C3=B3rny?= Date: Fri, 24 Apr 2020 21:23:35 +0200 Subject: [PATCH] dev-python/twisted: Backport py3.8 fixes to 20.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Signed-off-by: Michał Górny --- .../files/twisted-20.3.0-py38-cgi.patch | 259 ++++++++++++++++++ .../files/twisted-20.3.0-py38-hmac.patch | 94 +++++++ dev-python/twisted/twisted-20.3.0.ebuild | 5 + 3 files changed, 358 insertions(+) create mode 100644 dev-python/twisted/files/twisted-20.3.0-py38-cgi.patch create mode 100644 dev-python/twisted/files/twisted-20.3.0-py38-hmac.patch diff --git a/dev-python/twisted/files/twisted-20.3.0-py38-cgi.patch b/dev-python/twisted/files/twisted-20.3.0-py38-cgi.patch new file mode 100644 index 000000000000..5151f297f7f4 --- /dev/null +++ b/dev-python/twisted/files/twisted-20.3.0-py38-cgi.patch @@ -0,0 +1,259 @@ +From 62ab0203c59c1f9788c53dfad4a212774094d05c Mon Sep 17 00:00:00 2001 +From: Craig Rodrigues +Date: Mon, 13 Apr 2020 01:22:23 -0700 +Subject: [PATCH 2/2] Merge 9801-rodrigc-cgi: Change import of cgi.parse_qs to + urllib.parse.parse_qs + +Author: rodrigc +Reviewer: hawkowl +Fixes: ticket:9801 +--- + src/twisted/web/client.py | 17 ++++----- + src/twisted/web/http.py | 49 ++++++++++++------------- + src/twisted/web/newsfragments/9801.misc | 0 + src/twisted/web/test/test_http.py | 41 +++------------------ + src/twisted/web/test/test_webclient.py | 5 +-- + 5 files changed, 38 insertions(+), 74 deletions(-) + create mode 100644 src/twisted/web/newsfragments/9801.misc + +diff --git a/src/twisted/web/client.py b/src/twisted/web/client.py +index 7e4642ef3..8209f5a5e 100644 +--- a/src/twisted/web/client.py ++++ b/src/twisted/web/client.py +@@ -12,15 +12,8 @@ import os + import collections + import warnings + +-try: +- from urlparse import urlunparse, urljoin, urldefrag +-except ImportError: +- from urllib.parse import urljoin, urldefrag +- from urllib.parse import urlunparse as _urlunparse +- +- def urlunparse(parts): +- result = _urlunparse(tuple([p.decode("charmap") for p in parts])) +- return result.encode("charmap") ++from urllib.parse import urljoin, urldefrag ++from urllib.parse import urlunparse as _urlunparse + + import zlib + from functools import wraps +@@ -51,6 +44,12 @@ from twisted.web._newclient import _ensureValidURI, _ensureValidMethod + + + ++def urlunparse(parts): ++ result = _urlunparse(tuple([p.decode("charmap") for p in parts])) ++ return result.encode("charmap") ++ ++ ++ + class PartialDownloadError(error.Error): + """ + Page was only partially downloaded, we got disconnected in middle. +diff --git a/src/twisted/web/http.py b/src/twisted/web/http.py +index b7afa8b0d..94d0ae81f 100644 +--- a/src/twisted/web/http.py ++++ b/src/twisted/web/http.py +@@ -66,27 +66,10 @@ import time + import calendar + import warnings + import os +-from io import BytesIO as StringIO +- +-try: +- from urlparse import ( +- ParseResult as ParseResultBytes, urlparse as _urlparse) +- from urllib import unquote +- from cgi import parse_header as _parseHeader +-except ImportError: +- from urllib.parse import ( +- ParseResultBytes, urlparse as _urlparse, unquote_to_bytes as unquote) +- +- def _parseHeader(line): +- # cgi.parse_header requires a str +- key, pdict = cgi.parse_header(line.decode('charmap')) +- +- # We want the key as bytes, and cgi.parse_multipart (which consumes +- # pdict) expects a dict of str keys but bytes values +- key = key.encode('charmap') +- pdict = {x:y.encode('charmap') for x, y in pdict.items()} +- return (key, pdict) ++from io import BytesIO + ++from urllib.parse import ( ++ ParseResultBytes, urlparse as _urlparse, unquote_to_bytes as unquote) + + from zope.interface import Attribute, Interface, implementer, provider + +@@ -163,6 +146,20 @@ monthname = [None, + weekdayname_lower = [name.lower() for name in weekdayname] + monthname_lower = [name and name.lower() for name in monthname] + ++ ++ ++def _parseHeader(line): ++ # cgi.parse_header requires a str ++ key, pdict = cgi.parse_header(line.decode('charmap')) ++ ++ # We want the key as bytes, and cgi.parse_multipart (which consumes ++ # pdict) expects a dict of str keys but bytes values ++ key = key.encode('charmap') ++ pdict = {x: y.encode('charmap') for x, y in pdict.items()} ++ return (key, pdict) ++ ++ ++ + def urlparse(url): + """ + Parse an URL into six components. +@@ -486,13 +483,15 @@ class _IDeprecatedHTTPChannelToRequestInterface(Interface): + + class StringTransport: + """ +- I am a StringIO wrapper that conforms for the transport API. I support ++ I am a BytesIO wrapper that conforms for the transport API. I support + the `writeSequence' method. + """ + def __init__(self): +- self.s = StringIO() ++ self.s = BytesIO() ++ + def writeSequence(self, seq): + self.s.write(b''.join(seq)) ++ + def __getattr__(self, attr): + return getattr(self.__dict__['s'], attr) + +@@ -513,7 +512,7 @@ class HTTPClient(basic.LineReceiver): + @type firstLine: C{bool} + + @ivar __buffer: The buffer that stores the response to the HTTP request. +- @type __buffer: A C{StringIO} object. ++ @type __buffer: A C{BytesIO} object. + + @ivar _header: Part or all of an HTTP request header. + @type _header: C{bytes} +@@ -579,7 +578,7 @@ class HTTPClient(basic.LineReceiver): + if self._header != b"": + # Only extract headers if there are any + self.extractHeader(self._header) +- self.__buffer = StringIO() ++ self.__buffer = BytesIO() + self.handleEndHeaders() + self.setRawMode() + return +@@ -665,7 +664,7 @@ def _getContentFile(length): + Get a writeable file-like object to which request content can be written. + """ + if length is not None and length < 100000: +- return StringIO() ++ return BytesIO() + return tempfile.TemporaryFile() + + +diff --git a/src/twisted/web/newsfragments/9801.misc b/src/twisted/web/newsfragments/9801.misc +new file mode 100644 +index 000000000..e69de29bb +diff --git a/src/twisted/web/test/test_http.py b/src/twisted/web/test/test_http.py +index a3067f732..4189b307c 100644 +--- a/src/twisted/web/test/test_http.py ++++ b/src/twisted/web/test/test_http.py +@@ -9,15 +9,11 @@ from __future__ import absolute_import, division + + import base64 + import calendar +-import cgi + import random + + import hamcrest + +-try: +- from urlparse import urlparse, urlunsplit, clear_cache +-except ImportError: +- from urllib.parse import urlparse, urlunsplit, clear_cache ++from urllib.parse import urlparse, urlunsplit, clear_cache, parse_qs + + from io import BytesIO + from itertools import cycle +@@ -28,7 +24,7 @@ from zope.interface import ( + ) + from zope.interface.verify import verifyObject + +-from twisted.python.compat import (_PY3, iterbytes, long, networkString, ++from twisted.python.compat import (iterbytes, long, networkString, + unicode, intToBytes) + from twisted.python.components import proxyForInterface + from twisted.python.failure import Failure +@@ -2019,33 +2015,6 @@ Content-Type: application/x-www-form-urlencoded + self.assertEqual(content, [networkString(query)]) + + +- def test_missingContentDisposition(self): +- """ +- If the C{Content-Disposition} header is missing, the request is denied +- as a bad request. +- """ +- req = b'''\ +-POST / HTTP/1.0 +-Content-Type: multipart/form-data; boundary=AaB03x +-Content-Length: 103 +- +---AaB03x +-Content-Type: text/plain +-Content-Transfer-Encoding: quoted-printable +- +-abasdfg +---AaB03x-- +-''' +- channel = self.runRequest(req, http.Request, success=False) +- self.assertEqual( +- channel.transport.value(), +- b"HTTP/1.1 400 Bad Request\r\n\r\n") +- +- if _PY3: +- test_missingContentDisposition.skip = ( +- "cgi.parse_multipart is much more error-tolerant on Python 3.") +- +- + def test_multipartProcessingFailure(self): + """ + When the multipart processing fails the client gets a 400 Bad Request. +@@ -2373,15 +2342,15 @@ ok + class QueryArgumentsTests(unittest.TestCase): + def testParseqs(self): + self.assertEqual( +- cgi.parse_qs(b"a=b&d=c;+=f"), ++ parse_qs(b"a=b&d=c;+=f"), + http.parse_qs(b"a=b&d=c;+=f")) + self.assertRaises( + ValueError, http.parse_qs, b"blah", strict_parsing=True) + self.assertEqual( +- cgi.parse_qs(b"a=&b=c", keep_blank_values=1), ++ parse_qs(b"a=&b=c", keep_blank_values=1), + http.parse_qs(b"a=&b=c", keep_blank_values=1)) + self.assertEqual( +- cgi.parse_qs(b"a=&b=c"), ++ parse_qs(b"a=&b=c"), + http.parse_qs(b"a=&b=c")) + + +diff --git a/src/twisted/web/test/test_webclient.py b/src/twisted/web/test/test_webclient.py +index 680e02780..672594993 100644 +--- a/src/twisted/web/test/test_webclient.py ++++ b/src/twisted/web/test/test_webclient.py +@@ -11,10 +11,7 @@ import io + import os + from errno import ENOSPC + +-try: +- from urlparse import urlparse, urljoin +-except ImportError: +- from urllib.parse import urlparse, urljoin ++from urllib.parse import urlparse, urljoin + + from twisted.python.compat import networkString, nativeString, intToBytes + from twisted.trial import unittest, util +-- +2.26.2 + diff --git a/dev-python/twisted/files/twisted-20.3.0-py38-hmac.patch b/dev-python/twisted/files/twisted-20.3.0-py38-hmac.patch new file mode 100644 index 000000000000..1c1ee01b2187 --- /dev/null +++ b/dev-python/twisted/files/twisted-20.3.0-py38-hmac.patch @@ -0,0 +1,94 @@ +From 653fb2aea0ca1f60558917d52f4ff0c33cd7b067 Mon Sep 17 00:00:00 2001 +From: Craig Rodrigues +Date: Sun, 12 Apr 2020 14:28:23 -0700 +Subject: [PATCH 1/2] Add digestmod parameter to HMAC.__init__() invocations + +This parameter is now required on Python 3.8+ +--- + src/twisted/cred/credentials.py | 3 ++- + src/twisted/cred/test/test_cramauth.py | 11 ++++++++--- + src/twisted/mail/test/test_pop3.py | 4 +++- + 3 files changed, 13 insertions(+), 5 deletions(-) + +diff --git a/src/twisted/cred/credentials.py b/src/twisted/cred/credentials.py +index 5469e5158..67c24cb01 100644 +--- a/src/twisted/cred/credentials.py ++++ b/src/twisted/cred/credentials.py +@@ -441,7 +441,8 @@ class CramMD5Credentials(object): + + + def checkPassword(self, password): +- verify = hexlify(hmac.HMAC(password, self.challenge).digest()) ++ verify = hexlify(hmac.HMAC(password, self.challenge, ++ digestmod=md5).digest()) + return verify == self.response + + +diff --git a/src/twisted/cred/test/test_cramauth.py b/src/twisted/cred/test/test_cramauth.py +index 1ee08712b..d21f2f68c 100644 +--- a/src/twisted/cred/test/test_cramauth.py ++++ b/src/twisted/cred/test/test_cramauth.py +@@ -7,6 +7,8 @@ Tests for L{twisted.cred}'s implementation of CRAM-MD5. + + from __future__ import division, absolute_import + ++import hashlib ++ + from hmac import HMAC + from binascii import hexlify + +@@ -39,7 +41,8 @@ class CramMD5CredentialsTests(TestCase): + """ + c = CramMD5Credentials() + chal = c.getChallenge() +- c.response = hexlify(HMAC(b'secret', chal).digest()) ++ c.response = hexlify(HMAC(b'secret', chal, ++ digestmod=hashlib.md5).digest()) + self.assertTrue(c.checkPassword(b'secret')) + + +@@ -61,7 +64,8 @@ class CramMD5CredentialsTests(TestCase): + """ + c = CramMD5Credentials() + chal = c.getChallenge() +- c.response = hexlify(HMAC(b'thewrongsecret', chal).digest()) ++ c.response = hexlify(HMAC(b'thewrongsecret', chal, ++ digestmod=hashlib.md5).digest()) + self.assertFalse(c.checkPassword(b'secret')) + + +@@ -75,7 +79,8 @@ class CramMD5CredentialsTests(TestCase): + chal = c.getChallenge() + c.setResponse(b" ".join( + (b"squirrel", +- hexlify(HMAC(b'supersecret', chal).digest())))) ++ hexlify(HMAC(b'supersecret', chal, ++ digestmod=hashlib.md5).digest())))) + self.assertTrue(c.checkPassword(b'supersecret')) + self.assertEqual(c.username, b"squirrel") + +diff --git a/src/twisted/mail/test/test_pop3.py b/src/twisted/mail/test/test_pop3.py +index 4a59c3b49..ea513487c 100644 +--- a/src/twisted/mail/test/test_pop3.py ++++ b/src/twisted/mail/test/test_pop3.py +@@ -11,6 +11,7 @@ import hmac + import base64 + import itertools + ++from hashlib import md5 + from collections import OrderedDict + from io import BytesIO + +@@ -1097,7 +1098,8 @@ class SASLTests(unittest.TestCase): + p.lineReceived(b"AUTH CRAM-MD5") + chal = s.getvalue().splitlines()[-1][2:] + chal = base64.decodestring(chal) +- response = hmac.HMAC(b'testpassword', chal).hexdigest().encode("ascii") ++ response = hmac.HMAC(b'testpassword', chal, ++ digestmod=md5).hexdigest().encode("ascii") + + p.lineReceived( + base64.encodestring(b'testuser ' + response).rstrip(b'\n')) +-- +2.26.2 + diff --git a/dev-python/twisted/twisted-20.3.0.ebuild b/dev-python/twisted/twisted-20.3.0.ebuild index 5ec79085b942..1f547fbdba48 100644 --- a/dev-python/twisted/twisted-20.3.0.ebuild +++ b/dev-python/twisted/twisted-20.3.0.ebuild @@ -81,6 +81,11 @@ DEPEND=" S=${WORKDIR}/${TWISTED_P} python_prepare_all() { + local PATCHES=( + "${FILESDIR}"/twisted-20.3.0-py38-cgi.patch + "${FILESDIR}"/twisted-20.3.0-py38-hmac.patch + ) + # upstream test for making releases; not very useful and requires # sphinx (including on py2) rm src/twisted/python/test/test_release.py || die -- 2.26.2