def hostnames( draw, allow_leading_digit=True, allow_idn=True ): # pragma: no cover # 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). """ labels = draw( lists(hostname_labels(allow_idn=allow_idn), min_size=1, max_size=5) .filter(lambda ls: sum(len(l) for l in ls) + len(ls) - 1 <= 252) ) name = u".".join(labels) # Filter names that are not IDNA-encodable. # We try pretty hard not to generate bogus names in the first place... but # catching all cases is not trivial. try: idna_encode(name) except IDNAError: assume(False) return name
def hostnames(draw, allow_leading_digit=True, allow_idn=True): # pragma: no cover # 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). """ labels = cast( Sequence[Text], draw( lists( hostname_labels(allow_idn=allow_idn), min_size=1, max_size=5).filter( lambda ls: sum(len(s) for s in ls) + len(ls) - 1 <= 252)), ) name = u".".join(labels) # Filter names that are not IDNA-encodable. # We try pretty hard not to generate bogus names in the first place... but # catching all cases is not trivial. try: idna_encode(name) except IDNAError: assume(False) return name
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
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))
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))
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))
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))