Runnable with both Python3 and Python2, with PyQt4 #2249

Open
kashikoibumi wants to merge 60 commits from kashikoibumi/py3 into v0.6
12 changed files with 70 additions and 38 deletions
Showing only changes of commit df2631c4ee - Show all commits

View File

@ -476,7 +476,7 @@ class MessageList_TimeWidget(BMTableWidgetItem):
msgid is available by QtCore.Qt.UserRole msgid is available by QtCore.Qt.UserRole
""" """
def __init__(self, label=None, unread=False, timestamp=None, msgid=''): def __init__(self, label=None, unread=False, timestamp=None, msgid=b''):
super(MessageList_TimeWidget, self).__init__(label, unread) super(MessageList_TimeWidget, self).__init__(label, unread)
self.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid)) self.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid))
self.setData(TimestampRole, int(timestamp)) self.setData(TimestampRole, int(timestamp))

View File

@ -28,8 +28,6 @@ class Inventory:
# cheap inheritance copied from asyncore # cheap inheritance copied from asyncore
def __getattr__(self, attr): def __getattr__(self, attr):
if attr == "__contains__":
self.numberOfInventoryLookupsPerformed += 1
try: try:
realRet = getattr(self._realInventory, attr) realRet = getattr(self._realInventory, attr)
except AttributeError: except AttributeError:
@ -40,6 +38,10 @@ class Inventory:
else: else:
return realRet return realRet
def __contains__(self, key):
self.numberOfInventoryLookupsPerformed += 1
return key in self._realInventory
# hint for pylint: this is dictionary like object # hint for pylint: this is dictionary like object
def __getitem__(self, key): def __getitem__(self, key):
return self._realInventory[key] return self._realInventory[key]

View File

@ -724,21 +724,6 @@ class dispatcher(object):
if why.args[0] not in (ENOTCONN, EBADF): if why.args[0] not in (ENOTCONN, EBADF):
raise raise
# cheap inheritance, used to pass all other attribute
# references to the underlying socket object.
def __getattr__(self, attr):
try:
retattr = getattr(self.socket, attr)
except AttributeError:
raise AttributeError(
"%s instance has no attribute '%s'"
% (self.__class__.__name__, attr))
else:
msg = "%(me)s.%(attr)s is deprecated; use %(me)s.socket.%(attr)s"\
" instead" % {'me': self.__class__.__name__, 'attr': attr}
warnings.warn(msg, DeprecationWarning, stacklevel=2)
return retattr
# log and log_info may be overridden to provide more sophisticated # log and log_info may be overridden to provide more sophisticated
# logging and warning methods. In general, log is for 'hit' logging # logging and warning methods. In general, log is for 'hit' logging
# and 'log_info' is for informational, warning and error logging. # and 'log_info' is for informational, warning and error logging.

View File

@ -9,6 +9,7 @@ import re
import socket import socket
import struct import struct
import time import time
import six
# magic imports! # magic imports!
import addresses import addresses
@ -34,6 +35,18 @@ from .objectracker import ObjectTracker, missingObjects
logger = logging.getLogger('default') logger = logging.getLogger('default')
def _hoststr(v):
if six.PY3:
return v
else: # assume six.PY2
return str(v)
def _restr(v):
if six.PY3:
return v.decode("utf-8", "replace")
else: # assume six.PY2
return v
class BMProtoError(ProxyError): class BMProtoError(ProxyError):
"""A Bitmessage Protocol Base Error""" """A Bitmessage Protocol Base Error"""
errorCodes = ("Protocol error") errorCodes = ("Protocol error")
@ -115,7 +128,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
if not self.invalid: if not self.invalid:
try: try:
retval = getattr( retval = getattr(
self, "bm_command_" + str(self.command).lower())() self, "bm_command_" + self.command.decode("utf-8", "replace").lower())()
except AttributeError: except AttributeError:
# unimplemented command # unimplemented command
logger.debug('unimplemented command %s', self.command) logger.debug('unimplemented command %s', self.command)
@ -169,16 +182,16 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
# protocol.checkIPAddress() # protocol.checkIPAddress()
services, host, port = self.decode_payload_content("Q16sH") services, host, port = self.decode_payload_content("Q16sH")
if host[0:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': if host[0:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
host = socket.inet_ntop(socket.AF_INET, str(host[12:16])) host = socket.inet_ntop(socket.AF_INET, _hoststr(host[12:16]))
elif host[0:6] == b'\xfd\x87\xd8\x7e\xeb\x43': elif host[0:6] == b'\xfd\x87\xd8\x7e\xeb\x43':
# Onion, based on BMD/bitcoind # Onion, based on BMD/bitcoind
host = base64.b32encode(host[6:]).lower() + b".onion" host = base64.b32encode(host[6:]).lower() + b".onion"
else: else:
host = socket.inet_ntop(socket.AF_INET6, str(host)) host = socket.inet_ntop(socket.AF_INET6, _hoststr(host))
if host == b"": if host == b"":
# This can happen on Windows systems which are not 64-bit # This can happen on Windows systems which are not 64-bit
# compatible so let us drop the IPv6 address. # compatible so let us drop the IPv6 address.
host = socket.inet_ntop(socket.AF_INET, str(host[12:16])) host = socket.inet_ntop(socket.AF_INET, _hoststr(host[12:16]))
return Node(services, host, port) return Node(services, host, port)
@ -534,7 +547,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker):
self.append_write_buf(protocol.CreatePacket(b'verack')) self.append_write_buf(protocol.CreatePacket(b'verack'))
self.verackSent = True self.verackSent = True
ua_valid = re.match( ua_valid = re.match(
r'^/[a-zA-Z]+:[0-9]+\.?[\w\s\(\)\./:;-]*/$', self.userAgent) r'^/[a-zA-Z]+:[0-9]+\.?[\w\s\(\)\./:;-]*/$', _restr(self.userAgent))
if not ua_valid: if not ua_valid:
self.userAgent = b'/INVALID:0/' self.userAgent = b'/INVALID:0/'
if not self.isOutbound: if not self.isOutbound:

View File

@ -14,10 +14,16 @@ from queues import queue, portCheckerQueue
logger = logging.getLogger('default') logger = logging.getLogger('default')
def _ends_with(s, tail):
try:
return s.endswith(tail)
except:
return s.decode("utf-8", "replace").endswith(tail)
def getDiscoveredPeer(): def getDiscoveredPeer():
"""Get a peer from the local peer discovery list""" """Get a peer from the local peer discovery list"""
try: try:
peer = random.choice(state.discoveredPeers.keys()) # nosec B311 peer = random.choice(list(state.discoveredPeers.keys())) # nosec B311
except (IndexError, KeyError): except (IndexError, KeyError):
raise ValueError raise ValueError
try: try:
@ -45,7 +51,7 @@ def chooseConnection(stream):
return getDiscoveredPeer() return getDiscoveredPeer()
for _ in range(50): for _ in range(50):
peer = random.choice( # nosec B311 peer = random.choice( # nosec B311
knownnodes.knownNodes[stream].keys()) list(knownnodes.knownNodes[stream].keys()))
try: try:
peer_info = knownnodes.knownNodes[stream][peer] peer_info = knownnodes.knownNodes[stream][peer]
if peer_info.get('self'): if peer_info.get('self'):
@ -57,10 +63,10 @@ def chooseConnection(stream):
if haveOnion: if haveOnion:
# do not connect to raw IP addresses # do not connect to raw IP addresses
# --keep all traffic within Tor overlay # --keep all traffic within Tor overlay
if onionOnly and not peer.host.endswith('.onion'): if onionOnly and not _ends_with(peer.host, '.onion'):
continue continue
# onion addresses have a higher priority when SOCKS # onion addresses have a higher priority when SOCKS
if peer.host.endswith('.onion') and rating > 0: if _ends_with(peer.host, '.onion') and rating > 0:
rating = 1 rating = 1
# TODO: need better check # TODO: need better check
elif not peer.host.startswith('bootstrap'): elif not peer.host.startswith('bootstrap'):

View File

@ -25,6 +25,12 @@ from .udp import UDPSocket
logger = logging.getLogger('default') logger = logging.getLogger('default')
def _ends_with(s, tail):
try:
return s.endswith(tail)
except:
return s.decode("utf-8", "replace").endswith(tail)
class BMConnectionPool(object): class BMConnectionPool(object):
"""Pool of all existing connections""" """Pool of all existing connections"""
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
@ -160,8 +166,8 @@ class BMConnectionPool(object):
@staticmethod @staticmethod
def getListeningIP(): def getListeningIP():
"""What IP are we supposed to be listening on?""" """What IP are we supposed to be listening on?"""
if config.safeGet( if _ends_with(config.safeGet(
"bitmessagesettings", "onionhostname", "").endswith(".onion"): "bitmessagesettings", "onionhostname", ""), ".onion"):
host = config.safeGet( host = config.safeGet(
"bitmessagesettings", "onionbindip") "bitmessagesettings", "onionbindip")
else: else:
@ -314,7 +320,7 @@ class BMConnectionPool(object):
continue continue
try: try:
if chosen.host.endswith(".onion") and Proxy.onion_proxy: if _ends_with(chosen.host, ".onion") and Proxy.onion_proxy:
if onionsocksproxytype == "SOCKS5": if onionsocksproxytype == "SOCKS5":
self.addConnection(Socks5BMConnection(chosen)) self.addConnection(Socks5BMConnection(chosen))
elif onionsocksproxytype == "SOCKS4a": elif onionsocksproxytype == "SOCKS4a":

View File

@ -185,8 +185,8 @@ class Dandelion: # pylint: disable=old-style-class
try: try:
# random two connections # random two connections
self.stem = sample( self.stem = sample(
connectionpool.BMConnectionPool( sorted(connectionpool.BMConnectionPool(
).outboundConnections.values(), MAX_STEMS) ).outboundConnections.values()), MAX_STEMS)
# not enough stems available # not enough stems available
except ValueError: except ValueError:
self.stem = connectionpool.BMConnectionPool( self.stem = connectionpool.BMConnectionPool(

View File

@ -106,6 +106,12 @@ def addKnownNode(stream, peer, lastseen=None, is_self=False):
Returns True if added a new node. Returns True if added a new node.
""" """
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
if not isinstance(peer.host, str):
try:
peer = Peer(peer.host.decode("ascii"), peer.port)
except UnicodeDecodeError as err:
logger.warning("Invalid host: {}".format(peer.host.decode("ascii", "backslashreplace")))
return
if isinstance(stream, Iterable): if isinstance(stream, Iterable):
with knownNodesLock: with knownNodesLock:
for s in stream: for s in stream:
@ -151,7 +157,7 @@ def createDefaultKnownNodes():
def readKnownNodes(): def readKnownNodes():
"""Load knownnodes from filesystem""" """Load knownnodes from filesystem"""
try: try:
with open(state.appdata + 'knownnodes.dat', 'rb') as source: with open(state.appdata + 'knownnodes.dat', 'r') as source:
with knownNodesLock: with knownNodesLock:
try: try:
json_deserialize_knownnodes(source) json_deserialize_knownnodes(source)

View File

@ -14,6 +14,12 @@ from .node import Peer
logger = logging.getLogger('default') logger = logging.getLogger('default')
def _ends_with(s, tail):
try:
return s.endswith(tail)
except:
return s.decode("utf-8", "replace").endswith(tail)
class ProxyError(Exception): class ProxyError(Exception):
"""Base proxy exception class""" """Base proxy exception class"""
errorCodes = ("Unknown error",) errorCodes = ("Unknown error",)
@ -125,7 +131,7 @@ class Proxy(AdvancedDispatcher):
self.auth = None self.auth = None
self.connect( self.connect(
self.onion_proxy self.onion_proxy
if address.host.endswith(".onion") and self.onion_proxy else if _ends_with(address.host, ".onion") and self.onion_proxy else
self.proxy self.proxy
) )

View File

@ -40,6 +40,12 @@ maximumAgeOfNodesThatIAdvertiseToOthers = 10800 #: Equals three hours
maximumTimeOffsetWrongCount = 3 #: Connections with wrong time offset maximumTimeOffsetWrongCount = 3 #: Connections with wrong time offset
def _ends_with(s, tail):
try:
return s.endswith(tail)
except:
return s.decode("utf-8", "replace").endswith(tail)
class TCPConnection(BMProto, TLSDispatcher): class TCPConnection(BMProto, TLSDispatcher):
# pylint: disable=too-many-instance-attributes # pylint: disable=too-many-instance-attributes
""" """
@ -195,7 +201,7 @@ class TCPConnection(BMProto, TLSDispatcher):
(k, v) for k, v in six.iteritems(nodes) (k, v) for k, v in six.iteritems(nodes)
if v["lastseen"] > int(time.time()) if v["lastseen"] > int(time.time())
- maximumAgeOfNodesThatIAdvertiseToOthers - maximumAgeOfNodesThatIAdvertiseToOthers
and v["rating"] >= 0 and not k.host.endswith('.onion') and v["rating"] >= 0 and not _ends_with(k.host, '.onion')
] ]
# sent 250 only if the remote isn't interested in it # sent 250 only if the remote isn't interested in it
elemCount = min( elemCount = min(

View File

@ -81,7 +81,7 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes
return True return True
remoteport = False remoteport = False
for seenTime, stream, _, ip, port in addresses: for seenTime, stream, _, ip, port in addresses:
decodedIP = protocol.checkIPAddress(str(ip)) decodedIP = protocol.checkIPAddress(ip)
if stream not in network.connectionpool.pool.streams: if stream not in network.connectionpool.pool.streams:
continue continue
if (seenTime < time.time() - protocol.MAX_TIME_OFFSET if (seenTime < time.time() - protocol.MAX_TIME_OFFSET

View File

@ -170,7 +170,7 @@ def checkIPAddress(host, private=False):
otherwise returns False otherwise returns False
""" """
if host[0:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF': if host[0:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xFF\xFF':
hostStandardFormat = socket.inet_ntop(socket.AF_INET, host[12:]) hostStandardFormat = socket.inet_ntop(socket.AF_INET, bytes(host[12:]))
return checkIPv4Address(host[12:], hostStandardFormat, private) return checkIPv4Address(host[12:], hostStandardFormat, private)
elif host[0:6] == b'\xfd\x87\xd8\x7e\xeb\x43': elif host[0:6] == b'\xfd\x87\xd8\x7e\xeb\x43':
# Onion, based on BMD/bitcoind # Onion, based on BMD/bitcoind
@ -419,7 +419,7 @@ def assembleVersionMessage(
return CreatePacket(b'version', payload) return CreatePacket(b'version', payload)
def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''): def assembleErrorMessage(fatal=0, banTime=0, inventoryVector=b'', errorText=''):
""" """
Construct the payload of an error message, Construct the payload of an error message,
return the resulting bytes of running `CreatePacket` on it return the resulting bytes of running `CreatePacket` on it
@ -428,6 +428,8 @@ def assembleErrorMessage(fatal=0, banTime=0, inventoryVector='', errorText=''):
payload += encodeVarint(banTime) payload += encodeVarint(banTime)
payload += encodeVarint(len(inventoryVector)) payload += encodeVarint(len(inventoryVector))
payload += inventoryVector payload += inventoryVector
if isinstance(errorText, str):
errorText = errorText.encode("utf-8", "replace")
payload += encodeVarint(len(errorText)) payload += encodeVarint(len(errorText))
payload += errorText payload += errorText
return CreatePacket(b'error', payload) return CreatePacket(b'error', payload)