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 warnings
import datetime
import functools
from PyQt5.QtCore import QUrl, Qt
from PyQt5.QtGui import QColor, QFont
from PyQt5.QtNetwork import QNetworkProxy
from PyQt5.QtWidgets import QTabWidget, QTabBar
from qutebrowser.commands import cmdutils
from qutebrowser.config import configexc
from qutebrowser.utils import standarddir, utils
from qutebrowser.browser.webkit.network import pac
SYSTEM_PROXY = object() # Return value for Proxy type
@ -1016,38 +1013,10 @@ class ShellCommand(BaseType):
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):
"""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):
super().__init__(none_ok)
self.valid_values = ValidValues(
@ -1055,19 +1024,18 @@ class Proxy(BaseType):
('none', "Don't use any proxy"))
def validate(self, value):
from qutebrowser.utils import urlutils
self._basic_validation(value)
if not value:
return
elif value in self.valid_values:
return
url = QUrl(value)
if not url.isValid():
raise configexc.ValidationError(
value, "invalid url, {}".format(url.errorString()))
elif url.scheme() not in self.PROXY_TYPES:
raise configexc.ValidationError(value, "must be a proxy URL "
"(http://... or socks://...) or "
"system/none!")
try:
self.transform(value)
except (urlutils.InvalidUrlError, urlutils.InvalidProxyTypeError) as e:
raise configexc.ValidationError(value, e)
def complete(self):
out = []
@ -1081,14 +1049,17 @@ class Proxy(BaseType):
return out
def transform(self, value):
from qutebrowser.utils import urlutils
if not value:
return None
elif value == 'system':
return SYSTEM_PROXY
elif value == 'none':
return QNetworkProxy(QNetworkProxy.NoProxy)
url = QUrl(value)
return self.PROXY_TYPES[url.scheme()](url)
if value == 'none':
url = QUrl('direct://')
else:
url = QUrl(value)
return urlutils.proxy_from_url(url)
class SearchEngineName(BaseType):

View File

@ -27,11 +27,12 @@ import posixpath
import urllib.parse
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.utils import log, qtutils, message, utils
from qutebrowser.commands import cmdexc
from qutebrowser.browser.webkit.network import pac
# 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))
qtutils.ensure_valid(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
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:
"""A class to compare regex objects."""
@ -1490,10 +1480,7 @@ class TestProxy:
'system',
'none',
'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+https://example.com/proxy.pac',
])
def test_validate_valid(self, klass, val):
klass(none_ok=True).validate(val)
@ -1519,27 +1506,18 @@ class TestProxy:
@pytest.mark.parametrize('val, expected', [
('', None),
('system', configtypes.SYSTEM_PROXY),
('none', NetworkProxy(QNetworkProxy.NoProxy)),
('none', QNetworkProxy(QNetworkProxy.NoProxy)),
('socks://example.com/',
NetworkProxy(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')),
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com')),
('socks5://foo:bar@example.com:2323',
NetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323, 'foo',
'bar')),
QNetworkProxy(QNetworkProxy.Socks5Proxy, 'example.com', 2323,
'foo', 'bar')),
])
def test_transform(self, klass, val, expected):
"""Test transform with an empty value."""
actual = klass().transform(val)
if isinstance(actual, QNetworkProxy):
actual = NetworkProxy(actual)
actual = QNetworkProxy(actual)
assert actual == expected

View File

@ -24,9 +24,11 @@ import collections
import logging
from PyQt5.QtCore import QUrl
from PyQt5.QtNetwork import QNetworkProxy
import pytest
from qutebrowser.commands import cmdexc
from qutebrowser.browser.webkit.network import pac
from qutebrowser.utils import utils, urlutils, qtutils, usertypes
@ -736,3 +738,43 @@ def test_file_url():
def test_data_url():
url = urlutils.data_url('text/plain', b'foo')
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))