Skip to content

Commit 05fbbfa

Browse files
committed
Support python interception for http.client
1 parent e137606 commit 05fbbfa

File tree

4 files changed

+64
-7
lines changed

4 files changed

+64
-7
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# This module _must_ trigger the import for http.client. We need to ensure we preload
2+
# the real http module (or import http & import http.server don't work properly), but
3+
# if we do that before importing the client, the client can never be intercepted.
4+
5+
# There might be a cleaner alternative, but for now we just aggressively pre-intercept
6+
# the client instead.
7+
8+
from . import client
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
from httptoolkit_intercept import preload_real_module
2+
3+
preload_real_module('http', 'http.client')
4+
5+
import http.client, os, functools
6+
7+
# Re-export all public fields
8+
from http.client import *
9+
# Load a few extra notable private fields, for max compatibility
10+
from http.client import __file__, __doc__
11+
12+
_httpProxy = os.environ['HTTP_PROXY']
13+
[_proxyHost, _proxyPort] = _httpProxy.split('://')[1].split(':')
14+
_certPath = os.environ['SSL_CERT_FILE']
15+
16+
# Redirect and then tunnel all plain HTTP connections:
17+
_http_connection_init = HTTPConnection.__init__
18+
@functools.wraps(_http_connection_init)
19+
def _new_http_connection_init(self, host, port=80, *k, **kw):
20+
_http_connection_init(self, _proxyHost, _proxyPort, *k, **kw)
21+
self.set_tunnel(host, port)
22+
HTTPConnection.__init__ = _new_http_connection_init
23+
24+
def _build_default_context():
25+
import ssl
26+
context = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
27+
context.options |= ssl.OP_NO_SSLv2
28+
context.options |= ssl.OP_NO_SSLv3
29+
return context
30+
31+
# Redirect & tunnel HTTPS connections, and inject our CA certificate:
32+
_https_connection_init = HTTPSConnection.__init__
33+
@functools.wraps(_https_connection_init)
34+
def _new_https_connection_init(self, host, port=443, *k, **kw):
35+
context = None
36+
if 'context' in kw:
37+
context = kw.get('context')
38+
elif len(k) > 7:
39+
context = k[7]
40+
41+
if context == None:
42+
context = kw['context'] = _build_default_context()
43+
44+
context.load_verify_locations(_certPath)
45+
46+
_https_connection_init(self, _proxyHost, _proxyPort, *k, **kw)
47+
self.set_tunnel(host, port)
48+
HTTPSConnection.__init__ = _new_https_connection_init

overrides/pythonpath/httplib.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
[_proxyHost, _proxyPort] = _httpProxy.split('://')[1].split(':')
1414
_certPath = os.environ['SSL_CERT_FILE']
1515

16+
# Redirect and then tunnel all plain HTTP connections:
1617
_http_connection_init = HTTPConnection.__init__
1718
@functools.wraps(_http_connection_init)
1819
def _new_http_connection_init(self, host, port=80, *k, **kw):
@@ -27,6 +28,7 @@ def _build_default_context():
2728
context.options |= ssl.OP_NO_SSLv3
2829
return context
2930

31+
# Redirect & tunnel HTTPS connections, and inject our CA certificate:
3032
_https_connection_init = HTTPSConnection.__init__
3133
@functools.wraps(_https_connection_init)
3234
def _new_https_connection_init(self, host, port=443, *k, **kw):

overrides/pythonpath/httptoolkit_intercept.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@
66
import imp
77
importlib = imp
88

9-
def preload_real_module(moduleName):
9+
def preload_real_module(*module_names):
1010
# Re-importing the real module at the top level of an override fails after deleting it from
11-
# sys.modules['httplib'] in Python 2. Some interesting issues there ofc, but doing this
11+
# sys.modules['httplib'] in Python 2. Some interesting issues there ofc, but doing this
1212
# instead works nicely in both Python 2 & 3.
1313
import sys, os
1414

1515
override_path = os.path.dirname(os.path.abspath(__file__))
16-
# print('override path %s' % override_path)
1716
original_sys_path = list(sys.path)
18-
# print('sys_path %s' % original_sys_path)
1917
sys.path = [p for p in sys.path if p != override_path and p != '']
20-
# print('new sys_path %s' % sys.path)
2118

22-
del sys.modules[moduleName]
23-
__import__(moduleName)
19+
for mod in module_names:
20+
if mod in sys.modules:
21+
del sys.modules[mod]
22+
__import__(mod)
2423

2524
sys.path = original_sys_path

0 commit comments

Comments
 (0)