diff --git a/src/bitmessageqt/foldertree.py b/src/bitmessageqt/foldertree.py index b1133f56..41a5da83 100644 --- a/src/bitmessageqt/foldertree.py +++ b/src/bitmessageqt/foldertree.py @@ -476,7 +476,7 @@ class MessageList_TimeWidget(BMTableWidgetItem): 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) self.setData(QtCore.Qt.UserRole, QtCore.QByteArray(msgid)) self.setData(TimestampRole, int(timestamp)) diff --git a/src/inventory.py b/src/inventory.py index 5b739e84..8356262c 100644 --- a/src/inventory.py +++ b/src/inventory.py @@ -28,8 +28,6 @@ class Inventory: # cheap inheritance copied from asyncore def __getattr__(self, attr): - if attr == "__contains__": - self.numberOfInventoryLookupsPerformed += 1 try: realRet = getattr(self._realInventory, attr) except AttributeError: @@ -40,6 +38,10 @@ class Inventory: else: return realRet + def __contains__(self, key): + self.numberOfInventoryLookupsPerformed += 1 + return key in self._realInventory + # hint for pylint: this is dictionary like object def __getitem__(self, key): return self._realInventory[key] diff --git a/src/network/asyncore_pollchoose.py b/src/network/asyncore_pollchoose.py index a41145a1..5de3a18f 100644 --- a/src/network/asyncore_pollchoose.py +++ b/src/network/asyncore_pollchoose.py @@ -724,21 +724,6 @@ class dispatcher(object): if why.args[0] not in (ENOTCONN, EBADF): 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 # logging and warning methods. In general, log is for 'hit' logging # and 'log_info' is for informational, warning and error logging. diff --git a/src/network/bmproto.py b/src/network/bmproto.py index ab23cce5..c051c252 100644 --- a/src/network/bmproto.py +++ b/src/network/bmproto.py @@ -9,6 +9,7 @@ import re import socket import struct import time +import six # magic imports! import addresses @@ -34,6 +35,18 @@ from .objectracker import ObjectTracker, missingObjects 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): """A Bitmessage Protocol Base Error""" errorCodes = ("Protocol error") @@ -115,7 +128,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): if not self.invalid: try: retval = getattr( - self, "bm_command_" + str(self.command).lower())() + self, "bm_command_" + self.command.decode("utf-8", "replace").lower())() except AttributeError: # unimplemented command logger.debug('unimplemented command %s', self.command) @@ -169,16 +182,16 @@ class BMProto(AdvancedDispatcher, ObjectTracker): # protocol.checkIPAddress() 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': - 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': # Onion, based on BMD/bitcoind host = base64.b32encode(host[6:]).lower() + b".onion" else: - host = socket.inet_ntop(socket.AF_INET6, str(host)) + host = socket.inet_ntop(socket.AF_INET6, _hoststr(host)) if host == b"": # This can happen on Windows systems which are not 64-bit # 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) @@ -534,7 +547,7 @@ class BMProto(AdvancedDispatcher, ObjectTracker): self.append_write_buf(protocol.CreatePacket(b'verack')) self.verackSent = True 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: self.userAgent = b'/INVALID:0/' if not self.isOutbound: diff --git a/src/network/connectionchooser.py b/src/network/connectionchooser.py index f4ae075d..03764dfc 100644 --- a/src/network/connectionchooser.py +++ b/src/network/connectionchooser.py @@ -14,10 +14,16 @@ from queues import queue, portCheckerQueue 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(): """Get a peer from the local peer discovery list""" try: - peer = random.choice(state.discoveredPeers.keys()) # nosec B311 + peer = random.choice(list(state.discoveredPeers.keys())) # nosec B311 except (IndexError, KeyError): raise ValueError try: @@ -45,7 +51,7 @@ def chooseConnection(stream): return getDiscoveredPeer() for _ in range(50): peer = random.choice( # nosec B311 - knownnodes.knownNodes[stream].keys()) + list(knownnodes.knownNodes[stream].keys())) try: peer_info = knownnodes.knownNodes[stream][peer] if peer_info.get('self'): @@ -57,10 +63,10 @@ def chooseConnection(stream): if haveOnion: # do not connect to raw IP addresses # --keep all traffic within Tor overlay - if onionOnly and not peer.host.endswith('.onion'): + if onionOnly and not _ends_with(peer.host, '.onion'): continue # 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 # TODO: need better check elif not peer.host.startswith('bootstrap'): diff --git a/src/network/connectionpool.py b/src/network/connectionpool.py index d8cd68b8..7611d87d 100644 --- a/src/network/connectionpool.py +++ b/src/network/connectionpool.py @@ -25,6 +25,12 @@ from .udp import UDPSocket 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): """Pool of all existing connections""" # pylint: disable=too-many-instance-attributes @@ -160,8 +166,8 @@ class BMConnectionPool(object): @staticmethod def getListeningIP(): """What IP are we supposed to be listening on?""" - if config.safeGet( - "bitmessagesettings", "onionhostname", "").endswith(".onion"): + if _ends_with(config.safeGet( + "bitmessagesettings", "onionhostname", ""), ".onion"): host = config.safeGet( "bitmessagesettings", "onionbindip") else: @@ -314,7 +320,7 @@ class BMConnectionPool(object): continue try: - if chosen.host.endswith(".onion") and Proxy.onion_proxy: + if _ends_with(chosen.host, ".onion") and Proxy.onion_proxy: if onionsocksproxytype == "SOCKS5": self.addConnection(Socks5BMConnection(chosen)) elif onionsocksproxytype == "SOCKS4a": diff --git a/src/network/dandelion.py b/src/network/dandelion.py index 466f031e..3e67d1a7 100644 --- a/src/network/dandelion.py +++ b/src/network/dandelion.py @@ -185,8 +185,8 @@ class Dandelion: # pylint: disable=old-style-class try: # random two connections self.stem = sample( - connectionpool.BMConnectionPool( - ).outboundConnections.values(), MAX_STEMS) + sorted(connectionpool.BMConnectionPool( + ).outboundConnections.values()), MAX_STEMS) # not enough stems available except ValueError: self.stem = connectionpool.BMConnectionPool( diff --git a/src/network/knownnodes.py b/src/network/knownnodes.py index d8b9178b..2ce698f9 100644 --- a/src/network/knownnodes.py +++ b/src/network/knownnodes.py @@ -106,6 +106,12 @@ def addKnownNode(stream, peer, lastseen=None, is_self=False): Returns True if added a new node. """ # 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): with knownNodesLock: for s in stream: @@ -151,7 +157,7 @@ def createDefaultKnownNodes(): def readKnownNodes(): """Load knownnodes from filesystem""" try: - with open(state.appdata + 'knownnodes.dat', 'rb') as source: + with open(state.appdata + 'knownnodes.dat', 'r') as source: with knownNodesLock: try: json_deserialize_knownnodes(source) diff --git a/src/network/proxy.py b/src/network/proxy.py index eb76ce97..9cce6bf6 100644 --- a/src/network/proxy.py +++ b/src/network/proxy.py @@ -14,6 +14,12 @@ from .node import Peer 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): """Base proxy exception class""" errorCodes = ("Unknown error",) @@ -125,7 +131,7 @@ class Proxy(AdvancedDispatcher): self.auth = None self.connect( 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 ) diff --git a/src/network/tcp.py b/src/network/tcp.py index 0ef719ec..95b7eb58 100644 --- a/src/network/tcp.py +++ b/src/network/tcp.py @@ -40,6 +40,12 @@ maximumAgeOfNodesThatIAdvertiseToOthers = 10800 #: Equals three hours 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): # pylint: disable=too-many-instance-attributes """ @@ -195,7 +201,7 @@ class TCPConnection(BMProto, TLSDispatcher): (k, v) for k, v in six.iteritems(nodes) if v["lastseen"] > int(time.time()) - 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 elemCount = min( diff --git a/src/network/udp.py b/src/network/udp.py index e0abe110..d40b2f1b 100644 --- a/src/network/udp.py +++ b/src/network/udp.py @@ -81,7 +81,7 @@ class UDPSocket(BMProto): # pylint: disable=too-many-instance-attributes return True remoteport = False for seenTime, stream, _, ip, port in addresses: - decodedIP = protocol.checkIPAddress(str(ip)) + decodedIP = protocol.checkIPAddress(ip) if stream not in network.connectionpool.pool.streams: continue if (seenTime < time.time() - protocol.MAX_TIME_OFFSET diff --git a/src/protocol.py b/src/protocol.py index 493fefe5..125204fc 100644 --- a/src/protocol.py +++ b/src/protocol.py @@ -170,7 +170,7 @@ def checkIPAddress(host, private=False): otherwise returns False """ 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) elif host[0:6] == b'\xfd\x87\xd8\x7e\xeb\x43': # Onion, based on BMD/bitcoind @@ -419,7 +419,7 @@ def assembleVersionMessage( 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, 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(len(inventoryVector)) payload += inventoryVector + if isinstance(errorText, str): + errorText = errorText.encode("utf-8", "replace") payload += encodeVarint(len(errorText)) payload += errorText return CreatePacket(b'error', payload)