Skip to content

Commit

Permalink
Merge branch 'trunk' into 8858-tracebacks
Browse files Browse the repository at this point in the history
  • Loading branch information
glyph authored Oct 22, 2016
2 parents 0ad862e + aa3a7a7 commit 0149bcc
Show file tree
Hide file tree
Showing 20 changed files with 123 additions and 153 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ matrix:
# twistedchecker job was introduce as an experimental job.
# Once it is stable we can enforce it
- env: TOXENV=txchecker-travis
- osx_image: xcode7.1


addons:
Expand Down
4 changes: 3 additions & 1 deletion docs/core/development/policy/compatibility-policy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -130,8 +130,10 @@ Test Changes
No code or data in a test package should be imported or used by a non-test package within Twisted.
By doing so, there's no chance anything could access these objects by going through the public API.

Test code and test helpers are considered private API and it should be imported outside
Test code and test helpers are considered private API and should not be imported outside
of the Twisted testing infrastructure.
As an exception to this, :api:`twisted.test.proto_helpers` is considered a public API
(see `#6435 <https://twistedmatrix.com/trac/ticket/6435>`_ for more discussion).


Private Changes
Expand Down
2 changes: 0 additions & 2 deletions docs/installation/howto/optional.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@ The following optional dependencies are supported:
* `idna`_

* **conch** - packages for working with conch/SSH.
* `gmpy`_
* `pyasn1`_
* `cryptography`_

Expand All @@ -57,7 +56,6 @@ The following optional dependencies are supported:
.. _pydoctor: https://pypi.python.org/pypi/pydoctor
.. _pyOpenSSL: https://pypi.python.org/pypi/pyOpenSSL
.. _service_identity: https://pypi.python.org/pypi/service_identity
.. _gmpy: https://pypi.python.org/pypi/gmpy/1.17
.. _pyasn1: https://pypi.python.org/pypi/pyasn1
.. _cryptography: https://pypi.python.org/pypi/cryptography
.. _SOAPpy: https://pypi.python.org/pypi/SOAPpy
Expand Down
8 changes: 6 additions & 2 deletions src/twisted/conch/scripts/ckeygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from twisted.conch.ssh import keys
from twisted.python import failure, filepath, log, usage
from twisted.python.compat import raw_input, _PY3



Expand Down Expand Up @@ -184,7 +185,7 @@ def changePassPhrase(options):
except (keys.EncryptedKeyError, keys.BadKeyError) as e:
sys.exit('Could not change passphrase: %s' % (e,))

with open(options['filename'], 'w') as fd:
with open(options['filename'], 'wb') as fd:
fd.write(newkeydata)

print('Your identification has been saved with the new passphrase.')
Expand All @@ -202,7 +203,10 @@ def displayPublicKey(options):
options['pass'] = getpass.getpass('Enter passphrase: ')
key = keys.Key.fromFile(
options['filename'], passphrase = options['pass'])
print(key.public().toString('openssh'))
displayKey = key.public().toString('openssh')
if _PY3:
displayKey = displayKey.decode("ascii")
print(displayKey)



Expand Down
61 changes: 9 additions & 52 deletions src/twisted/conch/ssh/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@
Maintainer: Paul Swartz
"""

from __future__ import absolute_import, division

import struct

from twisted.conch.ssh._cryptography_backports import (
intFromBytes as int_from_bytes, intToBytes as int_to_bytes)

from twisted.python.compat import _PY3, long
from twisted.python.deprecate import deprecated
from twisted.python.versions import Version

__all__ = ["NS", "getNS", "MP", "getMP", "ffs"]



def NS(t):
Expand Down Expand Up @@ -87,56 +92,8 @@ def ffs(c, s):



getMP_py = getMP
MP_py = MP
_MPpow_py = _MPpow
pyPow = pow



def _fastgetMP(data, count=1):
mp = []
c = 0
for i in range(count):
length = struct.unpack('!L', data[c:c + 4])[0]
mp.append(long(
gmpy.mpz(data[c + 4:c + 4 + length][::-1] + b'\x00', 256)))
c += length + 4
return tuple(mp) + (data[c:],)



def _fastMP(i):
i2 = gmpy.mpz(i).binary()[::-1]
return struct.pack('!L', len(i2)) + i2



def _fastMPpow(x, y, z=None):
r = pyPow(gmpy.mpz(x), y, z).binary()[::-1]
return struct.pack('!L', len(r)) + r



@deprecated(Version("Twisted", 16, 5, 0))
def install():
global getMP, MP, _MPpow
getMP = _fastgetMP
MP = _fastMP
_MPpow = _fastMPpow
# XXX: We override builtin pow so that other code can benefit too.
# This is monkeypatching, and therefore VERY BAD.
def _fastpow(x, y, z=None, mpz=gmpy.mpz):
if type(x) in (long, int):
x = mpz(x)
return pyPow(x, y, z)
if _PY3:
__builtins__['pow'] = _fastpow
else:
import __builtin__
__builtin__.pow = _fastpow

try:
import gmpy
install()
except ImportError:
# This used to install gmpy, but is technically public API, so just do
# nothing.
pass
10 changes: 8 additions & 2 deletions src/twisted/conch/ssh/keys.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from twisted.conch.ssh.common import int_from_bytes, int_to_bytes
from twisted.python import randbytes
from twisted.python.compat import (
iterbytes, long, izip, nativeString, _PY3,
iterbytes, long, izip, nativeString, unicode, _PY3,
_b64decodebytes as decodebytes, _b64encodebytes as encodebytes)
from twisted.python.constants import NamedConstant, Names
from twisted.python.deprecate import deprecated, getDeprecationWarningString
Expand Down Expand Up @@ -144,6 +144,10 @@ def fromString(cls, data, type=None, passphrase=None):
@rtype: L{Key}
@return: The loaded key.
"""
if isinstance(data, unicode):
data = data.encode("utf-8")
if isinstance(passphrase, unicode):
passphrase = passphrase.encode("utf-8")
if type is None:
type = cls._guessStringType(data)
if type is None:
Expand Down Expand Up @@ -1024,10 +1028,12 @@ def toString(self, type, extra=None):
is not part of the key itself. For public OpenSSH keys, this is
a comment. For private OpenSSH keys, this is a passphrase to
encrypt with.
@type extra: L{bytes} or L{None}
@type extra: L{bytes} or L{unicode} or L{None}
@rtype: L{bytes}
"""
if isinstance(extra, unicode):
extra = extra.encode("utf-8")
method = getattr(self, '_toString_%s' % (type.upper(),), None)
if method is None:
raise BadKeyError('unknown key type: %s' % (type,))
Expand Down
56 changes: 38 additions & 18 deletions src/twisted/conch/test/test_ckeygen.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
Tests for L{twisted.conch.scripts.ckeygen}.
"""

import __builtin__
import getpass
import sys
from StringIO import StringIO

from io import BytesIO, StringIO

from twisted.python.compat import unicode, _PY3
from twisted.python.reflect import requireModule

if requireModule('cryptography') and requireModule('pyasn1'):
Expand Down Expand Up @@ -41,7 +42,7 @@ def makeGetpass(*passphrases):
passphrases = iter(passphrases)

def fakeGetpass(_):
return passphrases.next()
return next(passphrases)

return fakeGetpass

Expand All @@ -53,10 +54,12 @@ class KeyGenTests(TestCase):
"""
def setUp(self):
"""
Patch C{sys.stdout} with a L{StringIO} instance to tests can make
assertions about what's printed.
Patch C{sys.stdout} so tests can make assertions about what's printed.
"""
self.stdout = StringIO()
if _PY3:
self.stdout = StringIO()
else:
self.stdout = BytesIO()
self.patch(sys, 'stdout', self.stdout)


Expand Down Expand Up @@ -231,7 +234,8 @@ def test_saveKeyNoFilename(self):
base.makedirs()
keyPath = base.child('custom_key').path

self.patch(__builtin__, 'raw_input', lambda _: keyPath)
import twisted.conch.scripts.ckeygen
self.patch(twisted.conch.scripts.ckeygen, 'raw_input', lambda _: keyPath)
key = Key.fromString(privateRSA_openssh)
_saveKey(key, {'filename': None, 'no-passphrase': True,
'format': 'md5-hex'})
Expand All @@ -250,8 +254,11 @@ def test_displayPublicKey(self):
pubKey = Key.fromString(publicRSA_openssh)
FilePath(filename).setContent(privateRSA_openssh)
displayPublicKey({'filename': filename})
displayed = self.stdout.getvalue().strip('\n')
if isinstance(displayed, unicode):
displayed = displayed.encode("ascii")
self.assertEqual(
self.stdout.getvalue().strip('\n'),
displayed,
pubKey.toString('openssh'))


Expand All @@ -264,8 +271,11 @@ def test_displayPublicKeyEncrypted(self):
pubKey = Key.fromString(publicRSA_openssh)
FilePath(filename).setContent(privateRSA_openssh_encrypted)
displayPublicKey({'filename': filename, 'pass': 'encrypted'})
displayed = self.stdout.getvalue().strip('\n')
if isinstance(displayed, unicode):
displayed = displayed.encode("ascii")
self.assertEqual(
self.stdout.getvalue().strip('\n'),
displayed,
pubKey.toString('openssh'))


Expand All @@ -279,8 +289,11 @@ def test_displayPublicKeyEncryptedPassphrasePrompt(self):
FilePath(filename).setContent(privateRSA_openssh_encrypted)
self.patch(getpass, 'getpass', lambda x: 'encrypted')
displayPublicKey({'filename': filename})
displayed = self.stdout.getvalue().strip('\n')
if isinstance(displayed, unicode):
displayed = displayed.encode("ascii")
self.assertEqual(
self.stdout.getvalue().strip('\n'),
displayed,
pubKey.toString('openssh'))


Expand Down Expand Up @@ -392,13 +405,16 @@ def test_changePassphraseBadKey(self):
key.
"""
filename = self.mktemp()
FilePath(filename).setContent('foobar')
FilePath(filename).setContent(b'foobar')
error = self.assertRaises(
SystemExit, changePassPhrase, {'filename': filename})
self.assertEqual(
"Could not change passphrase: cannot guess the type of 'foobar'",
str(error))
self.assertEqual('foobar', FilePath(filename).getContent())

if _PY3:
expected = "Could not change passphrase: cannot guess the type of b'foobar'"
else:
expected = "Could not change passphrase: cannot guess the type of 'foobar'"
self.assertEqual(expected, str(error))
self.assertEqual(b'foobar', FilePath(filename).getContent())


def test_changePassphraseCreateError(self):
Expand Down Expand Up @@ -442,9 +458,13 @@ def toString(*args, **kwargs):
SystemExit, changePassPhrase,
{'filename': filename, 'newpass': 'newencrypt'})

self.assertEqual(
"Could not change passphrase: "
"cannot guess the type of ''", str(error))
if _PY3:
expected = (
"Could not change passphrase: cannot guess the type of b''")
else:
expected = (
"Could not change passphrase: cannot guess the type of ''")
self.assertEqual(expected, str(error))

self.assertEqual(privateRSA_openssh, FilePath(filename).getContent())

Expand Down
61 changes: 11 additions & 50 deletions src/twisted/conch/test/test_ssh.py
Original file line number Diff line number Diff line change
Expand Up @@ -970,58 +970,19 @@ def test_notEnoughData(self):
self.assertRaises(struct.error, self.getMP, b'\x02\x00')



class PyMPTests(MPTests):
"""
Tests for the python implementation of L{common.getMP}.
"""
getMP = staticmethod(common.getMP_py)



class GMPYMPTests(MPTests):
class GMPYInstallDeprecationTests(unittest.TestCase):
"""
Tests for the gmpy implementation of L{common.getMP}.
Tests for the deprecation of former GMPY accidental public API.
"""
getMP = staticmethod(common._fastgetMP)


class BuiltinPowHackTests(unittest.TestCase):
"""
Tests that the builtin pow method is still correct after
L{twisted.conch.ssh.common} monkeypatches it to use gmpy.
"""

def test_floatBase(self):
"""
pow gives the correct result when passed a base of type float with a
non-integer value.
"""
self.assertEqual(6.25, pow(2.5, 2))

def test_intBase(self):
"""
pow gives the correct result when passed a base of type int.
"""
self.assertEqual(81, pow(3, 4))

def test_longBase(self):
"""
pow gives the correct result when passed a base of type long.
"""
self.assertEqual(81, pow(3, 4))

def test_mpzBase(self):
def test_deprecated(self):
"""
pow gives the correct result when passed a base of type gmpy.mpz.
L{twisted.conch.ssh.common.install} is deprecated.
"""
if gmpy is None:
raise unittest.SkipTest('gmpy not available')
self.assertEqual(81, pow(gmpy.mpz(3), 4))


try:
import gmpy
except ImportError:
GMPYMPTests.skip = "gmpy not available"
gmpy = None
common.install()
warnings = self.flushWarnings([self.test_deprecated])
self.assertEqual(len(warnings), 1)
self.assertEqual(
warnings[0]["message"],
"twisted.conch.ssh.common.install was deprecated in Twisted 16.5.0"
)
1 change: 1 addition & 0 deletions src/twisted/conch/topfiles/8079.removal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
twisted.conch.ssh no longer uses gmpy, if available. gmpy is unmaintained, does not have binary wheels for any platforms, and an alternative for higher performance is available in the form of PyPy.
1 change: 1 addition & 0 deletions src/twisted/conch/topfiles/8855.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ckeygen has been ported to Python 3
4 changes: 2 additions & 2 deletions src/twisted/internet/defer.py
Original file line number Diff line number Diff line change
Expand Up @@ -729,7 +729,7 @@ def __iter__(self):
return self


def __send__(self, value=None):
def send(self, value=None):
result = getattr(self, 'result', _NO_RESULT)
if result is _NO_RESULT:
return self
Expand All @@ -738,7 +738,7 @@ def __send__(self, value=None):

# For PEP-492 support (async/await)
__await__ = __iter__
__next__ = __send__
__next__ = send



Expand Down
Loading

0 comments on commit 0149bcc

Please sign in to comment.