From 3178b69172c8058c448210d48dad2d7cb32746bc Mon Sep 17 00:00:00 2001
From: Ivanq <imachug@yandex.ru>
Date: Mon, 9 Dec 2019 22:13:38 +0300
Subject: [PATCH] Switch to bencode_open

---
 .../AnnounceBitTorrentPlugin.py               |   6 +-
 requirements.txt                              |   1 -
 src/lib/bencode_open/LICENSE                  |  21 +++
 src/lib/bencode_open/__init__.py              | 160 ++++++++++++++++++
 4 files changed, 183 insertions(+), 5 deletions(-)
 create mode 100644 src/lib/bencode_open/LICENSE
 create mode 100644 src/lib/bencode_open/__init__.py

diff --git a/plugins/AnnounceBitTorrent/AnnounceBitTorrentPlugin.py b/plugins/AnnounceBitTorrent/AnnounceBitTorrentPlugin.py
index ae674c00..defa9266 100644
--- a/plugins/AnnounceBitTorrent/AnnounceBitTorrentPlugin.py
+++ b/plugins/AnnounceBitTorrent/AnnounceBitTorrentPlugin.py
@@ -3,7 +3,7 @@ import urllib.request
 import struct
 import socket
 
-import bencode
+import bencode_open
 from lib.subtl.subtl import UdpTrackerClient
 import socks
 import sockshandler
@@ -133,9 +133,7 @@ class SiteAnnouncerPlugin(object):
 
         # Decode peers
         try:
-            peer_data = bencode.decode(response)["peers"]
-            if type(peer_data) is not bytes:
-                peer_data = peer_data.encode()
+            peer_data = bencode_open.loads(response)[b"peers"]
             response = None
             peer_count = int(len(peer_data) / 6)
             peers = []
diff --git a/requirements.txt b/requirements.txt
index 8e756f66..7c063e3f 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,7 +7,6 @@ PySocks>=1.6.8
 pyasn1
 websocket_client
 gevent-websocket
-bencode.py
 coincurve
 python-bitcoinlib
 maxminddb
diff --git a/src/lib/bencode_open/LICENSE b/src/lib/bencode_open/LICENSE
new file mode 100644
index 00000000..f0e46d71
--- /dev/null
+++ b/src/lib/bencode_open/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 Ivan Machugovskiy
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/src/lib/bencode_open/__init__.py b/src/lib/bencode_open/__init__.py
new file mode 100644
index 00000000..e3c783cc
--- /dev/null
+++ b/src/lib/bencode_open/__init__.py
@@ -0,0 +1,160 @@
+def loads(data):
+    if not isinstance(data, bytes):
+        raise TypeError("Expected 'bytes' object, got {}".format(type(data)))
+
+    offset = 0
+
+
+    def parseInteger():
+        nonlocal offset
+
+        offset += 1
+        had_digit = False
+        abs_value = 0
+
+        sign = 1
+        if data[offset] == ord("-"):
+            sign = -1
+            offset += 1
+        while offset < len(data):
+            if data[offset] == ord("e"):
+                # End of string
+                offset += 1
+                if not had_digit:
+                    raise ValueError("Integer without value")
+                break
+            if ord("0") <= data[offset] <= ord("9"):
+                abs_value = abs_value * 10 + int(chr(data[offset]))
+                had_digit = True
+                offset += 1
+            else:
+                raise ValueError("Invalid integer")
+        else:
+            raise ValueError("Unexpected EOF, expected integer")
+
+        if not had_digit:
+            raise ValueError("Empty integer")
+
+        return sign * abs_value
+
+
+    def parseString():
+        nonlocal offset
+
+        length = int(chr(data[offset]))
+        offset += 1
+
+        while offset < len(data):
+            if data[offset] == ord(":"):
+                offset += 1
+                break
+            if ord("0") <= data[offset] <= ord("9"):
+                length = length * 10 + int(chr(data[offset]))
+                offset += 1
+            else:
+                raise ValueError("Invalid string length")
+        else:
+            raise ValueError("Unexpected EOF, expected string contents")
+
+        if offset + length > len(data):
+            raise ValueError("Unexpected EOF, expected string contents")
+        offset += length
+
+        return data[offset - length:offset]
+
+
+    def parseList():
+        nonlocal offset
+
+        offset += 1
+        values = []
+
+        while offset < len(data):
+            if data[offset] == ord("e"):
+                # End of list
+                offset += 1
+                return values
+            else:
+                values.append(parse())
+
+        raise ValueError("Unexpected EOF, expected list contents")
+
+
+    def parseDict():
+        nonlocal offset
+
+        offset += 1
+        items = {}
+
+        while offset < len(data):
+            if data[offset] == ord("e"):
+                # End of list
+                offset += 1
+                return items
+            else:
+                key, value = parse(), parse()
+                if not isinstance(key, bytes):
+                    raise ValueError("A dict key must be a byte string")
+                if key in items:
+                    raise ValueError("Duplicate dict key: {}".format(key))
+                items[key] = value
+
+        raise ValueError("Unexpected EOF, expected dict contents")
+
+
+    def parse():
+        nonlocal offset
+
+        if data[offset] == ord("i"):
+            return parseInteger()
+        elif data[offset] == ord("l"):
+            return parseList()
+        elif data[offset] == ord("d"):
+            return parseDict()
+        elif ord("0") <= data[offset] <= ord("9"):
+            return parseString()
+
+        raise ValueError("Unknown type specifier: '{}'".format(chr(data[offset])))
+
+    result = parse()
+
+    if offset != len(data):
+        raise ValueError("Expected EOF, got {} bytes left".format(len(data) - offset))
+
+    return result
+
+
+def dumps(data):
+    result = bytearray()
+
+
+    def convert(data):
+        nonlocal result
+
+        if isinstance(data, str):
+            raise ValueError("bencode only supports bytes, not str. Use encode")
+
+        if isinstance(data, bytes):
+            result += str(len(data)).encode() + b":" + data
+        elif isinstance(data, int):
+            result += b"i" + str(data).encode() + b"e"
+        elif isinstance(data, list):
+            result += b"l"
+            for val in data:
+                convert(val)
+            result += b"e"
+        elif isinstance(data, dict):
+            result += b"d"
+            for key in sorted(data.keys()):
+                if not isinstance(key, bytes):
+                    raise ValueError("Dict key can only be bytes, not {}".format(type(key)))
+                convert(key)
+                convert(data[key])
+            result += b"e"
+        else:
+            raise ValueError("bencode only supports bytes, int, list and dict")
+
+
+    convert(data)
+
+    return bytes(result)