Add urlutils.proxy_for_url

This commit is contained in:
Florian Bruhin 2016-12-22 13:51:27 +01:00
parent cd8d179813
commit b220b5438f
4 changed files with 106 additions and 70 deletions

View File

@ -28,17 +28,14 @@ import itertools
import collections import collections
import warnings import warnings
import datetime import datetime
import functools
from PyQt5.QtCore import QUrl, Qt from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QColor, QFont from PyQt5.QtGui import QColor, QFont
from PyQt5.QtNetwork import QNetworkProxy
from PyQt5.QtWidgets import QTabWidget, QTabBar from PyQt5.QtWidgets import QTabWidget, QTabBar
from qutebrowser.commands import cmdutils from qutebrowser.commands import cmdutils
from qutebrowser.config import configexc from qutebrowser.config import configexc
from qutebrowser.utils import standarddir, utils from qutebrowser.utils import standarddir, utils
from qutebrowser.browser.webkit.network import pac
SYSTEM_PROXY = object() # Return value for Proxy type SYSTEM_PROXY = object() # Return value for Proxy type
@ -1016,38 +1013,10 @@ class ShellCommand(BaseType):
return shlex.split(value) return shlex.split(value)
def proxy_from_url(typ, url):
"""Create a QNetworkProxy from QUrl and a proxy type.
Args:
typ: QNetworkProxy::ProxyType.
url: URL of a proxy (possibly with credentials).
Return:
New QNetworkProxy.
"""
proxy = QNetworkProxy(typ, url.host())
if url.port() != -1:
proxy.setPort(url.port())
if url.userName():
proxy.setUser(url.userName())
if url.password():
proxy.setPassword(url.password())
return proxy
class Proxy(BaseType): class Proxy(BaseType):
"""A proxy URL or special value.""" """A proxy URL or special value."""
PROXY_TYPES = {
'http': functools.partial(proxy_from_url, QNetworkProxy.HttpProxy),
'pac+http': pac.PACFetcher,
'pac+https': pac.PACFetcher,
'socks': functools.partial(proxy_from_url, QNetworkProxy.Socks5Proxy),
'socks5': functools.partial(proxy_from_url, QNetworkProxy.Socks5Proxy),
}
def __init__(self, none_ok=False): def __init__(self, none_ok=False):
super().__init__(none_ok) super().__init__(none_ok)
self.valid_values = ValidValues( self.valid_values = ValidValues(
@ -1055,19 +1024,18 @@ class Proxy(BaseType):
('none', "Don't use any proxy")) ('none', "Don't use any proxy"))
def validate(self, value): def validate(self, value):
from qutebrowser.utils import urlutils
self._basic_validation(value) self._basic_validation(value)
if not value: if not value:
return return
elif value in self.valid_values: elif value in self.valid_values:
return return
url = QUrl(value) url = QUrl(value)
if not url.isValid():
raise configexc.ValidationError( try:
value, "invalid url, {}".format(url.errorString())) self.transform(value)
elif url.scheme() not in self.PROXY_TYPES: except (urlutils.InvalidUrlError, urlutils.InvalidProxyTypeError) as e:
raise configexc.ValidationError(value, "must be a proxy URL " raise configexc.ValidationError(value, e)
"(http://... or socks://...) or "
"system/none!")
def complete(self): def complete(self):
out = [] out = []
@ -1081,14 +1049,17 @@ class Proxy(BaseType):
return out return out
def transform(self, value): def transform(self, value):
from qutebrowser.utils import urlutils
if not value: if not value:
return None return None
elif value == 'system': elif value == 'system':
return SYSTEM_PROXY return SYSTEM_PROXY
elif value == 'none':
return QNetworkProxy(QNetworkProxy.NoProxy) if value == 'none':
url = QUrl(value) url = QUrl('direct://')
return self.PROXY_TYPES[url.scheme()](url) else:
url = QUrl(value)
return urlutils.proxy_from_url(url)
class SearchEngineName(BaseType): class SearchEngineName(BaseType):

View File

@ -27,11 +27,12 @@ import posixpath
import urllib.parse import urllib.parse
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QHostInfo, QHostAddress from PyQt5.QtNetwork import QHostInfo, QHostAddress, QNetworkProxy
from qutebrowser.config import config, configexc from qutebrowser.config import config, configexc
from qutebrowser.utils import log, qtutils, message, utils from qutebrowser.utils import log, qtutils, message, utils
from qutebrowser.commands import cmdexc from qutebrowser.commands import cmdexc
from qutebrowser.browser.webkit.network import pac
# FIXME: we probably could raise some exceptions on invalid URLs # FIXME: we probably could raise some exceptions on invalid URLs
@ -589,3 +590,47 @@ def data_url(mimetype, data):
url = QUrl('data:{};base64,{}'.format(mimetype, b64)) url = QUrl('data:{};base64,{}'.format(mimetype, b64))
qtutils.ensure_valid(url) qtutils.ensure_valid(url)
return url return url
class InvalidProxyTypeError(Exception):
"""Error raised when proxy_from_url gets an unknown proxy type."""
def __init__(self, typ):
super().__init__("Invalid proxy type {}!".format(typ))
def proxy_from_url(url):
"""Create a QNetworkProxy from QUrl and a proxy type.
Args:
url: URL of a proxy (possibly with credentials).
Return:
New QNetworkProxy.
"""
if not url.isValid():
raise InvalidUrlError(url)
scheme = url.scheme()
if scheme in ['pac+http', 'pac+https']:
return pac.PACFetcher
types = {
'http': QNetworkProxy.HttpProxy,
'socks': QNetworkProxy.Socks5Proxy,
'socks5': QNetworkProxy.Socks5Proxy,
'direct': QNetworkProxy.NoProxy,
}
if scheme not in types:
raise InvalidProxyTypeError(scheme)
proxy = QNetworkProxy(types[scheme], url.host())
if url.port() != -1:
proxy.setPort(url.port())
if url.userName():
proxy.setUser(url.userName())
if url.password():
proxy.setPassword(url.password())
return proxy

View File

@ -59,16 +59,6 @@ class Font(QFont):
return f return f
class NetworkProxy(QNetworkProxy):
"""A QNetworkProxy with a nicer repr()."""
def __repr__(self):
return utils.get_repr(self, type=self.type(), hostName=self.hostName(),
port=self.port(), user=self.user(),
password=self.password())
class RegexEq: class RegexEq:
"""A class to compare regex objects.""" """A class to compare regex objects."""
@ -1490,10 +1480,7 @@ class TestProxy:
'system', 'system',
'none', 'none',
'http://user:pass@example.com:2323/', 'http://user:pass@example.com:2323/',
'socks://user:pass@example.com:2323/',
'socks5://user:pass@example.com:2323/',
'pac+http://example.com/proxy.pac', 'pac+http://example.com/proxy.pac',
'pac+https://example.com/proxy.pac',
]) ])
def test_validate_valid(self, klass, val): def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val) klass(none_ok=True).validate(val)
@ -1519,27 +1506,18 @@ class TestProxy:
@pytest.mark.parametrize('val, expected', [ @pytest.mark.parametrize('val, expected', [
('', None), ('', None),
('system', configtypes.SYSTEM_PROXY), ('system', configtypes.SYSTEM_PROXY),
('none', NetworkProxy(QNetworkProxy.NoProxy)), ('none', QNetworkProxy(QNetworkProxy.NoProxy)),
('socks://example.com/', ('socks://example.com/',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')), QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')),
('socks5://example.com',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')),
('socks5://example.com:2342',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2342)),
('socks5://foo@example.com',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo')),
('socks5://foo:bar@example.com',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo',
'bar')),
('socks5://foo:bar@example.com:2323', ('socks5://foo:bar@example.com:2323',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, 'foo', QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323,
'bar')), 'foo', 'bar')),
]) ])
def test_transform(self, klass, val, expected): def test_transform(self, klass, val, expected):
"""Test transform with an empty value.""" """Test transform with an empty value."""
actual = klass().transform(val) actual = klass().transform(val)
if isinstance(actual, QNetworkProxy): if isinstance(actual, QNetworkProxy):
actual = NetworkProxy(actual) actual = QNetworkProxy(actual)
assert actual == expected assert actual == expected

View File

@ -24,9 +24,11 @@ import collections
import logging import logging
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QNetworkProxy
import pytest import pytest
from qutebrowser.commands import cmdexc from qutebrowser.commands import cmdexc
from qutebrowser.browser.webkit.network import pac
from qutebrowser.utils import utils, urlutils, qtutils, usertypes from qutebrowser.utils import utils, urlutils, qtutils, usertypes
@ -736,3 +738,43 @@ def test_file_url():
def test_data_url(): def test_data_url():
url = urlutils.data_url('text/plain', b'foo') url = urlutils.data_url('text/plain', b'foo')
assert url == QUrl('data:text/plain;base64,Zm9v') assert url == QUrl('data:text/plain;base64,Zm9v')
class TestProxyFromUrl:
@pytest.mark.parametrize('url, expected', [
('socks://example.com/',
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')),
('socks5://example.com',
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')),
('socks5://example.com:2342',
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2342)),
('socks5://foo@example.com',
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo')),
('socks5://foo:bar@example.com',
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 0, 'foo',
'bar')),
('socks5://foo:bar@example.com:2323',
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323,
'foo', 'bar')),
('direct://', QNetworkProxy(QNetworkProxy.NoProxy)),
])
def test_proxy_from_url_valid(self, url, expected):
assert urlutils.proxy_from_url(QUrl(url)) == expected
@pytest.mark.parametrize('scheme', ['pac+http', 'pac+https'])
def test_proxy_from_url_pac(self, scheme):
fetcher = urlutils.proxy_from_url(QUrl('{}://foo'.format(scheme)))
assert fetcher is pac.PACFetcher
@pytest.mark.parametrize('url, exception', [
('blah', urlutils.InvalidProxyTypeError),
(':', urlutils.InvalidUrlError), # invalid URL
# Invalid/unsupported scheme
('ftp://example.com/', urlutils.InvalidProxyTypeError),
('socks4://example.com/', urlutils.InvalidProxyTypeError),
])
def test_invalid(self, url, exception):
with pytest.raises(exception):
urlutils.proxy_from_url(QUrl(url))