From 1aa6119e319b9478e16c7277b2b411cecfc5c7ea Mon Sep 17 00:00:00 2001 From: Jovial Joe Jayarson Date: Mon, 10 Apr 2023 17:15:47 +0530 Subject: [PATCH] fix: removes 64-char limit for url path & query - removes IDNA enforced 64-char limit on url path & query - validates absurd quoted url: `http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com` - updates dependencies, changelog and pre-commit-hooks - bumps patch version **Related items** *Issues* - Closes #257 --- .pre-commit-config.yaml | 4 +- CHANGES.md | 10 ++++ poetry.lock | 102 ++++++++++++++++------------------------ pyproject.toml | 14 +++--- tests/test_url.py | 2 + validators/__init__.py | 2 +- validators/url.py | 20 ++++++-- 7 files changed, 78 insertions(+), 76 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 21068c66..f97936bc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -14,7 +14,7 @@ repos: args: ["--branch", "master"] - id: trailing-whitespace - repo: https://github.com/psf/black - rev: 23.1.0 + rev: 23.3.0 hooks: - id: black - repo: https://github.com/PyCQA/isort @@ -22,6 +22,6 @@ repos: hooks: - id: isort - repo: https://github.com/PyCQA/flake8 - rev: 5.0.4 + rev: 6.0.0 hooks: - id: flake8 diff --git a/CHANGES.md b/CHANGES.md index 8f9dfe8d..243e1a4c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,15 @@ # Changelog +## 0.21.1 (2023-04-10) + +- fix: `source .venv/bin/activate` before build by @joe733 in [#260](https://github.com/python-validators/validators/pull/260) +- fix: id-token write permission at job level by @joe733 in [#261](https://github.com/python-validators/validators/pull/261) +- feat: docs can be built with both sphinx & mkdocs by @joe733 in [#262](https://github.com/python-validators/validators/pull/262) +- fix: improves build process by @joe733 in [#263](https://github.com/python-validators/validators/pull/263) +- fix: removes 64-char limit for url path & query by @joe733 in [#264](https://github.com/python-validators/validators/pull/264) + +**Full Changelog**: [0.21.0...0.21.1](https://github.com/python-validators/validators/compare/0.21.0...0.21.1) + ## 0.21.0 (2023-03-25) - feat: add build for pypi workflow by @joe733 in [#255](https://github.com/python-validators/validators/pull/255) diff --git a/poetry.lock b/poetry.lock index 87e5005b..340b323b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -12,25 +12,6 @@ files = [ {file = "alabaster-0.7.13.tar.gz", hash = "sha256:a27a4a084d5e690e16e01e03ad2b2e552c61a65469419b907243193de1a84ae2"}, ] -[[package]] -name = "attrs" -version = "22.2.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=3.6" -files = [ - {file = "attrs-22.2.0-py3-none-any.whl", hash = "sha256:29e95c7f6778868dbd49170f98f8818f78f3dc5e0e37c0b1f474e3561b240836"}, - {file = "attrs-22.2.0.tar.gz", hash = "sha256:c9227bfc2f01993c03f68db37d1d15c9690188323c067c641f1a35ca58185f99"}, -] - -[package.extras] -cov = ["attrs[tests]", "coverage-enable-subprocess", "coverage[toml] (>=5.3)"] -dev = ["attrs[docs,tests]"] -docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope.interface"] -tests = ["attrs[tests-no-zope]", "zope.interface"] -tests-no-zope = ["cloudpickle", "cloudpickle", "hypothesis", "hypothesis", "mypy (>=0.971,<0.990)", "mypy (>=0.971,<0.990)", "pympler", "pympler", "pytest (>=4.3.0)", "pytest (>=4.3.0)", "pytest-mypy-plugins", "pytest-mypy-plugins", "pytest-xdist[psutil]", "pytest-xdist[psutil]"] - [[package]] name = "babel" version = "2.12.1" @@ -321,18 +302,18 @@ test = ["pytest (>=6)"] [[package]] name = "filelock" -version = "3.10.7" +version = "3.11.0" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "filelock-3.10.7-py3-none-any.whl", hash = "sha256:bde48477b15fde2c7e5a0713cbe72721cb5a5ad32ee0b8f419907960b9d75536"}, - {file = "filelock-3.10.7.tar.gz", hash = "sha256:892be14aa8efc01673b5ed6589dbccb95f9a8596f0507e232626155495c18105"}, + {file = "filelock-3.11.0-py3-none-any.whl", hash = "sha256:f08a52314748335c6460fc8fe40cd5638b85001225db78c2aa01c8c0db83b318"}, + {file = "filelock-3.11.0.tar.gz", hash = "sha256:3618c0da67adcc0506b015fd11ef7faf1b493f0b40d87728e19986b536890c37"}, ] [package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.2.2)", "diff-cover (>=7.5)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-timeout (>=2.1)"] [[package]] @@ -418,14 +399,14 @@ gitdb = ">=4.0.1,<5" [[package]] name = "griffe" -version = "0.25.5" +version = "0.27.0" description = "Signatures for entire Python programs. Extract the structure, the frame, the skeleton of your project, to generate API documentation or find breaking changes in your API." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "griffe-0.25.5-py3-none-any.whl", hash = "sha256:1fb9edff48e66d4873014a2ebf21aca5f271d0006a4c937826e3cf592ffb3706"}, - {file = "griffe-0.25.5.tar.gz", hash = "sha256:11ea3403ef0560a1cbcf7f302eb5d21cf4c1d8ed3f8a16a75aa9f6f458caf3f1"}, + {file = "griffe-0.27.0-py3-none-any.whl", hash = "sha256:f3a5726e2d5876ac882d48ff9ca1a95c5bc267196a8f114263e66e234141bb84"}, + {file = "griffe-0.27.0.tar.gz", hash = "sha256:dcf3cc4205f33cbb16095324803a6904e0b293cd1630ceab4b66a9115af6b818"}, ] [package.dependencies] @@ -475,14 +456,14 @@ files = [ [[package]] name = "importlib-metadata" -version = "6.1.0" +version = "6.3.0" description = "Read metadata from Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "importlib_metadata-6.1.0-py3-none-any.whl", hash = "sha256:ff80f3b5394912eb1b108fcfd444dc78b7f1f3e16b16188054bd01cb9cb86f09"}, - {file = "importlib_metadata-6.1.0.tar.gz", hash = "sha256:43ce9281e097583d758c2c708c4376371261a02c34682491a8e98352365aad20"}, + {file = "importlib_metadata-6.3.0-py3-none-any.whl", hash = "sha256:8f8bd2af397cf33bd344d35cfe7f489219b7d14fc79a3f854b75b8417e9226b0"}, + {file = "importlib_metadata-6.3.0.tar.gz", hash = "sha256:23c2bcae4762dfb0bbe072d358faec24957901d75b6c4ab11172c0c982532402"}, ] [package.dependencies] @@ -747,14 +728,14 @@ mkdocs = ">=1.1" [[package]] name = "mkdocs-material" -version = "9.1.4" +version = "9.1.6" description = "Documentation that simply works" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocs_material-9.1.4-py3-none-any.whl", hash = "sha256:4c92dcf9365068259bef3eed8e0dd5410056b6f7187bdea2d52848c0f94cd94c"}, - {file = "mkdocs_material-9.1.4.tar.gz", hash = "sha256:c3a8943e9e4a7d2624291da365bbccf0b9f88688aa6947a46260d8c165cd4389"}, + {file = "mkdocs_material-9.1.6-py3-none-any.whl", hash = "sha256:f2eb1d40db89da9922944833c1387207408f8937e1c2b46ab86e0c8f170b71e0"}, + {file = "mkdocs_material-9.1.6.tar.gz", hash = "sha256:2e555152f9771646bfa62dc78a86052876183eff69ce30db03a33e85702b21fc"}, ] [package.dependencies] @@ -808,19 +789,19 @@ python-legacy = ["mkdocstrings-python-legacy (>=0.2.1)"] [[package]] name = "mkdocstrings-python" -version = "0.8.3" +version = "0.9.0" description = "A Python handler for mkdocstrings." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "mkdocstrings-python-0.8.3.tar.gz", hash = "sha256:9ae473f6dc599339b09eee17e4d2b05d6ac0ec29860f3fc9b7512d940fc61adf"}, - {file = "mkdocstrings_python-0.8.3-py3-none-any.whl", hash = "sha256:4e6e1cd6f37a785de0946ced6eb846eb2f5d891ac1cc2c7b832943d3529087a7"}, + {file = "mkdocstrings-python-0.9.0.tar.gz", hash = "sha256:da0a54d7d46523a25a5227f0ecc74b491291bd9d36fc71445bfb0ea64283e287"}, + {file = "mkdocstrings_python-0.9.0-py3-none-any.whl", hash = "sha256:00e02b5d3d444f9abdec2398f9ba0c73e15deab78685f793f5801fd4d62a5b6f"}, ] [package.dependencies] griffe = ">=0.24" -mkdocstrings = ">=0.19" +mkdocstrings = ">=0.20" [[package]] name = "mypy-extensions" @@ -946,14 +927,14 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" -version = "3.2.1" +version = "3.2.2" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = ">=3.8" files = [ - {file = "pre_commit-3.2.1-py2.py3-none-any.whl", hash = "sha256:a06a7fcce7f420047a71213c175714216498b49ebc81fe106f7716ca265f5bb6"}, - {file = "pre_commit-3.2.1.tar.gz", hash = "sha256:b5aee7d75dbba21ee161ba641b01e7ae10c5b91967ebf7b2ab0dfae12d07e1f1"}, + {file = "pre_commit-3.2.2-py2.py3-none-any.whl", hash = "sha256:0b4210aea813fe81144e87c5a291f09ea66f199f367fa1df41b55e1d26e1e2b4"}, + {file = "pre_commit-3.2.2.tar.gz", hash = "sha256:5b808fcbda4afbccf6d6633a56663fed35b6c2bc08096fd3d47ce197ac351d9d"}, ] [package.dependencies] @@ -1022,14 +1003,14 @@ files = [ [[package]] name = "pygments" -version = "2.14.0" +version = "2.15.0" description = "Pygments is a syntax highlighting package written in Python." category = "dev" optional = false -python-versions = ">=3.6" +python-versions = ">=3.7" files = [ - {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, - {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, + {file = "Pygments-2.15.0-py3-none-any.whl", hash = "sha256:77a3299119af881904cd5ecd1ac6a66214b6e9bed1f2db16993b54adede64094"}, + {file = "Pygments-2.15.0.tar.gz", hash = "sha256:f7e36cffc4c517fbc252861b9a6e4644ca0e5abadf9a113c72d1358ad09b9500"}, ] [package.extras] @@ -1037,14 +1018,14 @@ plugins = ["importlib-metadata"] [[package]] name = "pymdown-extensions" -version = "9.10" +version = "9.11" description = "Extension pack for Python Markdown." category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pymdown_extensions-9.10-py3-none-any.whl", hash = "sha256:31eaa76ce6f96aabfcea98787c2fff2c5c0611b20a53a94213970cfbf05f02b8"}, - {file = "pymdown_extensions-9.10.tar.gz", hash = "sha256:562c38eee4ce3f101ce631b804bfc2177a8a76c7e4dc908871fb6741a90257a7"}, + {file = "pymdown_extensions-9.11-py3-none-any.whl", hash = "sha256:a499191d8d869f30339de86fcf072a787e86c42b6f16f280f5c2cf174182b7f3"}, + {file = "pymdown_extensions-9.11.tar.gz", hash = "sha256:f7e86c1d3981f23d9dc43294488ecb54abadd05b0be4bf8f0e15efc90f7853ff"}, ] [package.dependencies] @@ -1088,14 +1069,14 @@ testing = ["covdefaults (>=2.2.2)", "importlib-metadata (>=6)", "pytest (>=7.2.1 [[package]] name = "pyright" -version = "1.1.301" +version = "1.1.302" description = "Command line wrapper for pyright" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pyright-1.1.301-py3-none-any.whl", hash = "sha256:ecc3752ba8c866a8041c90becf6be79bd52f4c51f98472e4776cae6d55e12826"}, - {file = "pyright-1.1.301.tar.gz", hash = "sha256:6ac4afc0004dca3a977a4a04a8ba25b5b5aa55f8289550697bfc20e11be0d5f2"}, + {file = "pyright-1.1.302-py3-none-any.whl", hash = "sha256:1929e3126b664b5281dba66a789e8e04358afca48c10994ee0243b8c2a14acdf"}, + {file = "pyright-1.1.302.tar.gz", hash = "sha256:e74a7dfbbb1d754941d015cccea8a6d29b395d8e4cb0e45dcfcaf3b6c6cfd540"}, ] [package.dependencies] @@ -1107,18 +1088,17 @@ dev = ["twine (>=3.4.1)"] [[package]] name = "pytest" -version = "7.2.2" +version = "7.3.0" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "pytest-7.2.2-py3-none-any.whl", hash = "sha256:130328f552dcfac0b1cec75c12e3f005619dc5f874f0a06e8ff7263f0ee6225e"}, - {file = "pytest-7.2.2.tar.gz", hash = "sha256:c99ab0c73aceb050f68929bc93af19ab6db0558791c6a0715723abe9d0ade9d4"}, + {file = "pytest-7.3.0-py3-none-any.whl", hash = "sha256:933051fa1bfbd38a21e73c3960cebdad4cf59483ddba7696c48509727e17f201"}, + {file = "pytest-7.3.0.tar.gz", hash = "sha256:58ecc27ebf0ea643ebfdf7fb1249335da761a00c9f955bcd922349bcb68ee57d"}, ] [package.dependencies] -attrs = ">=19.2.0" colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" @@ -1127,7 +1107,7 @@ pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] [[package]] name = "python-dateutil" @@ -1546,30 +1526,30 @@ files = [ [[package]] name = "tox" -version = "4.4.8" +version = "4.4.11" description = "tox is a generic virtualenv management and test command line tool" category = "dev" optional = false python-versions = ">=3.7" files = [ - {file = "tox-4.4.8-py3-none-any.whl", hash = "sha256:12fe562b8992ea63b1e92556b7e28600cd1b70c9e01ce08984f60ce2d32c243c"}, - {file = "tox-4.4.8.tar.gz", hash = "sha256:524640254de8b0f03facbdc6b7c18a35700592e3ada0ede42f509b3504b745ff"}, + {file = "tox-4.4.11-py3-none-any.whl", hash = "sha256:6fa4dbd933d0e335b5392c81e9cd467630119b3669705dbad47814a93b6c9586"}, + {file = "tox-4.4.11.tar.gz", hash = "sha256:cd88e41aef9c71f0ba02b6d7939f102760b192b63458fbe04dbbaed82f7bf5f5"}, ] [package.dependencies] cachetools = ">=5.3" chardet = ">=5.1" colorama = ">=0.4.6" -filelock = ">=3.10" +filelock = ">=3.10.7" packaging = ">=23" -platformdirs = ">=3.1.1" +platformdirs = ">=3.2" pluggy = ">=1" pyproject-api = ">=1.5.1" tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} virtualenv = ">=20.21" [package.extras] -docs = ["furo (>=2022.12.7)", "sphinx (>=6.1.3)", "sphinx-argparse-cli (>=1.11)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)", "sphinx-copybutton (>=0.5.1)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] +docs = ["furo (>=2023.3.27)", "sphinx (>=6.1.3)", "sphinx-argparse-cli (>=1.11)", "sphinx-autodoc-typehints (>=1.22,!=1.23.4)", "sphinx-copybutton (>=0.5.1)", "sphinx-inline-tabs (>=2022.1.2b11)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=22.12)"] testing = ["build[virtualenv] (>=0.10)", "covdefaults (>=2.3)", "devpi-process (>=0.3)", "diff-cover (>=7.5)", "distlib (>=0.3.6)", "flaky (>=3.7)", "hatch-vcs (>=0.3)", "hatchling (>=1.13)", "psutil (>=5.9.4)", "pytest (>=7.2.2)", "pytest-cov (>=4)", "pytest-mock (>=3.10)", "pytest-xdist (>=3.2.1)", "re-assert (>=1.1)", "time-machine (>=2.9)", "wheel (>=0.40)"] [[package]] @@ -1681,4 +1661,4 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more [metadata] lock-version = "2.0" python-versions = "^3.8" -content-hash = "ff38208b4d963c3fe9212d221fa7e0369bd4c8651c988341ed4a1feaf35bad03" +content-hash = "556ac6389a41805fc1b4b2ca9ddd63fd8214587d17f0ef8509d06ca62a5571f5" diff --git a/pyproject.toml b/pyproject.toml index cc43f4fc..e287fd0d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "validators" -version = "0.21.0" +version = "0.21.1" description = "Python Data Validation for Humans™" authors = ["Konsta Vesterinen "] license = "MIT" @@ -28,16 +28,16 @@ include = ["CHANGES.md", "docs/*", "docs/validators.1", "validators/py.typed"] python = "^3.8" [tool.poetry.group.dev.dependencies] -tox = "^4.4.8" +tox = "^4.4.11" [tool.poetry.group.docs.dependencies] mkdocs = "^1.4.2" -mkdocs-material = "^9.1.4" +mkdocs-material = "^9.1.6" mkdocstrings = { extras = ["python"], version = "^0.20.0" } pyaml = "^21.10.1" [tool.poetry.group.hooks.dependencies] -pre-commit = "^3.2.1" +pre-commit = "^3.2.2" [tool.poetry.group.sast.dependencies] bandit = "^1.7.5" @@ -48,14 +48,14 @@ myst-parser = "^1.0.0" pypandoc-binary = "^1.11" [tool.poetry.group.tests.dependencies] -pytest = "^7.2.2" +pytest = "^7.3.0" [tool.poetry.group.type-lint-format.dependencies] -black = "^23.1.1" +black = "^23.3.0" flake8 = "^5.0.4" flake8-docstrings = "^1.7.0" isort = "^5.12.0" -pyright = "^1.1.301" +pyright = "^1.1.302" [build-system] requires = ["poetry-core"] diff --git a/tests/test_url.py b/tests/test_url.py index a5d377fa..63c769e8 100644 --- a/tests/test_url.py +++ b/tests/test_url.py @@ -83,6 +83,8 @@ "http://президент.рф/", "http://10.24.90.255:83/", "https://travel-usa.com/wisconsin/旅行/", + "http://:::::::::::::@exmp.com", + "http://-.~_!$&'()*+,;=:%40:80%2f::::::@example.com", # when simple_host=True # "http://localhost", # "http://localhost:8000", diff --git a/validators/__init__.py b/validators/__init__.py index c4ccee2e..c78c27ab 100644 --- a/validators/__init__.py +++ b/validators/__init__.py @@ -64,4 +64,4 @@ "fi_ssn", ) -__version__ = "0.20.0" +__version__ = "0.21.1" diff --git a/validators/url.py b/validators/url.py index 116ddf4a..ade70f72 100644 --- a/validators/url.py +++ b/validators/url.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- # standard -from urllib.parse import urlsplit +from urllib.parse import urlsplit, unquote from functools import lru_cache import re @@ -24,7 +24,15 @@ def _username_regex(): @lru_cache def _path_regex(): - return re.compile(r"^[\/a-zA-Z0-9\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=\:\@\%]+$", re.IGNORECASE) + return re.compile( + # allowed symbols + r"^[\/a-zA-Z0-9\-\.\_\~\!\$\&\'\(\)\*\+\,\;\=\:\@\%" + # emoticons / emoji + + r"\U0001F600-\U0001F64F" + # multilingual unicode ranges + + r"\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+$", + re.IGNORECASE, + ) @lru_cache @@ -52,7 +60,9 @@ def _validate_auth_segment(value: str): if not value: return True if (colon_count := value.count(":")) > 1: - return False + # everything before @ is then considered as a username + # this is a bad practice, but syntactically valid URL + return _username_regex().match(unquote(value)) if colon_count < 1: return _username_regex().match(value) username, password = value.rsplit(":", 1) @@ -103,9 +113,9 @@ def _validate_optionals(path: str, query: str, fragment: str): """Validate path query and fragments.""" optional_segments = True if path: - optional_segments &= bool(_path_regex().match(path.encode("idna").decode("utf-8"))) + optional_segments &= bool(_path_regex().match(path)) if query: - optional_segments &= bool(_query_regex().match(query.encode("idna").decode("utf-8"))) + optional_segments &= bool(_query_regex().match(query)) if fragment: optional_segments &= all(char_to_avoid not in fragment for char_to_avoid in ("/", "?")) return optional_segments