From 128053750d6cf504c8aa368cfddff36dfa3aef46 Mon Sep 17 00:00:00 2001 From: Lonami Date: Sun, 8 Jul 2018 17:45:49 +0200 Subject: [PATCH] Implement HTTP(S) mode (closes #112) (#883) --- telethon/extensions/tcpclient.py | 9 +++- telethon/network/connection/__init__.py | 1 + telethon/network/connection/http.py | 62 +++++++++++++++++++++++++ 3 files changed, 71 insertions(+), 1 deletion(-) create mode 100644 telethon/network/connection/http.py diff --git a/telethon/extensions/tcpclient.py b/telethon/extensions/tcpclient.py index ace0d38c..2b9ee03d 100644 --- a/telethon/extensions/tcpclient.py +++ b/telethon/extensions/tcpclient.py @@ -11,6 +11,7 @@ import asyncio import errno import logging import socket +import ssl from io import BytesIO CONN_RESET_ERRNOS = { @@ -28,6 +29,7 @@ try: except ImportError: socks = None +SSL_PORT = 443 __log__ = logging.getLogger(__name__) @@ -37,15 +39,18 @@ class TcpClient: class SocketClosed(ConnectionError): pass - def __init__(self, *, loop, timeout, proxy=None): + def __init__(self, *, loop, timeout, ssl=None, proxy=None): """ Initializes the TCP client. :param proxy: the proxy to be used, if any. :param timeout: the timeout for connect, read and write operations. + :param ssl: ssl.wrap_socket keyword arguments to use when connecting + if port == SSL_PORT, or do nothing if not present. """ self._loop = loop self.proxy = proxy + self.ssl = ssl self._socket = None self._closed = asyncio.Event(loop=self._loop) self._closed.set() @@ -87,6 +92,8 @@ class TcpClient: try: if self._socket is None: self._socket = self._create_socket(mode, self.proxy) + if self.ssl and port == SSL_PORT: + self._socket = ssl.wrap_socket(self._socket, **self.ssl) await asyncio.wait_for( self._loop.sock_connect(self._socket, address), diff --git a/telethon/network/connection/__init__.py b/telethon/network/connection/__init__.py index 0c7a07d0..262aaa3a 100644 --- a/telethon/network/connection/__init__.py +++ b/telethon/network/connection/__init__.py @@ -2,3 +2,4 @@ from .tcpfull import ConnectionTcpFull from .tcpabridged import ConnectionTcpAbridged from .tcpobfuscated import ConnectionTcpObfuscated from .tcpintermediate import ConnectionTcpIntermediate +from .http import ConnectionHttp diff --git a/telethon/network/connection/http.py b/telethon/network/connection/http.py new file mode 100644 index 00000000..955b9ab3 --- /dev/null +++ b/telethon/network/connection/http.py @@ -0,0 +1,62 @@ +import errno +import ssl + +from .common import Connection +from ...extensions import TcpClient + + +class ConnectionHttp(Connection): + def __init__(self, *, loop, timeout, proxy=None): + super().__init__(loop=loop, timeout=timeout, proxy=proxy) + self.conn = TcpClient( + timeout=self._timeout, loop=self._loop, proxy=self._proxy, + ssl=dict(ssl_version=ssl.PROTOCOL_SSLv23, ciphers='ADH-AES256-SHA') + ) + self.read = self.conn.read + self.write = self.conn.write + self._host = None + + async def connect(self, ip, port): + self._host = '{}:{}'.format(ip, port) + try: + await self.conn.connect(ip, port) + except OSError as e: + if e.errno == errno.EISCONN: + return # Already connected, no need to re-set everything up + else: + raise + + def get_timeout(self): + return self.conn.timeout + + def is_connected(self): + return self.conn.is_connected + + async def close(self): + self.conn.close() + + async def recv(self): + while True: + line = await self._read_line() + if line.lower().startswith(b'content-length: '): + await self.read(2) + length = int(line[16:-2]) + return await self.read(length) + + async def _read_line(self): + newline = ord('\n') + line = await self.read(1) + while line[-1] != newline: + line += await self.read(1) + return line + + async def send(self, message): + await self.write( + 'POST /api HTTP/1.1\r\n' + 'Host: {}\r\n' + 'Content-Type: application/x-www-form-urlencoded\r\n' + 'Connection: keep-alive\r\n' + 'Keep-Alive: timeout=100000, max=10000000\r\n' + 'Content-Length: {}\r\n\r\n'.format(self._host, len(message)) + .encode('ascii') + message + )