Skip to content

Commit 7a67d0f

Browse files
authored
urlsplit compliance for https CONNECT (#360)
* Add fix to be complaint with urlparse.urlsplit semantics which expects a fully qualified URL * Log exception stacktrace on OSError
1 parent e3522e0 commit 7a67d0f

File tree

4 files changed

+18
-15
lines changed

4 files changed

+18
-15
lines changed

proxy/http/parser.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,27 +105,29 @@ def del_headers(self, headers: List[bytes]) -> None:
105105
self.del_header(key.lower())
106106

107107
def set_url(self, url: bytes) -> None:
108+
# Work around with urlsplit semantics.
109+
#
110+
# For CONNECT requests, request line contains
111+
# upstream_host:upstream_port which is not complaint
112+
# with urlsplit, which expects a fully qualified url.
113+
if self.method == b'CONNECT':
114+
url = b'https://' + url
108115
self.url = urlparse.urlsplit(url)
109116
self.set_line_attributes()
110117

111118
def set_line_attributes(self) -> None:
112119
if self.type == httpParserTypes.REQUEST_PARSER:
113120
if self.method == httpMethods.CONNECT and self.url:
114-
if self.url.scheme == b'':
115-
u = urlparse.urlsplit(b'//' + self.url.path)
116-
self.host, self.port = u.hostname, u.port
117-
else:
118-
self.host = self.url.scheme
119-
self.port = 443 if self.url.path == b'' else \
120-
int(self.url.path)
121+
self.host = self.url.hostname
122+
self.port = 443 if self.url.port is None else self.url.port
121123
elif self.url:
122124
self.host, self.port = self.url.hostname, self.url.port \
123125
if self.url.port else DEFAULT_HTTP_PORT
124126
else:
125127
raise KeyError(
126128
'Invalid request. Method: %r, Url: %r' %
127129
(self.method, self.url))
128-
self.path = self.build_url()
130+
self.path = self.build_path()
129131

130132
def is_chunked_encoded(self) -> bool:
131133
return b'transfer-encoding' in self.headers and \
@@ -222,7 +224,7 @@ def process_header(self, raw: bytes) -> None:
222224
value = COLON.join(parts[1:]).strip()
223225
self.add_headers([(key, value)])
224226

225-
def build_url(self) -> bytes:
227+
def build_path(self) -> bytes:
226228
if not self.url:
227229
return b'/None'
228230
url = self.url.path

proxy/http/proxy/server.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,8 +278,8 @@ def on_request_complete(self) -> Union[socket.socket, bool]:
278278
logger.error(
279279
'BrokenPipeError when wrapping client')
280280
return True
281-
except OSError:
282-
logger.error('OSError when wrapping client')
281+
except OSError as e:
282+
logger.exception('OSError when wrapping client', exc_info=e)
283283
return True
284284
# Update all plugin connection reference
285285
for plugin in self.plugins.values():
@@ -398,8 +398,8 @@ def wrap_client(self) -> None:
398398
self.client._conn = ssl.wrap_socket(
399399
self.client.connection,
400400
server_side=True,
401-
keyfile=self.flags.ca_signing_key_file,
402-
certfile=generated_cert)
401+
certfile=generated_cert,
402+
keyfile=self.flags.ca_signing_key_file)
403403
self.client.connection.setblocking(False)
404404
logger.debug(
405405
'TLS interception using %s', generated_cert)

requirements-testing.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ codecov==2.1.4
1010
tox==3.15.1
1111
mccabe==0.6.1
1212
pylint==2.5.2
13+
rope==0.17.0

tests/http/test_http_parser.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ def test_get_full_parse(self) -> None:
132132
b'example.com')
133133
self.parser.parse(pkt)
134134
self.assertEqual(self.parser.total_size, len(pkt))
135-
self.assertEqual(self.parser.build_url(), b'/path/dir/?a=b&c=d#p=q')
135+
self.assertEqual(self.parser.build_path(), b'/path/dir/?a=b&c=d#p=q')
136136
self.assertEqual(self.parser.method, b'GET')
137137
assert self.parser.url
138138
self.assertEqual(self.parser.url.hostname, b'example.com')
@@ -149,7 +149,7 @@ def test_get_full_parse(self) -> None:
149149
self.parser.build())
150150

151151
def test_build_url_none(self) -> None:
152-
self.assertEqual(self.parser.build_url(), b'/None')
152+
self.assertEqual(self.parser.build_path(), b'/None')
153153

154154
def test_line_rcvd_to_rcving_headers_state_change(self) -> None:
155155
pkt = b'GET http://localhost HTTP/1.1'

0 commit comments

Comments
 (0)