From 2f1653e2d0c814da8ed633bb8b9eb1475eca60c2 Mon Sep 17 00:00:00 2001 From: Adam Lev-Libfeld Date: Tue, 19 Jul 2022 23:00:28 +0300 Subject: [PATCH 1/3] git validation: create validator and test --- tests/test_git.py | 163 ++++++++++++++++++++++++++++++++++++++++++++++ validators/git.py | 132 +++++++++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+) create mode 100644 tests/test_git.py create mode 100644 validators/git.py diff --git a/tests/test_git.py b/tests/test_git.py new file mode 100644 index 00000000..3d51e3f7 --- /dev/null +++ b/tests/test_git.py @@ -0,0 +1,163 @@ +# -*- coding: utf-8 -*- +import pytest + +from validators import url, ValidationFailure + + +@pytest.mark.parametrize('address', [ + u'git@bitbucket.org:username/reponame.git', + u'https://username@bitbucket.org/otherusername/reponame.git', + u'https://github.com/username/reponame.git', + u'git@github.com:username/reponame.git', + u'git@gitlab.com:groupname/reponame.git', + u'https://gitlab.com/groupname/reponame.git', + u'git@bitbucket.org:workspace/reponame.git', + u'https://username@bitbucket.org/workspace/reponame.git', + u'https://bitbucket.org/workspace/reponame.git', + u'git@gitlab.com:username/reponame.git', + u'https://gitlab.com/username/reponame.git', + u'git://github.com/somthing/somthing.git#ff786f9f', + u'git://github.com/somthing/somthing.git#gh-pages', + u'git://github.com/somthing/somthing.git#master', + u'git://github.com/somthing/somthing.git#Quick-Fix', + u'git://github.com/somthing/somthing.git#quick_fix', + u'git://github.com/somthing/somthing.git#v0.1.0', + u'git://host.xz/path/to/repo.git/', + u'git://host.xz/~user/path/to/repo.git/', + u'git@192.168.101.127:user/project.git', + u'git@github.com:user/project.git', + u'git@github.com:user/some-project.git', + u'git@github.com:user/some-project.git', + u'git@github.com:user/some_project.git', + u'git@github.com:user/some_project.git', + u'http://github.com/user/project.git', + u'http://host.xz/path/to/repo.git/', + u'https://github.com/user/project.git', + u'https://host.xz/path/to/repo.git/', + u'https://username::;*%$:@github.com/username/repository.git', + u'https://username:$fooABC@:@github.com/username/repository.git', + u'https://username:password@github.com/username/repository.git', + u'ssh://host.xz/path/to/repo.git/', + u'ssh://host.xz/path/to/repo.git/', + u'ssh://host.xz/~/path/to/repo.git', + u'ssh://host.xz/~user/path/to/repo.git/', + u'ssh://user@host.xz/path/to/repo.git/', + u'ssh://user@host.xz/path/to/repo.git/', + u'ssh://user@host.xz/~/path/to/repo.git', + u'ssh://user@host.xz/~user/path/to/repo.git/', + u'ssh://user@host.xz:port/path/to/repo.git/', + u'https://username@bitbucket.org:8080/otherusername/reponame.git', + u'ssh://host.xz:port/path/to/repo.git/', + u'ssh://host.xz:1234/path/to/repo.git/', + u'https://192.168.1.127/user/project.git', + u'http://192.168.1.127/user/project.git', + u'http://192.168.1.127:port/user/project.git', +]) +def test_returns_true_on_valid_url(address): + assert url(address) + + +@pytest.mark.parametrize('address', [ + u'git@bitbucket.org:username/reponame', + u'https://username@bitbucket.org/otherusername/reponame', + u'https://github.com/username/reponame', + u'git@github.com:username/reponame', + u'git@gitlab.com:groupname/reponame', + u'https://gitlab.com/groupname/reponame', + u'git@bitbucket.org:workspace/reponame', + u'https://username@bitbucket.org/workspace/reponame', + u'https://bitbucket.org/workspace/reponame', + u'git@gitlab.com:username/reponame', + u'https://gitlab.com/username/reponame', + u'git://host.xz/path/to/repo', + u'git://host.xz/~user/path/to/repo.', + u'git@192.168.101.127:user/project', + u'git@github.com:user/project', + u'git@github.com:user/some-project', + u'git@github.com:user/some-project', + u'git@github.com:user/some_project', + u'git@github.com:user/some_project', + u'http://github.com/user/project', + u'http://host.xz/path/to/repo.', + u'https://github.com/user/project', + u'https://host.xz/path/to/repo', + u'https://username::;*%$:@github.com/username/repository', + u'https://username:$fooABC@:@github.com/username/repository', + u'https://username:password@github.com/username/repository', + u'ssh://host.xz/path/to/repo', + u'ssh://host.xz/path/to/repo', + u'ssh://host.xz/~/path/to/rep', + u'ssh://host.xz/~user/path/to/repo', + u'ssh://user@host.xz/path/to/repo', + u'ssh://user@host.xz/path/to/repo', + u'ssh://user@host.xz/~/path/to/repo', + u'ssh://user@host.xz/~user/path/to/repo', + u'ssh://user@host.xz:port/path/to/repo', + u'https://username@bitbucket.org:8080/otherusername/reponame', + u'ssh://host.xz:port/path/to/repo', + u'ssh://host.xz:1234/path/to/repo', + u'https://192.168.1.127/user/project', + u'http://192.168.1.127/user/project', + u'http://192.168.1.127:port/user/project', +]) +def test_returns_true_on_valid_loose_url(address): + assert url(address, strict=False) + + +@pytest.mark.parametrize('address', 'strict', [ + u'https://www.github.com', + u'git@bitbucket.org:username/repo name', + u'git@bitbucket.org:username/repo name.git', + u'https://gitlab.com/username/reponame.git -b branch', + u'https://gitlab.com/username/reponame.git --flag', + u'https://gitlab.com/username/reponame.git \\-f', + u'/path/to/repo.git/', + u'file:///path/to/repo.git/', + u'file://~/path/to/repo.git/', + u'git@github.com:user/some_project.git/foo', + u'git@github.com:user/some_project.gitfoo', + u'host.xz:/path/to/repo.git/', + u'host.xz:path/to/repo.git', + u'host.xz:path/to/repo', + u'host.xz:~user/path/to/repo.git/', + u'path/to/repo.git/', + u'rsync://host.xz/path/to/repo.git/', + u'user@host.xz:/path/to/repo.git/', + u'user@host.xz:path/to/repo.git', + u'user@host.xz:~user/path/to/repo.git/', + u'~/path/to/repo.git', + u'git@bitbucket.org:username/reponame.gi', +]) +def test_returns_failed_loose_validation_on_invalid_url(address): + assert isinstance(url(address), ValidationFailure) + + +@pytest.mark.parametrize('address', [ + u'https://www.github.com', + u'git@bitbucket.org:username/"repo name".git', + u'https://gitlab.com/username/"reponame".git', + u'https://gitlab.com/username/\'reponame\'.git', + u'git@bitbucket.org:username/repo name', + u'git@bitbucket.org:username/repo name.git', + u'https://gitlab.com/username/reponame.git -b branch', + u'https://gitlab.com/username/reponame.git --flag', + u'https://gitlab.com/username/reponame.git \\-f', + u'/path/to/repo.git/', + u'file:///path/to/repo.git/', + u'file://~/path/to/repo.git/', + u'git@github.com:user/some_project.git/foo', + u'git@github.com:user/some_project.gitfoo', + u'host.xz:/path/to/repo.git/', + u'host.xz:path/to/repo.git', + u'host.xz:path/to/repo', + u'host.xz:~user/path/to/repo.git/', + u'path/to/repo.git/', + u'rsync://host.xz/path/to/repo.git/', + u'user@host.xz:/path/to/repo.git/', + u'user@host.xz:path/to/repo.git', + u'user@host.xz:~user/path/to/repo.git/', + u'~/path/to/repo.git', + u'git@bitbucket.org:username/reponame.gi', +]) +def test_returns_failed_strict_validation_on_invalid_url(address): + assert isinstance(url(address, strict=True), ValidationFailure) diff --git a/validators/git.py b/validators/git.py new file mode 100644 index 00000000..a971132c --- /dev/null +++ b/validators/git.py @@ -0,0 +1,132 @@ +import re + +from .utils import validator + + +url_regex = re.compile( + # protocol + # either prot:// (protocol captured as group 1) or git@serv (server captured as group 2) + r"(?:(git|ssh|https?)|git@([A-Za-z0-9][A-Za-z0-9\+\.\-_]+)):(?:\/\/)?" + # username and maybe password, if either provided (both are captured as group 3) + r"(?:(\S*)@)?" + # the actual targeted URL as per STD66 (RFC3986) [A-Za-z][A-Za-z0-9+.-]* + # with some added leniency for IPs and allowed URI special chars (_~/:) in order to capture full path+ports + # (entire URI captured as group 4) + r"([A-Za-z0-9][A-Za-z0-9\+\.\-_\/\~\:]*?)" + # .git matched if provided + r"(?:\.git)" + # ending '/' OR branch name/commit id (captured as group 5) + r"(\/?|\#[-\d\w._]+?)", + re.UNICODE | re.IGNORECASE +) + + +@validator +def git(value: str, strict: bool = False): + """ + Return whether a given value is a valid git URL. + + If the value is valid git URL this function returns ``True``, otherwise + :class:`~validators.utils.ValidationFailure`. + + Examples:: + + >>> git('git@bitbucket.org:username/reponame.git') + True + + >>> git('http://192.168.1.127/user/project') + True + + >>> git('git://github.com/somthing/somthing.git#ff786f9f') + True + + >>> git('https://www.github.com') + ValidationFailure(func=git, ...) + + >>> git('http://192.168.1.127/user/project', strict=True) + ValidationFailure(func=git, ...) + + :param value: URL address string to validate + :param strict: (default=False) enfocre URL end with .git + """ + result = url_regex.match(value) + + if result or strict: + return result + else: # not strict and failed to match + # try adding ".git" at end of value and retry + value = value[:-1] if value.endswith('/') else value + ".git" + result = url_regex.match(value) + return result + +''' +git@bitbucket.org:username/reponame.git +https://username@bitbucket.org/otherusername/reponame.git +https://github.com/username/reponame.git +git@github.com:username/reponame.git +git@gitlab.com:groupname/reponame.git +https://gitlab.com/groupname/reponame.git +git@bitbucket.org:workspace/reponame.git +https://username@bitbucket.org/workspace/reponame.git +https://bitbucket.org/workspace/reponame.git +git@gitlab.com:username/reponame.git +https://gitlab.com/username/reponame.git +git://github.com/somthing/somthing.git#ff786f9f +git://github.com/somthing/somthing.git#gh-pages +git://github.com/somthing/somthing.git#master +git://github.com/somthing/somthing.git#Quick-Fix +git://github.com/somthing/somthing.git#quick_fix +git://github.com/somthing/somthing.git#v0.1.0 +git://host.xz/path/to/repo.git/ +git://host.xz/~user/path/to/repo.git/ +git@192.168.101.127:user/project.git +git@github.com:user/project.git +git@github.com:user/some-project.git +git@github.com:user/some-project.git +git@github.com:user/some_project.git +git@github.com:user/some_project.git +http://github.com/user/project.git +http://host.xz/path/to/repo.git/ +https://github.com/user/project.git +https://host.xz/path/to/repo.git/ +https://username::;*%$:@github.com/username/repository.git +https://username:$fooABC@:@github.com/username/repository.git +https://username:password@github.com/username/repository.git +ssh://host.xz/path/to/repo.git/ +ssh://host.xz/path/to/repo.git/ +ssh://host.xz/~/path/to/repo.git +ssh://host.xz/~user/path/to/repo.git/ +ssh://user@host.xz/path/to/repo.git/ +ssh://user@host.xz/path/to/repo.git/ +ssh://user@host.xz/~/path/to/repo.git +ssh://user@host.xz/~user/path/to/repo.git/ +ssh://user@host.xz:port/path/to/repo.git/ +https://username@bitbucket.org:8080/otherusername/reponame.git +ssh://host.xz:port/path/to/repo.git/ +ssh://host.xz:1234/path/to/repo.git/ +https://192.168.1.127/user/project.git +http://192.168.1.127/user/project.git + + +https://www.github.com +git@bitbucket.org:username/repo name.git +https://gitlab.com/username/reponame.git -b branch +https://gitlab.com/username/reponame.git --flag +https://gitlab.com/username/reponame.git \-f +/path/to/repo.git/ +file:///path/to/repo.git/ +file://~/path/to/repo.git/ +git@github.com:user/some_project.git/foo +git@github.com:user/some_project.gitfoo +host.xz:/path/to/repo.git/ +host.xz:path/to/repo.git +host.xz:~user/path/to/repo.git/ +path/to/repo.git/ +rsync://host.xz/path/to/repo.git/ +user@host.xz:/path/to/repo.git/ +user@host.xz:path/to/repo.git +user@host.xz:~user/path/to/repo.git/ +~/path/to/repo.git +git@bitbucket.org:username/reponame.gi + +''' \ No newline at end of file From e323a1070681db2c9dc5cefa0b72063142f4de4d Mon Sep 17 00:00:00 2001 From: Adam Lev-Libfeld Date: Tue, 19 Jul 2022 23:11:42 +0300 Subject: [PATCH 2/3] cleanup and prepare for PR --- validators/git.py | 94 ++++++++--------------------------------------- 1 file changed, 16 insertions(+), 78 deletions(-) diff --git a/validators/git.py b/validators/git.py index a971132c..ec602297 100644 --- a/validators/git.py +++ b/validators/git.py @@ -2,7 +2,13 @@ from .utils import validator - +""" +sources: +https://github.com/git/git/blob/master/url.c +https://git-scm.com/docs/git-clone +https://stackoverflow.com/questions/23976019/how-to-verify-valid-format-of-url-as-a-git-repo/60000569#comment111902087_60000569 +https://github.com/jonschlinkert/is-git-url +""" url_regex = re.compile( # protocol # either prot:// (protocol captured as group 1) or git@serv (server captured as group 2) @@ -31,23 +37,27 @@ def git(value: str, strict: bool = False): Examples:: - >>> git('git@bitbucket.org:username/reponame.git') + >>> git('git@github.org:username/reponame.git') + True + + >>> git('git@github.com:username/reponame') True - >>> git('http://192.168.1.127/user/project') + >>> git('http://192.168.1.127/user/project.git') True - >>> git('git://github.com/somthing/somthing.git#ff786f9f') + >>> git('git://bitbucket.org/org/repo.git#ff786f9f') True >>> git('https://www.github.com') ValidationFailure(func=git, ...) - >>> git('http://192.168.1.127/user/project', strict=True) + >>> git('git@github.com:username/reponame', strict=True) ValidationFailure(func=git, ...) :param value: URL address string to validate - :param strict: (default=False) enfocre URL end with .git + :param strict: (default=False) enfocre URL end with .git (if false, upon validation failure '.git; is added + to the end if the procided value and validation retried) """ result = url_regex.match(value) @@ -58,75 +68,3 @@ def git(value: str, strict: bool = False): value = value[:-1] if value.endswith('/') else value + ".git" result = url_regex.match(value) return result - -''' -git@bitbucket.org:username/reponame.git -https://username@bitbucket.org/otherusername/reponame.git -https://github.com/username/reponame.git -git@github.com:username/reponame.git -git@gitlab.com:groupname/reponame.git -https://gitlab.com/groupname/reponame.git -git@bitbucket.org:workspace/reponame.git -https://username@bitbucket.org/workspace/reponame.git -https://bitbucket.org/workspace/reponame.git -git@gitlab.com:username/reponame.git -https://gitlab.com/username/reponame.git -git://github.com/somthing/somthing.git#ff786f9f -git://github.com/somthing/somthing.git#gh-pages -git://github.com/somthing/somthing.git#master -git://github.com/somthing/somthing.git#Quick-Fix -git://github.com/somthing/somthing.git#quick_fix -git://github.com/somthing/somthing.git#v0.1.0 -git://host.xz/path/to/repo.git/ -git://host.xz/~user/path/to/repo.git/ -git@192.168.101.127:user/project.git -git@github.com:user/project.git -git@github.com:user/some-project.git -git@github.com:user/some-project.git -git@github.com:user/some_project.git -git@github.com:user/some_project.git -http://github.com/user/project.git -http://host.xz/path/to/repo.git/ -https://github.com/user/project.git -https://host.xz/path/to/repo.git/ -https://username::;*%$:@github.com/username/repository.git -https://username:$fooABC@:@github.com/username/repository.git -https://username:password@github.com/username/repository.git -ssh://host.xz/path/to/repo.git/ -ssh://host.xz/path/to/repo.git/ -ssh://host.xz/~/path/to/repo.git -ssh://host.xz/~user/path/to/repo.git/ -ssh://user@host.xz/path/to/repo.git/ -ssh://user@host.xz/path/to/repo.git/ -ssh://user@host.xz/~/path/to/repo.git -ssh://user@host.xz/~user/path/to/repo.git/ -ssh://user@host.xz:port/path/to/repo.git/ -https://username@bitbucket.org:8080/otherusername/reponame.git -ssh://host.xz:port/path/to/repo.git/ -ssh://host.xz:1234/path/to/repo.git/ -https://192.168.1.127/user/project.git -http://192.168.1.127/user/project.git - - -https://www.github.com -git@bitbucket.org:username/repo name.git -https://gitlab.com/username/reponame.git -b branch -https://gitlab.com/username/reponame.git --flag -https://gitlab.com/username/reponame.git \-f -/path/to/repo.git/ -file:///path/to/repo.git/ -file://~/path/to/repo.git/ -git@github.com:user/some_project.git/foo -git@github.com:user/some_project.gitfoo -host.xz:/path/to/repo.git/ -host.xz:path/to/repo.git -host.xz:~user/path/to/repo.git/ -path/to/repo.git/ -rsync://host.xz/path/to/repo.git/ -user@host.xz:/path/to/repo.git/ -user@host.xz:path/to/repo.git -user@host.xz:~user/path/to/repo.git/ -~/path/to/repo.git -git@bitbucket.org:username/reponame.gi - -''' \ No newline at end of file From 19dd26632946af9ec1d7d6b8165b287c6797ae13 Mon Sep 17 00:00:00 2001 From: Adam Lev-Libfeld Date: Tue, 19 Jul 2022 23:19:24 +0300 Subject: [PATCH 3/3] fix minor python comprehansion bug --- validators/git.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/validators/git.py b/validators/git.py index ec602297..70a42468 100644 --- a/validators/git.py +++ b/validators/git.py @@ -65,6 +65,6 @@ def git(value: str, strict: bool = False): return result else: # not strict and failed to match # try adding ".git" at end of value and retry - value = value[:-1] if value.endswith('/') else value + ".git" - result = url_regex.match(value) + value = value[:-1] if value.endswith('/') else value + result = url_regex.match(value + ".git") return result