Skip to content
This repository has been archived by the owner on Jan 13, 2021. It is now read-only.

Implemented SOCKS5 proxies. #441

Open
wants to merge 9 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
root = true

[*.py]
charset = utf-8
indent_style = space
indent_size = 4
insert_final_newline = true
end_of_line = lf

[*.{yaml,yml}]
indent_size = 2
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,5 @@ __pycache__
.coverage
.tox/
.cache/

/hyper/version.py
6 changes: 4 additions & 2 deletions .travis/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ if [[ "$HYPER_FAST_PARSE" = true ]]; then
pip install pycohttpparser~=1.0
fi

pip install -U setuptools
pip install .
pip install -U pip
pip install -U setuptools wheel build
python3 -m build -nwx .
pip install --upgrade ./dist/*.whl
pip install -r test_requirements.txt
pip install flake8
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
The MIT License (MIT)

Copyright (c) 2014 Cory Benfield, Google Inc
Copyright (c) 2008-2020 Andrey Petrov and urllib3 contributors (see https://github.com/urllib3/urllib3/blob/master/CONTRIBUTORS.txt) (socks5 code was borrowed from there)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
11 changes: 11 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Hyper: HTTP/2 Client for Python

**This project is no longer maintained!**

@Lukasa has long dropped its maintainment.
The last maintainer was @Kriechi. He now has an own project, `HTTPX`_, that also supports HTTP/2 and is partially based on libs that were used in hyper, and now he develops it.

He has left the message

Please use an alternative, such as `HTTPX`_ or others.

.. _HTTPX: https://www.python-httpx.org/
Expand All @@ -18,6 +23,12 @@ Potential security issues will not be addressed.

----

and archived the project.

This fork is "maintained" by @KOLANICH. I don't really develop it, just add the stuff I need personally.



.. image:: https://raw.github.com/Lukasa/hyper/development/docs/source/images/hyper.png

HTTP is changing under our feet. HTTP/1.1, our old friend, is being
Expand Down
2 changes: 1 addition & 1 deletion hyper/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,4 @@
# Set default logging handler.
logging.getLogger(__name__).addHandler(logging.NullHandler())

__version__ = '0.8.0dev0'
from .version import __version__
17 changes: 15 additions & 2 deletions hyper/common/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class HTTPConnection(object):
:param proxy_port: (optional) The proxy port to connect to. If not provided
and one also isn't provided in the ``proxy_host`` parameter, defaults
to 8080.
:param proxy_type: (optional) type of the proxy to use. Allows usage of socks proxies
:param proxy_headers: (optional) The headers to send to a proxy.
"""
def __init__(self,
Expand All @@ -57,6 +58,7 @@ def __init__(self,
ssl_context=None,
proxy_host=None,
proxy_port=None,
proxy_type=None,
proxy_headers=None,
timeout=None,
**kwargs):
Expand All @@ -66,14 +68,14 @@ def __init__(self,
self._h1_kwargs = {
'secure': secure, 'ssl_context': ssl_context,
'proxy_host': proxy_host, 'proxy_port': proxy_port,
'proxy_headers': proxy_headers, 'enable_push': enable_push,
'proxy_headers': proxy_headers, "proxy_type": proxy_type, 'enable_push': enable_push,
'timeout': timeout
}
self._h2_kwargs = {
'window_manager': window_manager, 'enable_push': enable_push,
'secure': secure, 'ssl_context': ssl_context,
'proxy_host': proxy_host, 'proxy_port': proxy_port,
'proxy_headers': proxy_headers,
'proxy_headers': proxy_headers, "proxy_type": proxy_type,
'timeout': timeout
}

Expand Down Expand Up @@ -150,6 +152,17 @@ def get_response(self, *args, **kwargs):

return self._conn.get_response(1)

def reanimate(self):
"""Reanimate connection reset because of proxy"""
if hasattr(self, "streams"):
for stream in list(self.streams.values()):
stream.remote_closed = True
stream.local_closed = True
self._conn.close()
self._conn = HTTP11Connection(
self._host, self._port, **self._h1_kwargs,
)

# The following two methods are the implementation of the context manager
# protocol.
def __enter__(self): # pragma: no cover
Expand Down
66 changes: 49 additions & 17 deletions hyper/contrib.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,19 +56,32 @@ def get_connection(self, host, port, scheme, cert=None, verify=True,
ssl_context = init_context(cert_path=verify, cert=cert)

if proxy:
proxy = prepend_scheme_if_needed(proxy, 'http')
proxy_headers = self.proxy_headers(proxy)
proxy_netloc = urlparse(proxy).netloc
parsed = urlparse(proxy)
proxy_netloc = parsed.netloc
proxy_host_port = proxy_netloc.split(":")
if len(proxy_host_port) == 2:
proxy_host, proxy_port = proxy_host_port
proxy_port = int(proxy_port)
elif len(proxy_port_host) == 1:
raise ValueError("Specify proxy port!")
else:
raise ValueError("Invalid proxy netloc: ", repr(proxy_netloc))
proxy_type = parsed.scheme
else:
proxy_headers = None
proxy_netloc = None
proxy_host = None
proxy_port = None
proxy_type = None

# We put proxy headers in the connection_key, because
# ``proxy_headers`` method might be overridden, so we can't
# rely on proxy headers being the same for the same proxies.
proxy_headers_key = (frozenset(proxy_headers.items())
if proxy_headers else None)
connection_key = (host, port, scheme, cert, verify,
proxy_netloc, proxy_headers_key)
proxy_host, proxy_port, proxy_type, proxy_headers_key)
try:
conn = self.connections[connection_key]
except KeyError:
Expand All @@ -78,9 +91,13 @@ def get_connection(self, host, port, scheme, cert=None, verify=True,
secure=secure,
window_manager=self.window_manager,
ssl_context=ssl_context,
proxy_host=proxy_netloc,
proxy_host=proxy_host,
proxy_port=proxy_port,
proxy_headers=proxy_headers,
timeout=timeout)
proxy_type=proxy_type,
timeout=timeout,
enable_push=self.enable_push
)
self.connections[connection_key] = conn

return conn
Expand All @@ -95,6 +112,12 @@ def send(self, request, stream=False, cert=None, verify=True, proxies=None,
proxy = prepend_scheme_if_needed(proxy, 'http')

parsed = urlparse(request.url)

# Build the selector.
selector = parsed.path
selector += '?' + parsed.query if parsed.query else ''
selector += '#' + parsed.fragment if parsed.fragment else ''

conn = self.get_connection(
parsed.hostname,
parsed.port,
Expand All @@ -104,18 +127,27 @@ def send(self, request, stream=False, cert=None, verify=True, proxies=None,
proxy=proxy,
timeout=timeout)

# Build the selector.
selector = parsed.path
selector += '?' + parsed.query if parsed.query else ''
selector += '#' + parsed.fragment if parsed.fragment else ''

conn.request(
request.method,
selector,
request.body,
request.headers
)
resp = conn.get_response()
def do_req():
conn.request(
request.method,
selector,
request.body,
request.headers
)
resp = conn.get_response()
return conn, resp

retried = 0
max_retries = 1
while True:
try:
conn, resp = do_req()
break
except ConnectionAbortedError as e:
if retried < max_retries:
conn.reanimate()
else:
raise

r = self.build_response(request, resp)

Expand Down
68 changes: 49 additions & 19 deletions hyper/http11/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
import socket
import base64

from collections import Iterable, Mapping
from collections.abc import Iterable, Mapping

import collections
from hyperframe.frame import SettingsFrame
Expand Down Expand Up @@ -101,7 +101,7 @@ class HTTP11Connection(object):

def __init__(self, host, port=None, secure=None, ssl_context=None,
proxy_host=None, proxy_port=None, proxy_headers=None,
timeout=None, **kwargs):
proxy_type=None, timeout=None, **kwargs):
if port is None:
self.host, self.port = to_host_port_tuple(host, default_port=80)
else:
Expand Down Expand Up @@ -133,11 +133,14 @@ def __init__(self, host, port=None, secure=None, ssl_context=None,
self.proxy_host, self.proxy_port = to_host_port_tuple(
proxy_host, default_port=8080
)
self.proxy_type = proxy_type
elif proxy_host:
self.proxy_host, self.proxy_port = proxy_host, proxy_port
self.proxy_host, self.proxy_port, self.proxy_type = proxy_host, proxy_port, proxy_type
else:
self.proxy_host = None
self.proxy_port = None
self.proxy_type = None
raise ValueError("No proxy was set!")
self.proxy_headers = proxy_headers

#: The size of the in-memory buffer used to store data from the
Expand Down Expand Up @@ -169,22 +172,48 @@ def connect(self):
connect_timeout = self._timeout
read_timeout = self._timeout

if self.proxy_host and self.secure:
# Send http CONNECT method to a proxy and acquire the socket
sock = _create_tunnel(
self.proxy_host,
self.proxy_port,
self.host,
self.port,
proxy_headers=self.proxy_headers,
timeout=self._timeout
)
elif self.proxy_host:
# Simple http proxy
sock = socket.create_connection(
(self.proxy_host, self.proxy_port),
timeout=connect_timeout
)
if self.proxy_host:
if self.proxy_type.startswith("socks"):
import socks
rdns = (self.proxy_type[-1]=="h")
# any error will result in silently connecting without a proxy.
# IDK why it is done this way
if not rdns:
raise ValueError("RDNS is disabled. Proxying dns queries is disabled. NSA is spying you.")
if rdns and self.proxy_type.startswith("socks4"):
raise ValueError("RDNS is not supported for socks4. socks.create_connection ignores it silently.")
if not isinstance(self.proxy_host, str):
raise ValueError("self.proxy_host", repr(self.proxy_host), "is not str")
if not isinstance(self.proxy_port, int):
raise ValueError("self.proxy_port", repr(self.proxy_port), "is not int")
socks_version_char = self.proxy_type[5]
sock = socks.create_connection(
(self.host, self.port),
proxy_type=getattr(socks, "PROXY_TYPE_SOCKS" + socks_version_char),
proxy_addr=self.proxy_host,
proxy_port=self.proxy_port,
#proxy_username=username,
#proxy_password=password,
proxy_rdns=rdns,
)
elif self.proxy_host and self.secure:
# Send http CONNECT method to a proxy and acquire the socket
sock = _create_tunnel(
self.proxy_host,
self.proxy_port,
self.host,
self.port,
proxy_headers=self.proxy_headers,
timeout=self._timeout
)
elif self.proxy_host:
# Simple http proxy
sock = socket.create_connection(
(self.proxy_host, self.proxy_port),
timeout=connect_timeout
)
else:
raise Exception("Unsupported proxy type: "+repr(proxy_type))
else:
sock = socket.create_connection((self.host, self.port),
timeout=connect_timeout)
Expand Down Expand Up @@ -454,6 +483,7 @@ def _send_file_like_obj(self, fobj):

return


def close(self):
"""
Closes the connection. This closes the socket and then abandons the
Expand Down
Loading