okay fine

This commit is contained in:
pacnpal
2024-11-03 17:47:26 +00:00
parent 01c6004a79
commit 27eb239e97
10020 changed files with 1935769 additions and 2364 deletions

View File

@@ -0,0 +1,17 @@
from ._url import (
parse,
register_scheme,
URL,
EncodedURL,
DecodedURL,
URLParseError,
)
__all__ = (
"parse",
"register_scheme",
"URL",
"EncodedURL",
"DecodedURL",
"URLParseError",
)

View File

@@ -0,0 +1,53 @@
try:
from socket import inet_pton
except ImportError:
from typing import TYPE_CHECKING
if TYPE_CHECKING: # pragma: no cover
pass
else:
# based on https://gist.github.com/nnemkin/4966028
# this code only applies on Windows Python 2.7
import ctypes
import socket
class SockAddr(ctypes.Structure):
_fields_ = [
("sa_family", ctypes.c_short),
("__pad1", ctypes.c_ushort),
("ipv4_addr", ctypes.c_byte * 4),
("ipv6_addr", ctypes.c_byte * 16),
("__pad2", ctypes.c_ulong),
]
WSAStringToAddressA = ctypes.windll.ws2_32.WSAStringToAddressA
WSAAddressToStringA = ctypes.windll.ws2_32.WSAAddressToStringA
def inet_pton(address_family, ip_string):
# type: (int, str) -> bytes
addr = SockAddr()
ip_string_bytes = ip_string.encode("ascii")
addr.sa_family = address_family
addr_size = ctypes.c_int(ctypes.sizeof(addr))
try:
attribute, size = {
socket.AF_INET: ("ipv4_addr", 4),
socket.AF_INET6: ("ipv6_addr", 16),
}[address_family]
except KeyError:
raise socket.error("unknown address family")
if (
WSAStringToAddressA(
ip_string_bytes,
address_family,
None,
ctypes.byref(addr),
ctypes.byref(addr_size),
)
!= 0
):
raise socket.error(ctypes.FormatError())
return ctypes.string_at(getattr(addr, attribute), size)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,321 @@
# -*- coding: utf-8 -*-
"""
Hypothesis strategies.
"""
from __future__ import absolute_import
try:
import hypothesis
del hypothesis
except ImportError:
from typing import Tuple
__all__ = () # type: Tuple[str, ...]
else:
from csv import reader as csv_reader
from os.path import dirname, join
from string import ascii_letters, digits
from sys import maxunicode
from typing import (
Callable,
Iterable,
List,
Optional,
Sequence,
Text,
TypeVar,
cast,
)
from gzip import open as open_gzip
from . import DecodedURL, EncodedURL
from hypothesis import assume
from hypothesis.strategies import (
composite,
integers,
lists,
sampled_from,
text,
)
from idna import IDNAError, check_label, encode as idna_encode
__all__ = (
"decoded_urls",
"encoded_urls",
"hostname_labels",
"hostnames",
"idna_text",
"paths",
"port_numbers",
)
T = TypeVar("T")
DrawCallable = Callable[[Callable[..., T]], T]
try:
unichr
except NameError: # Py3
unichr = chr # type: Callable[[int], Text]
def idna_characters():
# type: () -> Text
"""
Returns a string containing IDNA characters.
"""
global _idnaCharacters
if not _idnaCharacters:
result = []
# Data source "IDNA Derived Properties":
# https://www.iana.org/assignments/idna-tables-6.3.0/
# idna-tables-6.3.0.xhtml#idna-tables-properties
dataFileName = join(
dirname(__file__), "idna-tables-properties.csv.gz"
)
with open_gzip(dataFileName) as dataFile:
reader = csv_reader(
(line.decode("utf-8") for line in dataFile),
delimiter=",",
)
next(reader) # Skip header row
for row in reader:
codes, prop, description = row
if prop != "PVALID":
# CONTEXTO or CONTEXTJ are also allowed, but they come
# with rules, so we're punting on those here.
# See: https://tools.ietf.org/html/rfc5892
continue
startEnd = row[0].split("-", 1)
if len(startEnd) == 1:
# No end of range given; use start
startEnd.append(startEnd[0])
start, end = (int(i, 16) for i in startEnd)
for i in range(start, end + 1):
if i > maxunicode: # Happens using Py2 on Windows
break
result.append(unichr(i))
_idnaCharacters = u"".join(result)
return _idnaCharacters
_idnaCharacters = "" # type: Text
@composite
def idna_text(draw, min_size=1, max_size=None):
# type: (DrawCallable, int, Optional[int]) -> Text
"""
A strategy which generates IDNA-encodable text.
@param min_size: The minimum number of characters in the text.
C{None} is treated as C{0}.
@param max_size: The maximum number of characters in the text.
Use C{None} for an unbounded size.
"""
alphabet = idna_characters()
assert min_size >= 1
if max_size is not None:
assert max_size >= 1
result = cast(
Text,
draw(text(min_size=min_size, max_size=max_size, alphabet=alphabet)),
)
# FIXME: There should be a more efficient way to ensure we produce
# valid IDNA text.
try:
idna_encode(result)
except IDNAError:
assume(False)
return result
@composite
def port_numbers(draw, allow_zero=False):
# type: (DrawCallable, bool) -> int
"""
A strategy which generates port numbers.
@param allow_zero: Whether to allow port C{0} as a possible value.
"""
if allow_zero:
min_value = 0
else:
min_value = 1
return cast(int, draw(integers(min_value=min_value, max_value=65535)))
@composite
def hostname_labels(draw, allow_idn=True):
# type: (DrawCallable, bool) -> Text
"""
A strategy which generates host name labels.
@param allow_idn: Whether to allow non-ASCII characters as allowed by
internationalized domain names (IDNs).
"""
if allow_idn:
label = cast(Text, draw(idna_text(min_size=1, max_size=63)))
try:
label.encode("ascii")
except UnicodeEncodeError:
# If the label doesn't encode to ASCII, then we need to check
# the length of the label after encoding to punycode and adding
# the xn-- prefix.
while len(label.encode("punycode")) > 63 - len("xn--"):
# Rather than bombing out, just trim from the end until it
# is short enough, so hypothesis doesn't have to generate
# new data.
label = label[:-1]
else:
label = cast(
Text,
draw(
text(
min_size=1,
max_size=63,
alphabet=Text(ascii_letters + digits + u"-"),
)
),
)
# Filter invalid labels.
# It would be better to reliably avoid generation of bogus labels in
# the first place, but it's hard...
try:
check_label(label)
except UnicodeError: # pragma: no cover (not always drawn)
assume(False)
return label
@composite
def hostnames(draw, allow_leading_digit=True, allow_idn=True):
# type: (DrawCallable, bool, bool) -> Text
"""
A strategy which generates host names.
@param allow_leading_digit: Whether to allow a leading digit in host
names; they were not allowed prior to RFC 1123.
@param allow_idn: Whether to allow non-ASCII characters as allowed by
internationalized domain names (IDNs).
"""
# Draw first label, filtering out labels with leading digits if needed
labels = [
cast(
Text,
draw(
hostname_labels(allow_idn=allow_idn).filter(
lambda l: (
True if allow_leading_digit else l[0] not in digits
)
)
),
)
]
# Draw remaining labels
labels += cast(
List[Text],
draw(
lists(
hostname_labels(allow_idn=allow_idn),
min_size=1,
max_size=4,
)
),
)
# Trim off labels until the total host name length fits in 252
# characters. This avoids having to filter the data.
while sum(len(label) for label in labels) + len(labels) - 1 > 252:
labels = labels[:-1]
return u".".join(labels)
def path_characters():
# type: () -> str
"""
Returns a string containing valid URL path characters.
"""
global _path_characters
if _path_characters is None:
def chars():
# type: () -> Iterable[Text]
for i in range(maxunicode):
c = unichr(i)
# Exclude reserved characters
if c in "#/?":
continue
# Exclude anything not UTF-8 compatible
try:
c.encode("utf-8")
except UnicodeEncodeError:
continue
yield c
_path_characters = "".join(chars())
return _path_characters
_path_characters = None # type: Optional[str]
@composite
def paths(draw):
# type: (DrawCallable) -> Sequence[Text]
return cast(
List[Text],
draw(
lists(text(min_size=1, alphabet=path_characters()), max_size=10)
),
)
@composite
def encoded_urls(draw):
# type: (DrawCallable) -> EncodedURL
"""
A strategy which generates L{EncodedURL}s.
Call the L{EncodedURL.to_uri} method on each URL to get an HTTP
protocol-friendly URI.
"""
port = cast(Optional[int], draw(port_numbers(allow_zero=True)))
host = cast(Text, draw(hostnames()))
path = cast(Sequence[Text], draw(paths()))
if port == 0:
port = None
return EncodedURL(
scheme=cast(Text, draw(sampled_from((u"http", u"https")))),
host=host,
port=port,
path=path,
)
@composite
def decoded_urls(draw):
# type: (DrawCallable) -> DecodedURL
"""
A strategy which generates L{DecodedURL}s.
Call the L{EncodedURL.to_uri} method on each URL to get an HTTP
protocol-friendly URI.
"""
return DecodedURL(draw(encoded_urls()))

View File

@@ -0,0 +1 @@
# See: https://www.python.org/dev/peps/pep-0561/

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""
Tests for hyperlink
"""
__all = ()
def _init_hypothesis():
# type: () -> None
from os import environ
if "CI" in environ:
try:
from hypothesis import HealthCheck, settings
except ImportError:
return
settings.register_profile(
"patience",
settings(
suppress_health_check=[
HealthCheck.too_slow,
HealthCheck.filter_too_much,
]
),
)
settings.load_profile("patience")
_init_hypothesis()

View File

@@ -0,0 +1,68 @@
from typing import Any, Callable, Optional, Type
from unittest import TestCase
class HyperlinkTestCase(TestCase):
"""This type mostly exists to provide a backwards-compatible
assertRaises method for Python 2.6 testing.
"""
def assertRaises( # type: ignore[override]
self,
expected_exception, # type: Type[BaseException]
callableObj=None, # type: Optional[Callable[..., Any]]
*args, # type: Any
**kwargs # type: Any
):
# type: (...) -> Any
"""Fail unless an exception of class expected_exception is raised
by callableObj when invoked with arguments args and keyword
arguments kwargs. If a different type of exception is
raised, it will not be caught, and the test case will be
deemed to have suffered an error, exactly as for an
unexpected exception.
If called with callableObj omitted or None, will return a
context object used like this::
with self.assertRaises(SomeException):
do_something()
The context manager keeps a reference to the exception as
the 'exception' attribute. This allows you to inspect the
exception after the assertion::
with self.assertRaises(SomeException) as cm:
do_something()
the_exception = cm.exception
self.assertEqual(the_exception.error_code, 3)
"""
context = _AssertRaisesContext(expected_exception, self)
if callableObj is None:
return context
with context:
callableObj(*args, **kwargs)
class _AssertRaisesContext(object):
"A context manager used to implement HyperlinkTestCase.assertRaises."
def __init__(self, expected, test_case):
# type: (Type[BaseException], TestCase) -> None
self.expected = expected
self.failureException = test_case.failureException
def __enter__(self):
# type: () -> "_AssertRaisesContext"
return self
def __exit__(self, exc_type, exc_value, tb):
# type: (Optional[Type[BaseException]], Any, Any) -> bool
if exc_type is None:
exc_name = self.expected.__name__
raise self.failureException("%s not raised" % (exc_name,))
if not issubclass(exc_type, self.expected):
# let unexpected exceptions pass through
return False
self.exception = exc_value # store for later retrieval
return True

View File

@@ -0,0 +1,116 @@
"""
Tests for hyperlink.test.common
"""
from typing import Any
from unittest import TestCase
from .common import HyperlinkTestCase
class _ExpectedException(Exception):
"""An exception used to test HyperlinkTestCase.assertRaises."""
class _UnexpectedException(Exception):
"""An exception used to test HyperlinkTestCase.assertRaises."""
class TestHyperlink(TestCase):
"""Tests for HyperlinkTestCase"""
def setUp(self):
# type: () -> None
self.hyperlink_test = HyperlinkTestCase("run")
def test_assertRaisesWithCallable(self):
# type: () -> None
"""HyperlinkTestCase.assertRaises does not raise an AssertionError
when given a callable that, when called with the provided
arguments, raises the expected exception.
"""
called_with = []
def raisesExpected(*args, **kwargs):
# type: (Any, Any) -> None
called_with.append((args, kwargs))
raise _ExpectedException
self.hyperlink_test.assertRaises(
_ExpectedException, raisesExpected, 1, keyword=True
)
self.assertEqual(called_with, [((1,), {"keyword": True})])
def test_[AWS-SECRET-REMOVED]ion(self):
# type: () -> None
"""When given a callable that raises an unexpected exception,
HyperlinkTestCase.assertRaises raises that exception.
"""
def doesNotRaiseExpected(*args, **kwargs):
# type: (Any, Any) -> None
raise _UnexpectedException
try:
self.hyperlink_test.assertRaises(
_ExpectedException, doesNotRaiseExpected
)
except _UnexpectedException:
pass
def test_assertRaisesWithCallableDoesNotRaise(self):
# type: () -> None
"""HyperlinkTestCase.assertRaises raises an AssertionError when given
a callable that, when called, does not raise any exception.
"""
def doesNotRaise(*args, **kwargs):
# type: (Any, Any) -> None
pass
try:
self.hyperlink_test.assertRaises(_ExpectedException, doesNotRaise)
except AssertionError:
pass
def test_assertRaisesContextManager(self):
# type: () -> None
"""HyperlinkTestCase.assertRaises does not raise an AssertionError
when used as a context manager with a suite that raises the
expected exception. The context manager stores the exception
instance under its `exception` instance variable.
"""
with self.hyperlink_test.assertRaises(_ExpectedException) as cm:
raise _ExpectedException
self.assertTrue( # type: ignore[unreachable]
isinstance(cm.exception, _ExpectedException)
)
def test_[AWS-SECRET-REMOVED]ption(self):
# type: () -> None
"""When used as a context manager with a block that raises an
unexpected exception, HyperlinkTestCase.assertRaises raises
that unexpected exception.
"""
try:
with self.hyperlink_test.assertRaises(_ExpectedException):
raise _UnexpectedException
except _UnexpectedException:
pass
def test_assertRaisesContextManagerDoesNotRaise(self):
# type: () -> None
"""HyperlinkTestcase.assertRaises raises an AssertionError when used
as a context manager with a block that does not raise any
exception.
"""
try:
with self.hyperlink_test.assertRaises(_ExpectedException):
pass
except AssertionError:
pass

View File

@@ -0,0 +1,228 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from typing import Dict, Union
from .. import DecodedURL, URL
from .._url import _percent_decode
from .common import HyperlinkTestCase
BASIC_URL = "http://example.com/#"
TOTAL_URL = (
"https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080/"
"a/nice%20nice/./path/?zot=23%25&zut#frég"
)
class TestURL(HyperlinkTestCase):
def test_durl_basic(self):
# type: () -> None
bdurl = DecodedURL.from_text(BASIC_URL)
assert bdurl.scheme == "http"
assert bdurl.host == "example.com"
assert bdurl.port == 80
assert bdurl.path == ("",)
assert bdurl.fragment == ""
durl = DecodedURL.from_text(TOTAL_URL)
assert durl.scheme == "https"
assert durl.host == "bücher.ch"
assert durl.port == 8080
assert durl.path == ("a", "nice nice", ".", "path", "")
assert durl.fragment == "frég"
assert durl.get("zot") == ["23%"]
assert durl.user == "user"
assert durl.userinfo == ("user", "\0\0\0\0")
def test_passthroughs(self):
# type: () -> None
# just basic tests for the methods that more or less pass straight
# through to the underlying URL
durl = DecodedURL.from_text(TOTAL_URL)
assert durl.sibling("te%t").path[-1] == "te%t"
assert durl.child("../test2%").path[-1] == "../test2%"
assert durl.child() == durl
assert durl.child() is durl
assert durl.click("/").path[-1] == ""
assert durl.user == "user"
assert "." in durl.path
assert "." not in durl.normalize().path
assert durl.to_uri().fragment == "fr%C3%A9g"
assert " " in durl.to_iri().path[1]
assert durl.to_text(with_password=True) == TOTAL_URL
assert durl.absolute
assert durl.rooted
assert durl == durl.encoded_url.get_decoded_url()
durl2 = DecodedURL.from_text(TOTAL_URL, lazy=True)
assert durl2 == durl2.encoded_url.get_decoded_url(lazy=True)
assert (
str(DecodedURL.from_text(BASIC_URL).child(" "))
== "http://example.com/%20"
)
assert not (durl == 1)
assert durl != 1
def test_repr(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
assert repr(durl) == "DecodedURL(url=" + repr(durl._url) + ")"
def test_query_manipulation(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
assert durl.get("zot") == ["23%"]
durl = durl.add(" ", "space")
assert durl.get(" ") == ["space"]
durl = durl.set(" ", "spa%ed")
assert durl.get(" ") == ["spa%ed"]
durl = DecodedURL(url=durl.to_uri())
assert durl.get(" ") == ["spa%ed"]
durl = durl.remove(" ")
assert durl.get(" ") == []
durl = DecodedURL.from_text("/?%61rg=b&arg=c")
assert durl.get("arg") == ["b", "c"]
assert durl.set("arg", "d").get("arg") == ["d"]
durl = DecodedURL.from_text(
"https://example.com/a/b/?fóó=1&bar=2&fóó=3"
)
assert durl.remove("fóó") == DecodedURL.from_text(
"https://example.com/a/b/?bar=2"
)
assert durl.remove("fóó", value="1") == DecodedURL.from_text(
"https://example.com/a/b/?bar=2&fóó=3"
)
assert durl.remove("fóó", limit=1) == DecodedURL.from_text(
"https://example.com/a/b/?bar=2&fóó=3"
)
assert durl.remove("fóó", value="1", limit=0) == DecodedURL.from_text(
"https://example.com/a/b/?fóó=1&bar=2&fóó=3"
)
def test_equality_and_hashability(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
durl2 = DecodedURL.from_text(TOTAL_URL)
burl = DecodedURL.from_text(BASIC_URL)
durl_uri = durl.to_uri()
assert durl == durl
assert durl == durl2
assert durl != burl
assert durl is not None
assert durl != durl._url
AnyURL = Union[URL, DecodedURL]
durl_map = {} # type: Dict[AnyURL, AnyURL]
durl_map[durl] = durl
durl_map[durl2] = durl2
assert len(durl_map) == 1
durl_map[burl] = burl
assert len(durl_map) == 2
durl_map[durl_uri] = durl_uri
assert len(durl_map) == 3
def test_replace_roundtrip(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
durl2 = durl.replace(
scheme=durl.scheme,
host=durl.host,
path=durl.path,
query=durl.query,
fragment=durl.fragment,
port=durl.port,
rooted=durl.rooted,
userinfo=durl.userinfo,
uses_netloc=durl.uses_netloc,
)
assert durl == durl2
def test_replace_userinfo(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
with self.assertRaises(ValueError):
durl.replace(
userinfo=( # type: ignore[arg-type]
"user",
"pw",
"thiswillcauseafailure",
)
)
return
def test_twisted_compat(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
assert durl == DecodedURL.fromText(TOTAL_URL)
assert "to_text" in dir(durl)
assert "asText" not in dir(durl)
assert durl.to_text() == durl.asText()
def test_percent_decode_mixed(self):
# type: () -> None
# See https://github.com/python-hyper/hyperlink/pull/59 for a
# nice discussion of the possibilities
assert _percent_decode("abcdé%C3%A9éfg") == "abcdéééfg"
# still allow percent encoding in the case of an error
assert _percent_decode("abcdé%C3éfg") == "abcdé%C3éfg"
# ...unless explicitly told otherwise
with self.assertRaises(UnicodeDecodeError):
_percent_decode("abcdé%C3éfg", raise_subencoding_exc=True)
# when not encodable as subencoding
assert _percent_decode("é%25é", subencoding="ascii") == "é%25é"
def test_click_decoded_url(self):
# type: () -> None
durl = DecodedURL.from_text(TOTAL_URL)
durl_dest = DecodedURL.from_text("/tëst")
clicked = durl.click(durl_dest)
assert clicked.host == durl.host
assert clicked.path == durl_dest.path
assert clicked.path == ("tëst",)
def test_decode_plus(self):
# type: () -> None
durl = DecodedURL.from_text("/x+y%2B?a=b+c%2B")
assert durl.path == ("x+y+",)
assert durl.get("a") == ["b c+"]
assert durl.query == (("a", "b c+"),)
def test_decode_nonplussed(self):
# type: () -> None
durl = DecodedURL.from_text(
"/x+y%2B?a=b+c%2B", query_plus_is_space=False
)
assert durl.path == ("x+y+",)
assert durl.get("a") == ["b+c+"]
assert durl.query == (("a", "b+c+"),)

View File

@@ -0,0 +1,214 @@
# -*- coding: utf-8 -*-
"""
Tests for hyperlink.hypothesis.
"""
try:
import hypothesis
del hypothesis
except ImportError:
pass
else:
from string import digits
from typing import Sequence, Text
try:
from unittest.mock import patch
except ImportError:
from mock import patch # type: ignore[misc]
from hypothesis import given, settings
from hypothesis.strategies import SearchStrategy, data
from idna import IDNAError, check_label, encode as idna_encode
from .common import HyperlinkTestCase
from .. import DecodedURL, EncodedURL
from ..hypothesis import (
DrawCallable,
composite,
decoded_urls,
encoded_urls,
hostname_labels,
hostnames,
idna_text,
paths,
port_numbers,
)
class TestHypothesisStrategies(HyperlinkTestCase):
"""
Tests for hyperlink.hypothesis.
"""
@given(idna_text())
def test_idna_text_valid(self, text):
# type: (Text) -> None
"""
idna_text() generates IDNA-encodable text.
"""
try:
idna_encode(text)
except IDNAError: # pragma: no cover
raise AssertionError("Invalid IDNA text: {!r}".format(text))
@given(data())
def test_idna_text_min_max(self, data):
# type: (SearchStrategy) -> None
"""
idna_text() raises AssertionError if min_size is < 1.
"""
self.assertRaises(AssertionError, data.draw, idna_text(min_size=0))
self.assertRaises(AssertionError, data.draw, idna_text(max_size=0))
@given(port_numbers())
def test_port_numbers_bounds(self, port):
# type: (int) -> None
"""
port_numbers() generates integers between 1 and 65535, inclusive.
"""
self.assertGreaterEqual(port, 1)
self.assertLessEqual(port, 65535)
@given(port_numbers(allow_zero=True))
def test_port_numbers_bounds_allow_zero(self, port):
# type: (int) -> None
"""
port_numbers(allow_zero=True) generates integers between 0 and
65535, inclusive.
"""
self.assertGreaterEqual(port, 0)
self.assertLessEqual(port, 65535)
@given(hostname_labels())
def test_hostname_labels_valid_idn(self, label):
# type: (Text) -> None
"""
hostname_labels() generates IDN host name labels.
"""
try:
check_label(label)
idna_encode(label)
except UnicodeError: # pragma: no cover
raise AssertionError("Invalid IDN label: {!r}".format(label))
@given(data())
@settings(max_examples=10)
def test_hostname_labels_long_idn_punycode(self, data):
# type: (SearchStrategy) -> None
"""
hostname_labels() handles case where idna_text() generates text
that encoded to punycode ends up as longer than allowed.
"""
@composite
def mock_idna_text(draw, min_size, max_size):
# type: (DrawCallable, int, int) -> Text
# We want a string that does not exceed max_size, but when
# encoded to punycode, does exceed max_size.
# So use a unicode character that is larger when encoded,
# "á" being a great example, and use it max_size times, which
# will be max_size * 3 in size when encoded.
return u"\N{LATIN SMALL LETTER A WITH ACUTE}" * max_size
with patch("hyperlink.hypothesis.idna_text", mock_idna_text):
label = data.draw(hostname_labels())
try:
check_label(label)
idna_encode(label)
except UnicodeError: # pragma: no cover
raise AssertionError(
"Invalid IDN label: {!r}".format(label)
)
@given(hostname_labels(allow_idn=False))
def test_hostname_labels_valid_ascii(self, label):
# type: (Text) -> None
"""
hostname_labels() generates a ASCII host name labels.
"""
try:
check_label(label)
label.encode("ascii")
except UnicodeError: # pragma: no cover
raise AssertionError("Invalid ASCII label: {!r}".format(label))
@given(hostnames())
def test_hostnames_idn(self, hostname):
# type: (Text) -> None
"""
hostnames() generates a IDN host names.
"""
try:
for label in hostname.split(u"."):
check_label(label)
idna_encode(hostname)
except UnicodeError: # pragma: no cover
raise AssertionError(
"Invalid IDN host name: {!r}".format(hostname)
)
@given(hostnames(allow_leading_digit=False))
def test_hostnames_idn_nolead(self, hostname):
# type: (Text) -> None
"""
hostnames(allow_leading_digit=False) generates a IDN host names
without leading digits.
"""
self.assertTrue(hostname == hostname.lstrip(digits))
@given(hostnames(allow_idn=False))
def test_hostnames_ascii(self, hostname):
# type: (Text) -> None
"""
hostnames() generates a ASCII host names.
"""
try:
for label in hostname.split(u"."):
check_label(label)
hostname.encode("ascii")
except UnicodeError: # pragma: no cover
raise AssertionError(
"Invalid ASCII host name: {!r}".format(hostname)
)
@given(hostnames(allow_leading_digit=False, allow_idn=False))
def test_hostnames_ascii_nolead(self, hostname):
# type: (Text) -> None
"""
hostnames(allow_leading_digit=False, allow_idn=False) generates
ASCII host names without leading digits.
"""
self.assertTrue(hostname == hostname.lstrip(digits))
@given(paths())
def test_paths(self, path):
# type: (Sequence[Text]) -> None
"""
paths() generates sequences of URL path components.
"""
text = u"/".join(path)
try:
text.encode("utf-8")
except UnicodeError: # pragma: no cover
raise AssertionError("Invalid URL path: {!r}".format(path))
for segment in path:
self.assertNotIn("#/?", segment)
@given(encoded_urls())
def test_encoded_urls(self, url):
# type: (EncodedURL) -> None
"""
encoded_urls() generates EncodedURLs.
"""
self.assertIsInstance(url, EncodedURL)
@given(decoded_urls())
def test_decoded_urls(self, url):
# type: (DecodedURL) -> None
"""
decoded_urls() generates DecodedURLs.
"""
self.assertIsInstance(url, DecodedURL)

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from .common import HyperlinkTestCase
from hyperlink import parse, EncodedURL, DecodedURL
BASIC_URL = "http://example.com/#"
TOTAL_URL = (
"https://%75%73%65%72:%00%00%00%00@xn--bcher-kva.ch:8080"
"/a/nice%20nice/./path/?zot=23%25&zut#frég"
)
UNDECODABLE_FRAG_URL = TOTAL_URL + "%C3"
# the %C3 above percent-decodes to an unpaired \xc3 byte which makes this
# invalid utf8
class TestURL(HyperlinkTestCase):
def test_parse(self):
# type: () -> None
purl = parse(TOTAL_URL)
assert isinstance(purl, DecodedURL)
assert purl.user == "user"
assert purl.get("zot") == ["23%"]
assert purl.fragment == "frég"
purl2 = parse(TOTAL_URL, decoded=False)
assert isinstance(purl2, EncodedURL)
assert purl2.get("zot") == ["23%25"]
with self.assertRaises(UnicodeDecodeError):
purl3 = parse(UNDECODABLE_FRAG_URL)
purl3 = parse(UNDECODABLE_FRAG_URL, lazy=True)
with self.assertRaises(UnicodeDecodeError):
purl3.fragment

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from typing import cast
from .. import _url
from .common import HyperlinkTestCase
from .._url import register_scheme, URL, DecodedURL
class TestSchemeRegistration(HyperlinkTestCase):
def setUp(self):
# type: () -> None
self._orig_scheme_port_map = dict(_url.SCHEME_PORT_MAP)
self._orig_no_netloc_schemes = set(_url.NO_NETLOC_SCHEMES)
def tearDown(self):
# type: () -> None
_url.SCHEME_PORT_MAP = self._orig_scheme_port_map
_url.NO_NETLOC_SCHEMES = self._orig_no_netloc_schemes
def test_register_scheme_basic(self):
# type: () -> None
register_scheme("deltron", uses_netloc=True, default_port=3030)
u1 = URL.from_text("deltron://example.com")
assert u1.scheme == "deltron"
assert u1.port == 3030
assert u1.uses_netloc is True
# test netloc works even when the original gives no indication
u2 = URL.from_text("deltron:")
u2 = u2.replace(host="example.com")
assert u2.to_text() == "deltron://example.com"
# test default port means no emission
u3 = URL.from_text("deltron://example.com:3030")
assert u3.to_text() == "deltron://example.com"
register_scheme("nonetron", default_port=3031)
u4 = URL(scheme="nonetron")
u4 = u4.replace(host="example.com")
assert u4.to_text() == "nonetron://example.com"
def test_register_no_netloc_scheme(self):
# type: () -> None
register_scheme("noloctron", uses_netloc=False)
u4 = URL(scheme="noloctron")
u4 = u4.replace(path=("example", "path"))
assert u4.to_text() == "noloctron:example/path"
def test_register_no_netloc_with_port(self):
# type: () -> None
with self.assertRaises(ValueError):
register_scheme("badnetlocless", uses_netloc=False, default_port=7)
def test_invalid_uses_netloc(self):
# type: () -> None
with self.assertRaises(ValueError):
register_scheme("badnetloc", uses_netloc=cast(bool, None))
with self.assertRaises(ValueError):
register_scheme("badnetloc", uses_netloc=cast(bool, object()))
def test_register_invalid_uses_netloc(self):
# type: () -> None
with self.assertRaises(ValueError):
register_scheme("lol", uses_netloc=cast(bool, object()))
def test_register_invalid_port(self):
# type: () -> None
with self.assertRaises(ValueError):
register_scheme("nope", default_port=cast(bool, object()))
def test_register_no_quote_plus_scheme(self):
# type: () -> None
register_scheme("keepplus", query_plus_is_space=False)
plus_is_not_space = DecodedURL.from_text(
"keepplus://example.com/?q=a+b"
)
plus_is_space = DecodedURL.from_text("https://example.com/?q=a+b")
assert plus_is_not_space.get("q") == ["a+b"]
assert plus_is_space.get("q") == ["a b"]

View File

@@ -0,0 +1,45 @@
# mypy: always-true=inet_pton
try:
from socket import inet_pton
except ImportError:
inet_pton = None # type: ignore[assignment]
if not inet_pton:
import socket
from .common import HyperlinkTestCase
from .._socket import inet_pton
class TestSocket(HyperlinkTestCase):
def test_inet_pton_ipv4_valid(self):
# type: () -> None
data = inet_pton(socket.AF_INET, "127.0.0.1")
assert isinstance(data, bytes)
def test_inet_pton_ipv4_bogus(self):
# type: () -> None
with self.assertRaises(socket.error):
inet_pton(socket.AF_INET, "blah")
def test_inet_pton_ipv6_valid(self):
# type: () -> None
data = inet_pton(socket.AF_INET6, "::1")
assert isinstance(data, bytes)
def test_inet_pton_ipv6_bogus(self):
# type: () -> None
with self.assertRaises(socket.error):
inet_pton(socket.AF_INET6, "blah")
def test_inet_pton_bogus_family(self):
# type: () -> None
# Find an integer not associated with a known address family
i = int(socket.AF_INET6)
while True:
if i != socket.AF_INET and i != socket.AF_INET6:
break
i += 100
with self.assertRaises(socket.error):
inet_pton(i, "127.0.0.1")

File diff suppressed because it is too large Load Diff