Skip to content

Commit f06a196

Browse files
authored
Invalid url exception (#2258)
* Fix #2241: Raise instead of on fetches with invalid URL * Add missing changes * Fix test * Fix spelling * Improve test coverage
1 parent 6ccf1b9 commit f06a196

File tree

8 files changed

+88
-27
lines changed

8 files changed

+88
-27
lines changed

aiohttp/client.py

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
from . import connector as connector_mod
1616
from . import client_exceptions, client_reqrep, hdrs, http, payload
1717
from .client_exceptions import * # noqa
18-
from .client_exceptions import (ClientError, ClientOSError, ServerTimeoutError,
19-
WSServerHandshakeError)
18+
from .client_exceptions import (ClientError, ClientOSError, InvalidURL,
19+
ServerTimeoutError, WSServerHandshakeError)
2020
from .client_reqrep import * # noqa
2121
from .client_reqrep import ClientRequest, ClientResponse
2222
from .client_ws import ClientWebSocketResponse
@@ -204,7 +204,15 @@ def _request(self, method, url, *,
204204
skip_headers.add(istr(i))
205205

206206
if proxy is not None:
207-
proxy = URL(proxy)
207+
try:
208+
proxy = URL(proxy)
209+
except ValueError:
210+
raise InvalidURL(url)
211+
212+
try:
213+
url = URL(url)
214+
except ValueError:
215+
raise InvalidURL(url)
208216

209217
# timeout is cumulative for all request operations
210218
# (request, redirects, responses, data consuming)
@@ -217,7 +225,7 @@ def _request(self, method, url, *,
217225
try:
218226
with timer:
219227
while True:
220-
url = URL(url).with_fragment(None)
228+
url = url.with_fragment(None)
221229
cookies = self._cookie_jar.filter_cookies(url)
222230

223231
req = self._request_class(
@@ -285,8 +293,12 @@ def _request(self, method, url, *,
285293
# see github.com/aio-libs/aiohttp/issues/2022
286294
break
287295

288-
r_url = URL(
289-
r_url, encoded=not self.requote_redirect_url)
296+
try:
297+
r_url = URL(
298+
r_url, encoded=not self.requote_redirect_url)
299+
300+
except ValueError:
301+
raise InvalidURL(r_url)
290302

291303
scheme = r_url.scheme
292304
if scheme not in ('http', 'https', ''):

aiohttp/client_exceptions.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
'ClientResponseError', 'ClientHttpProxyError',
1616
'WSServerHandshakeError', 'ContentTypeError',
1717

18-
'ClientPayloadError')
18+
'ClientPayloadError', 'InvalidURL')
1919

2020

2121
class ClientError(Exception):
@@ -131,3 +131,22 @@ def __repr__(self):
131131

132132
class ClientPayloadError(ClientError):
133133
"""Response payload error."""
134+
135+
136+
class InvalidURL(ClientError, ValueError):
137+
"""Invalid URL.
138+
139+
URL used for fetching is malformed, e.g. it doesn't contains host
140+
part."""
141+
142+
# Derive from ValueError for backward compatibility
143+
144+
def __init__(self, url):
145+
super().__init__(url)
146+
147+
@property
148+
def url(self):
149+
return self.args[0]
150+
151+
def __repr__(self):
152+
return '<{} {}>'.format(self.__class__.__name__, self.url)

aiohttp/client_reqrep.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515

1616
from . import hdrs, helpers, http, payload
1717
from .client_exceptions import (ClientConnectionError, ClientOSError,
18-
ClientResponseError, ContentTypeError)
18+
ClientResponseError, ContentTypeError,
19+
InvalidURL)
1920
from .formdata import FormData
2021
from .helpers import PY_35, HeadersMixin, SimpleCookie, TimerNoop, noop
2122
from .http import SERVER_SOFTWARE, HttpVersion10, HttpVersion11, PayloadWriter
@@ -143,8 +144,7 @@ def update_host(self, url):
143144
"""Update destination host, port and connection type (ssl)."""
144145
# get host/port
145146
if not url.host:
146-
raise ValueError(
147-
"Could not parse hostname from URL '{}'".format(url))
147+
raise InvalidURL(url)
148148

149149
# basic auth info
150150
username, password = url.user, url.password

changes/2241.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Raise `InvalidURL` instead of `ValueError` on fetches with invalid URL.

docs/client_reference.rst

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,6 +1476,29 @@ All exceptions are available as members of *aiohttp* module.
14761476
Derived from :exc:`Exception`
14771477

14781478

1479+
.. class:: ClientPayloadError
1480+
1481+
This exception can only be raised while reading the response
1482+
payload if one of these errors occurs:
1483+
1484+
1. invalid compression
1485+
2. malformed chunked encoding
1486+
3. not enough data that satisfy ``Content-Length`` HTTP header.
1487+
1488+
Derived from :exc:`ClientError`
1489+
1490+
.. exception:: InvalidURL
1491+
1492+
URL used for fetching is malformed, e.g. it does not contain host
1493+
part.
1494+
1495+
Derived from :exc:`ClientError` and :exc:`ValueError`
1496+
1497+
.. attribute:: url
1498+
1499+
Invalid URL, :class:`yarl.URL` instance.
1500+
1501+
14791502
Response errors
14801503
^^^^^^^^^^^^^^^
14811504

@@ -1576,17 +1599,6 @@ Connection errors
15761599
Derived from :exc:`ServerConnectonError`
15771600

15781601

1579-
.. class:: ClientPayloadError
1580-
1581-
This exception can only be raised while reading the response
1582-
payload if one of these errors occurs:
1583-
1584-
1. invalid compression
1585-
2. malformed chunked encoding
1586-
3. not enough data that satisfy ``Content-Length`` HTTP header.
1587-
1588-
Derived from :exc:`ClientError`
1589-
15901602
Hierarchy of exceptions
15911603
^^^^^^^^^^^^^^^^^^^^^^^
15921604

@@ -1614,3 +1626,5 @@ Hierarchy of exceptions
16141626
* :exc:`ServerFingerprintMismatch`
16151627

16161628
* :exc:`ClientPayloadError`
1629+
1630+
* :exc:`InvalidURL`

tests/test_client_exceptions.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Tests for http_exceptions.py"""
22

3+
from yarl import URL
4+
35
from aiohttp import client
46

57

@@ -8,3 +10,11 @@ def test_fingerprint_mismatch():
810
expected = ('<ServerFingerprintMismatch expected=exp'
911
' got=got host=host port=8888>')
1012
assert expected == repr(err)
13+
14+
15+
def test_invalid_url():
16+
url = URL('http://example.com')
17+
err = client.InvalidURL(url)
18+
assert err.args[0] is url
19+
assert err.url is url
20+
assert repr(err) == "<InvalidURL http://example.com>"

tests/test_client_functional.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2216,3 +2216,13 @@ def handler_redirect(request):
22162216

22172217
with pytest.raises(aiohttp.ClientResponseError):
22182218
yield from client.get('/')
2219+
2220+
2221+
@asyncio.coroutine
2222+
def test_invalid_idna(loop):
2223+
session = aiohttp.ClientSession(loop=loop)
2224+
try:
2225+
with pytest.raises(aiohttp.InvalidURL):
2226+
yield from session.get('http://\u2061owhefopw.com')
2227+
finally:
2228+
yield from session.close()

tests/test_client_request.py

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -286,15 +286,10 @@ def test_headers_default(make_request):
286286

287287

288288
def test_invalid_url(make_request):
289-
with pytest.raises(ValueError):
289+
with pytest.raises(aiohttp.InvalidURL):
290290
make_request('get', 'hiwpefhipowhefopw')
291291

292292

293-
def test_invalid_idna(make_request):
294-
with pytest.raises(ValueError):
295-
make_request('get', 'http://\u2061owhefopw.com')
296-
297-
298293
def test_no_path(make_request):
299294
req = make_request('get', 'http://python.org')
300295
assert '/' == req.url.path

0 commit comments

Comments
 (0)