From 007a95c2e3a95720416c5f53e2e4fe1d1ae12eab Mon Sep 17 00:00:00 2001 From: Sven Kieske Date: Tue, 12 Dec 2023 17:40:47 +0100 Subject: [PATCH 1/2] fix: tag regex should allow ports Closes: https://github.com/docker/docker-py/issues/3195 Related: https://github.com/opencontainers/distribution-spec/pull/498 Signed-off-by: Sven Kieske --- docker/utils/build.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docker/utils/build.py b/docker/utils/build.py index a5c4b0c2d..81a192258 100644 --- a/docker/utils/build.py +++ b/docker/utils/build.py @@ -10,8 +10,9 @@ _SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/') _TAG = re.compile( - r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*(\/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*" \ - + "(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$" + r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*" \ + + "(?::[0-9]+)?(/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*" \ + + "(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$" ) From c53857f04dc372c52b373712d750ae15ccf0130a Mon Sep 17 00:00:00 2001 From: Milas Bowman Date: Wed, 3 Jan 2024 10:39:29 -0500 Subject: [PATCH 2/2] fix(build): fix lint warning + add test case for tag format There are some xfails here for cases that the regex is not currently handling. It's too strict for IPv6 domains at the moment. Signed-off-by: Milas Bowman --- docker/utils/build.py | 6 ++-- tests/unit/utils_build_test.py | 53 ++++++++++++++++++++++++++++++++-- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/docker/utils/build.py b/docker/utils/build.py index 81a192258..86a4423f0 100644 --- a/docker/utils/build.py +++ b/docker/utils/build.py @@ -10,9 +10,9 @@ _SEP = re.compile('/|\\\\') if IS_WINDOWS_PLATFORM else re.compile('/') _TAG = re.compile( - r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*" \ - + "(?::[0-9]+)?(/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*" \ - + "(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$" + r"^[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*" + r"(?::[0-9]+)?(/[a-z0-9]+((\.|_|__|-+)[a-z0-9]+)*)*" + r"(:[a-zA-Z0-9_][a-zA-Z0-9._-]{0,127})?$" ) diff --git a/tests/unit/utils_build_test.py b/tests/unit/utils_build_test.py index fa7d833de..5f1bb1ec0 100644 --- a/tests/unit/utils_build_test.py +++ b/tests/unit/utils_build_test.py @@ -6,11 +6,10 @@ import tempfile import unittest +import pytest from docker.constants import IS_WINDOWS_PLATFORM -from docker.utils import exclude_paths, tar - -import pytest +from docker.utils import exclude_paths, tar, match_tag from ..helpers import make_tree @@ -489,3 +488,51 @@ def test_tar_directory_link(self): assert member in names assert 'a/c/b' in names assert 'a/c/b/utils.py' not in names + + +# selected test cases from https://github.com/distribution/reference/blob/8507c7fcf0da9f570540c958ea7b972c30eeaeca/reference_test.go#L13-L328 +@pytest.mark.parametrize("tag,expected", [ + ("test_com", True), + ("test.com:tag", True), + # N.B. this implicitly means "docker.io/library/test.com:5000" + # i.e. the `5000` is a tag, not a port here! + ("test.com:5000", True), + ("test.com/repo:tag", True), + ("test:5000/repo", True), + ("test:5000/repo:tag", True), + ("test:5000/repo", True), + ("", False), + (":justtag", False), + ("Uppercase:tag", False), + ("test:5000/Uppercase/lowercase:tag", False), + ("lowercase:Uppercase", True), + # length limits not enforced + pytest.param("a/"*128 + "a:tag", False, marks=pytest.mark.xfail), + ("a/"*127 + "a:tag-puts-this-over-max", True), + ("aa/asdf$$^/aa", False), + ("sub-dom1.foo.com/bar/baz/quux", True), + ("sub-dom1.foo.com/bar/baz/quux:some-long-tag", True), + ("b.gcr.io/test.example.com/my-app:test.example.com", True), + ("xn--n3h.com/myimage:xn--n3h.com", True), + ("foo_bar.com:8080", True), + ("foo/foo_bar.com:8080", True), + ("192.168.1.1", True), + ("192.168.1.1:tag", True), + ("192.168.1.1:5000", True), + ("192.168.1.1/repo", True), + ("192.168.1.1:5000/repo", True), + ("192.168.1.1:5000/repo:5050", True), + # regex does not properly handle ipv6 + pytest.param("[2001:db8::1]", False, marks=pytest.mark.xfail), + ("[2001:db8::1]:5000", False), + pytest.param("[2001:db8::1]/repo", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8:1:2:3:4:5:6]/repo:tag", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8::1]:5000/repo", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8::1]:5000/repo:tag", True, marks=pytest.mark.xfail), + pytest.param("[2001:db8::]:5000/repo", True, marks=pytest.mark.xfail), + pytest.param("[::1]:5000/repo", True, marks=pytest.mark.xfail), + ("[fe80::1%eth0]:5000/repo", False), + ("[fe80::1%@invalidzone]:5000/repo", False), +]) +def test_match_tag(tag: str, expected: bool): + assert match_tag(tag) == expected