...

Source file src/github.com/distribution/reference/regexp_test.go

Documentation: github.com/distribution/reference

     1  package reference
     2  
     3  import (
     4  	"regexp"
     5  	"strings"
     6  	"testing"
     7  )
     8  
     9  type regexpMatch struct {
    10  	input string
    11  	match bool
    12  	subs  []string
    13  }
    14  
    15  func checkRegexp(t *testing.T, r *regexp.Regexp, m regexpMatch) {
    16  	t.Helper()
    17  	matches := r.FindStringSubmatch(m.input)
    18  	if m.match && matches != nil {
    19  		if len(matches) != (r.NumSubexp()+1) || matches[0] != m.input {
    20  			t.Fatalf("Bad match result %#v for %q", matches, m.input)
    21  		}
    22  		if len(matches) < (len(m.subs) + 1) {
    23  			t.Errorf("Expected %d sub matches, only have %d for %q", len(m.subs), len(matches)-1, m.input)
    24  		}
    25  		for i := range m.subs {
    26  			if m.subs[i] != matches[i+1] {
    27  				t.Errorf("Unexpected submatch %d: %q, expected %q for %q", i+1, matches[i+1], m.subs[i], m.input)
    28  			}
    29  		}
    30  	} else if m.match {
    31  		t.Errorf("Expected match for %q", m.input)
    32  	} else if matches != nil {
    33  		t.Errorf("Unexpected match for %q", m.input)
    34  	}
    35  }
    36  
    37  func TestDomainRegexp(t *testing.T) {
    38  	t.Parallel()
    39  	tests := []struct {
    40  		input string
    41  		match bool
    42  	}{
    43  		{
    44  			input: "test.com",
    45  			match: true,
    46  		},
    47  		{
    48  			input: "test.com:10304",
    49  			match: true,
    50  		},
    51  		{
    52  			input: "test.com:http",
    53  			match: false,
    54  		},
    55  		{
    56  			input: "localhost",
    57  			match: true,
    58  		},
    59  		{
    60  			input: "localhost:8080",
    61  			match: true,
    62  		},
    63  		{
    64  			input: "a",
    65  			match: true,
    66  		},
    67  		{
    68  			input: "a.b",
    69  			match: true,
    70  		},
    71  		{
    72  			input: "ab.cd.com",
    73  			match: true,
    74  		},
    75  		{
    76  			input: "a-b.com",
    77  			match: true,
    78  		},
    79  		{
    80  			input: "-ab.com",
    81  			match: false,
    82  		},
    83  		{
    84  			input: "ab-.com",
    85  			match: false,
    86  		},
    87  		{
    88  			input: "ab.c-om",
    89  			match: true,
    90  		},
    91  		{
    92  			input: "ab.-com",
    93  			match: false,
    94  		},
    95  		{
    96  			input: "ab.com-",
    97  			match: false,
    98  		},
    99  		{
   100  			input: "0101.com",
   101  			match: true, // TODO(dmcgowan): valid if this should be allowed
   102  		},
   103  		{
   104  			input: "001a.com",
   105  			match: true,
   106  		},
   107  		{
   108  			input: "b.gbc.io:443",
   109  			match: true,
   110  		},
   111  		{
   112  			input: "b.gbc.io",
   113  			match: true,
   114  		},
   115  		{
   116  			input: "xn--n3h.com", // ☃.com in punycode
   117  			match: true,
   118  		},
   119  		{
   120  			input: "Asdf.com", // uppercase character
   121  			match: true,
   122  		},
   123  		{
   124  			input: "192.168.1.1:75050", // ipv4
   125  			match: true,
   126  		},
   127  		{
   128  			input: "192.168.1.1:750050", // port with more than 5 digits, it will fail on validation
   129  			match: true,
   130  		},
   131  		{
   132  			input: "[fd00:1:2::3]:75050", // ipv6 compressed
   133  			match: true,
   134  		},
   135  		{
   136  			input: "[fd00:1:2::3]75050", // ipv6 wrong port separator
   137  			match: false,
   138  		},
   139  		{
   140  			input: "[fd00:1:2::3]::75050", // ipv6 wrong port separator
   141  			match: false,
   142  		},
   143  		{
   144  			input: "[fd00:1:2::3%eth0]:75050", // ipv6 with zone
   145  			match: false,
   146  		},
   147  		{
   148  			input: "[fd00123123123]:75050", // ipv6 wrong format, will fail in validation
   149  			match: true,
   150  		},
   151  		{
   152  			input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:75050", // ipv6 long format
   153  			match: true,
   154  		},
   155  		{
   156  			input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:750505", // ipv6 long format and invalid port, it will fail in validation
   157  			match: true,
   158  		},
   159  		{
   160  			input: "fd00:1:2::3:75050", // bad ipv6 without square brackets
   161  			match: false,
   162  		},
   163  	}
   164  	r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
   165  	for _, tc := range tests {
   166  		tc := tc
   167  		t.Run(tc.input, func(t *testing.T) {
   168  			t.Parallel()
   169  			match := r.MatchString(tc.input)
   170  			if match != tc.match {
   171  				t.Errorf("Expected match=%t, got %t", tc.match, match)
   172  			}
   173  		})
   174  	}
   175  }
   176  
   177  func TestFullNameRegexp(t *testing.T) {
   178  	t.Parallel()
   179  	if anchoredNameRegexp.NumSubexp() != 2 {
   180  		t.Fatalf("anchored name regexp should have two submatches: %v, %v != 2",
   181  			anchoredNameRegexp, anchoredNameRegexp.NumSubexp())
   182  	}
   183  
   184  	tests := []regexpMatch{
   185  		{
   186  			input: "",
   187  			match: false,
   188  		},
   189  		{
   190  			input: "short",
   191  			match: true,
   192  			subs:  []string{"", "short"},
   193  		},
   194  		{
   195  			input: "simple/name",
   196  			match: true,
   197  			subs:  []string{"simple", "name"},
   198  		},
   199  		{
   200  			input: "library/ubuntu",
   201  			match: true,
   202  			subs:  []string{"library", "ubuntu"},
   203  		},
   204  		{
   205  			input: "docker/stevvooe/app",
   206  			match: true,
   207  			subs:  []string{"docker", "stevvooe/app"},
   208  		},
   209  		{
   210  			input: "aa/aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb",
   211  			match: true,
   212  			subs:  []string{"aa", "aa/aa/aa/aa/aa/aa/aa/aa/bb/bb/bb/bb/bb/bb"},
   213  		},
   214  		{
   215  			input: "aa/aa/bb/bb/bb",
   216  			match: true,
   217  			subs:  []string{"aa", "aa/bb/bb/bb"},
   218  		},
   219  		{
   220  			input: "a/a/a/a",
   221  			match: true,
   222  			subs:  []string{"a", "a/a/a"},
   223  		},
   224  		{
   225  			input: "a/a/a/a/",
   226  			match: false,
   227  		},
   228  		{
   229  			input: "a//a/a",
   230  			match: false,
   231  		},
   232  		{
   233  			input: "a",
   234  			match: true,
   235  			subs:  []string{"", "a"},
   236  		},
   237  		{
   238  			input: "a/aa",
   239  			match: true,
   240  			subs:  []string{"a", "aa"},
   241  		},
   242  		{
   243  			input: "a/aa/a",
   244  			match: true,
   245  			subs:  []string{"a", "aa/a"},
   246  		},
   247  		{
   248  			input: "foo.com",
   249  			match: true,
   250  			subs:  []string{"", "foo.com"},
   251  		},
   252  		{
   253  			input: "foo.com/",
   254  			match: false,
   255  		},
   256  		{
   257  			input: "foo.com:8080/bar",
   258  			match: true,
   259  			subs:  []string{"foo.com:8080", "bar"},
   260  		},
   261  		{
   262  			input: "foo.com:http/bar",
   263  			match: false,
   264  		},
   265  		{
   266  			input: "foo.com/bar",
   267  			match: true,
   268  			subs:  []string{"foo.com", "bar"},
   269  		},
   270  		{
   271  			input: "foo.com/bar/baz",
   272  			match: true,
   273  			subs:  []string{"foo.com", "bar/baz"},
   274  		},
   275  		{
   276  			input: "localhost:8080/bar",
   277  			match: true,
   278  			subs:  []string{"localhost:8080", "bar"},
   279  		},
   280  		{
   281  			input: "sub-dom1.foo.com/bar/baz/quux",
   282  			match: true,
   283  			subs:  []string{"sub-dom1.foo.com", "bar/baz/quux"},
   284  		},
   285  		{
   286  			input: "blog.foo.com/bar/baz",
   287  			match: true,
   288  			subs:  []string{"blog.foo.com", "bar/baz"},
   289  		},
   290  		{
   291  			input: "a^a",
   292  			match: false,
   293  		},
   294  		{
   295  			input: "aa/asdf$$^/aa",
   296  			match: false,
   297  		},
   298  		{
   299  			input: "asdf$$^/aa",
   300  			match: false,
   301  		},
   302  		{
   303  			input: "aa-a/a",
   304  			match: true,
   305  			subs:  []string{"aa-a", "a"},
   306  		},
   307  		{
   308  			input: strings.Repeat("a/", 128) + "a",
   309  			match: true,
   310  			subs:  []string{"a", strings.Repeat("a/", 127) + "a"},
   311  		},
   312  		{
   313  			input: "a-/a/a/a",
   314  			match: false,
   315  		},
   316  		{
   317  			input: "foo.com/a-/a/a",
   318  			match: false,
   319  		},
   320  		{
   321  			input: "-foo/bar",
   322  			match: false,
   323  		},
   324  		{
   325  			input: "foo/bar-",
   326  			match: false,
   327  		},
   328  		{
   329  			input: "foo-/bar",
   330  			match: false,
   331  		},
   332  		{
   333  			input: "foo/-bar",
   334  			match: false,
   335  		},
   336  		{
   337  			input: "_foo/bar",
   338  			match: false,
   339  		},
   340  		{
   341  			input: "foo_bar",
   342  			match: true,
   343  			subs:  []string{"", "foo_bar"},
   344  		},
   345  		{
   346  			input: "foo_bar.com",
   347  			match: true,
   348  			subs:  []string{"", "foo_bar.com"},
   349  		},
   350  		{
   351  			input: "foo_bar.com:8080",
   352  			match: false,
   353  		},
   354  		{
   355  			input: "foo_bar.com:8080/app",
   356  			match: false,
   357  		},
   358  		{
   359  			input: "foo.com/foo_bar",
   360  			match: true,
   361  			subs:  []string{"foo.com", "foo_bar"},
   362  		},
   363  		{
   364  			input: "____/____",
   365  			match: false,
   366  		},
   367  		{
   368  			input: "_docker/_docker",
   369  			match: false,
   370  		},
   371  		{
   372  			input: "docker_/docker_",
   373  			match: false,
   374  		},
   375  		{
   376  			input: "b.gcr.io/test.example.com/my-app",
   377  			match: true,
   378  			subs:  []string{"b.gcr.io", "test.example.com/my-app"},
   379  		},
   380  		{
   381  			input: "xn--n3h.com/myimage", // ☃.com in punycode
   382  			match: true,
   383  			subs:  []string{"xn--n3h.com", "myimage"},
   384  		},
   385  		{
   386  			input: "xn--7o8h.com/myimage", // 🐳.com in punycode
   387  			match: true,
   388  			subs:  []string{"xn--7o8h.com", "myimage"},
   389  		},
   390  		{
   391  			input: "example.com/xn--7o8h.com/myimage", // 🐳.com in punycode
   392  			match: true,
   393  			subs:  []string{"example.com", "xn--7o8h.com/myimage"},
   394  		},
   395  		{
   396  			input: "example.com/some_separator__underscore/myimage",
   397  			match: true,
   398  			subs:  []string{"example.com", "some_separator__underscore/myimage"},
   399  		},
   400  		{
   401  			input: "example.com/__underscore/myimage",
   402  			match: false,
   403  		},
   404  		{
   405  			input: "example.com/..dots/myimage",
   406  			match: false,
   407  		},
   408  		{
   409  			input: "example.com/.dots/myimage",
   410  			match: false,
   411  		},
   412  		{
   413  			input: "example.com/nodouble..dots/myimage",
   414  			match: false,
   415  		},
   416  		{
   417  			input: "example.com/nodouble..dots/myimage",
   418  			match: false,
   419  		},
   420  		{
   421  			input: "docker./docker",
   422  			match: false,
   423  		},
   424  		{
   425  			input: ".docker/docker",
   426  			match: false,
   427  		},
   428  		{
   429  			input: "docker-/docker",
   430  			match: false,
   431  		},
   432  		{
   433  			input: "-docker/docker",
   434  			match: false,
   435  		},
   436  		{
   437  			input: "do..cker/docker",
   438  			match: false,
   439  		},
   440  		{
   441  			input: "do__cker:8080/docker",
   442  			match: false,
   443  		},
   444  		{
   445  			input: "do__cker/docker",
   446  			match: true,
   447  			subs:  []string{"", "do__cker/docker"},
   448  		},
   449  		{
   450  			input: "b.gcr.io/test.example.com/my-app",
   451  			match: true,
   452  			subs:  []string{"b.gcr.io", "test.example.com/my-app"},
   453  		},
   454  		{
   455  			input: "registry.io/foo/project--id.module--name.ver---sion--name",
   456  			match: true,
   457  			subs:  []string{"registry.io", "foo/project--id.module--name.ver---sion--name"},
   458  		},
   459  		{
   460  			input: "Asdf.com/foo/bar", // uppercase character in hostname
   461  			match: true,
   462  		},
   463  		{
   464  			input: "Foo/FarB", // uppercase characters in remote name
   465  			match: false,
   466  		},
   467  	}
   468  	for _, tc := range tests {
   469  		tc := tc
   470  		t.Run(tc.input, func(t *testing.T) {
   471  			t.Parallel()
   472  			checkRegexp(t, anchoredNameRegexp, tc)
   473  		})
   474  	}
   475  }
   476  
   477  func TestReferenceRegexp(t *testing.T) {
   478  	t.Parallel()
   479  	if ReferenceRegexp.NumSubexp() != 3 {
   480  		t.Fatalf("anchored name regexp should have three submatches: %v, %v != 3",
   481  			ReferenceRegexp, ReferenceRegexp.NumSubexp())
   482  	}
   483  
   484  	tests := []regexpMatch{
   485  		{
   486  			input: "registry.com:8080/myapp:tag",
   487  			match: true,
   488  			subs:  []string{"registry.com:8080/myapp", "tag", ""},
   489  		},
   490  		{
   491  			input: "registry.com:8080/myapp@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
   492  			match: true,
   493  			subs:  []string{"registry.com:8080/myapp", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
   494  		},
   495  		{
   496  			input: "registry.com:8080/myapp:tag2@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
   497  			match: true,
   498  			subs:  []string{"registry.com:8080/myapp", "tag2", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
   499  		},
   500  		{
   501  			input: "registry.com:8080/myapp@sha256:badbadbadbad",
   502  			match: false,
   503  		},
   504  		{
   505  			input: "registry.com:8080/myapp:invalid~tag",
   506  			match: false,
   507  		},
   508  		{
   509  			input: "bad_hostname.com:8080/myapp:tag",
   510  			match: false,
   511  		},
   512  		{
   513  			input:// localhost treated as name, missing tag with 8080 as tag
   514  			"localhost:8080@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
   515  			match: true,
   516  			subs:  []string{"localhost", "8080", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
   517  		},
   518  		{
   519  			input: "localhost:8080/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
   520  			match: true,
   521  			subs:  []string{"localhost:8080/name", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
   522  		},
   523  		{
   524  			input: "localhost:http/name@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
   525  			match: false,
   526  		},
   527  		{
   528  			// localhost will be treated as an image name without a host
   529  			input: "localhost@sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912",
   530  			match: true,
   531  			subs:  []string{"localhost", "", "sha256:be178c0543eb17f5f3043021c9e5fcf30285e557a4fc309cce97ff9ca6182912"},
   532  		},
   533  		{
   534  			input: "registry.com:8080/myapp@bad",
   535  			match: false,
   536  		},
   537  		{
   538  			input: "registry.com:8080/myapp@2bad",
   539  			match: false, // TODO(dmcgowan): Support this as valid
   540  		},
   541  	}
   542  
   543  	for _, tc := range tests {
   544  		tc := tc
   545  		t.Run(tc.input, func(t *testing.T) {
   546  			t.Parallel()
   547  			checkRegexp(t, ReferenceRegexp, tc)
   548  		})
   549  	}
   550  }
   551  
   552  func TestIdentifierRegexp(t *testing.T) {
   553  	t.Parallel()
   554  	tests := []struct {
   555  		input string
   556  		match bool
   557  	}{
   558  		{
   559  			input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
   560  			match: true,
   561  		},
   562  		{
   563  			input: "7EC43B381E5AEFE6E04EFB0B3F0693FF2A4A50652D64AEC573905F2DB5889A1C",
   564  			match: false,
   565  		},
   566  		{
   567  			input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf",
   568  			match: false,
   569  		},
   570  		{
   571  			input: "sha256:da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf9821",
   572  			match: false,
   573  		},
   574  		{
   575  			input: "da304e823d8ca2b9d863a3c897baeb852ba21ea9a9f1414736394ae7fcaf98218482",
   576  			match: false,
   577  		},
   578  	}
   579  	for _, tc := range tests {
   580  		tc := tc
   581  		t.Run(tc.input, func(t *testing.T) {
   582  			t.Parallel()
   583  			match := anchoredIdentifierRegexp.MatchString(tc.input)
   584  			if match != tc.match {
   585  				t.Errorf("Expected match=%t, got %t", tc.match, match)
   586  			}
   587  		})
   588  	}
   589  }
   590  

View as plain text