Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve scheme check in repo url #602

Merged
merged 12 commits into from May 11, 2020
3 changes: 3 additions & 0 deletions mypy.ini
Expand Up @@ -38,6 +38,9 @@ ignore_missing_imports = True
; https://github.com/pypa/readme_renderer/issues/166
ignore_missing_imports = True

[mypy-rfc3986]
ignore_missing_imports = True

[mypy-setuptools]
; https://github.com/python/typeshed/issues/2171
ignore_missing_imports = True
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Expand Up @@ -42,6 +42,7 @@ install_requires=
tqdm >= 4.14
importlib_metadata; python_version < "3.8"
keyring >= 15.1
rfc3986 >= 1.4.0
setup_requires =
setuptools_scm >= 1.15

Expand Down
39 changes: 34 additions & 5 deletions tests/test_utils.py
Expand Up @@ -184,15 +184,44 @@ def test_get_repository_config_missing(tmpdir):
assert utils.get_repository_from_config(pypirc, "pypi") == exp


def test_get_repository_config_invalid_url(tmpdir):
def test_get_repository_config_invalid_scheme(tmpdir):
"""
Test if we get an URL without a protocol
Test if we get an URL with a invalid scheme
"""

pypirc = os.path.join(str(tmpdir), ".pypirc")

with pytest.raises(
exceptions.UnreachableRepositoryURLDetected,
match=r"Invalid repository URL: "
r"scheme was required to be one of \['http', 'https'\] but was 'ftp'.",
):
utils.get_repository_from_config(pypirc, "foo.bar", "ftp://test.pypi.org")


def test_get_repository_config_missing_components(tmpdir):
"""
Test if we get an URL with missing components
"""
pypirc = os.path.join(str(tmpdir), ".pypirc")

repository_url = "foo.bar"
with pytest.raises(exceptions.UnreachableRepositoryURLDetected):
utils.get_repository_from_config(pypirc, "foo.bar", repository_url)
with pytest.raises(
exceptions.UnreachableRepositoryURLDetected,
match="Invalid repository URL: host was required but missing.",
):
utils.get_repository_from_config(pypirc, "foo.bar", "https:/")

with pytest.raises(
exceptions.UnreachableRepositoryURLDetected,
match="Invalid repository URL: scheme was required but missing.",
):
utils.get_repository_from_config(pypirc, "foo.bar", "//test.pypi.org")

with pytest.raises(
exceptions.UnreachableRepositoryURLDetected,
match="Invalid repository URL: host, scheme were required but missing.",
):
utils.get_repository_from_config(pypirc, "foo.bar", "foo.bar")


def test_get_repository_config_missing_config(tmpdir):
Expand Down
28 changes: 22 additions & 6 deletions twine/utils.py
Expand Up @@ -28,6 +28,7 @@
from urllib.parse import urlunparse

import requests
import rfc3986

from twine import exceptions

Expand Down Expand Up @@ -99,23 +100,38 @@ def get_config(path: str = "~/.pypirc") -> Dict[str, RepositoryConfig]:
return dict(config)


def _validate_repository_url(repository_url: str) -> None:
"""Validate the given url for allowed schemes and components"""

# Allowed schemes are http and https, based on whether the repository
# supports TLS or not, and scheme and host must be present in the URL
validator = (
rfc3986.validators.Validator()
.allow_schemes("http", "https")
.require_presence_of("scheme", "host")
)
try:
validator.validate(rfc3986.uri_reference(repository_url))
except rfc3986.exceptions.RFC3986Exception as exc:
raise exceptions.UnreachableRepositoryURLDetected(
f"Invalid repository URL: {exc.args[0]}."
)


def get_repository_from_config(
config_file: str, repository: str, repository_url: Optional[str] = None
) -> RepositoryConfig:
# Get our config from, if provided, command-line values for the
# repository name and URL, or the .pypirc file
if repository_url and "://" in repository_url:

if repository_url:
_validate_repository_url(repository_url)
# prefer CLI `repository_url` over `repository` or .pypirc
return {
"repository": repository_url,
"username": None,
"password": None,
}
if repository_url and "://" not in repository_url:
raise exceptions.UnreachableRepositoryURLDetected(
"Repository URL {} has no protocol. Please add "
"'https://'. \n".format(repository_url)
)
try:
return get_config(config_file)[repository]
except KeyError:
Expand Down