diff --git a/src/common/models/retag.go b/src/common/models/retag.go index d684922b8..5b86576a7 100644 --- a/src/common/models/retag.go +++ b/src/common/models/retag.go @@ -38,7 +38,7 @@ type Image struct { func ParseImage(image string) (*Image, error) { repo := strings.SplitN(image, "/", 2) if len(repo) < 2 { - return nil, fmt.Errorf("Unable to parse image from string: %s", image) + return nil, fmt.Errorf("unable to parse image from string: %s", image) } i := strings.SplitN(repo[1], ":", 2) res := &Image{ diff --git a/src/common/utils/validate.go b/src/common/utils/validate.go new file mode 100644 index 000000000..19cf9fd61 --- /dev/null +++ b/src/common/utils/validate.go @@ -0,0 +1,24 @@ +package utils + +import ( + "fmt" + "regexp" +) + +const nameComponent = `[a-z0-9]+((?:[._]|__|[-]*)[a-z0-9]+)*` + +// TagRegexp is regular expression to match image tags, for example, 'v1.0' +var TagRegexp = regexp.MustCompile(`^[\w][\w.-]{0,127}$`) + +// RepoRegexp is regular expression to match repo name, for example, 'busybox', 'stage/busybox' +var RepoRegexp = regexp.MustCompile(fmt.Sprintf("^%s(/%s)*$", nameComponent, nameComponent)) + +// ValidateTag validates whether a tag is valid. +func ValidateTag(tag string) bool { + return TagRegexp.MatchString(tag) +} + +// ValidateRepo validates whether a repo name is valid. +func ValidateRepo(repo string) bool { + return RepoRegexp.MatchString(repo) +} diff --git a/src/common/utils/validate_test.go b/src/common/utils/validate_test.go new file mode 100644 index 000000000..5d91f6c63 --- /dev/null +++ b/src/common/utils/validate_test.go @@ -0,0 +1,183 @@ +package utils + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestValidateTag(t *testing.T) { + cases := []struct { + tag string + valid bool + }{ + { + "v1.0", + true, + }, + { + "1.0.0", + true, + }, + { + "v1.0-alpha.0", + true, + }, + { + "1__", + true, + }, + { + "__v1.0", + true, + }, + { + "_...", + true, + }, + { + "_-_", + true, + }, + { + "--v1.0", + false, + }, + { + ".0.1", + false, + }, + { + "-0.1", + false, + }, + { + "0.1.*", + false, + }, + { + "0.1.?", + false, + }, + } + + for _, c := range cases { + if c.valid { + assert.True(t, ValidateTag(c.tag)) + } else { + assert.False(t, ValidateTag(c.tag)) + } + } +} + +func TestValidateRepo(t *testing.T) { + cases := []struct { + repo string + valid bool + }{ + { + "a", + true, + }, + { + "a_a", + true, + }, + { + "a__a", + true, + }, + { + "a-a", + true, + }, + { + "a--a", + true, + }, + { + "a---a", + true, + }, + { + "a.a", + true, + }, + { + "a/b.b", + true, + }, + { + "a_a/b-b", + true, + }, + { + ".a", + false, + }, + { + "_a", + false, + }, + { + "-a", + false, + }, + { + "a.", + false, + }, + { + "a_", + false, + }, + { + "a-", + false, + }, + { + "a..a", + false, + }, + { + "a___a", + false, + }, + { + "a.-a", + false, + }, + { + "a_-a", + false, + }, + { + "a*", + false, + }, + { + "A/_a", + false, + }, + { + "A/.a", + false, + }, + { + "Aaaa", + false, + }, + { + "aaaA", + false, + }, + } + + for _, c := range cases { + if c.valid { + assert.True(t, ValidateRepo(c.repo)) + } else { + assert.False(t, ValidateRepo(c.repo)) + } + } +} diff --git a/src/core/api/repository.go b/src/core/api/repository.go index afb7b5b74..c97e2bd55 100644 --- a/src/core/api/repository.go +++ b/src/core/api/repository.go @@ -432,6 +432,12 @@ func (ra *RepositoryAPI) Retag() { } repoName := ra.GetString(":splat") + project, repo := utils.ParseRepository(repoName) + if !utils.ValidateRepo(repo) { + ra.HandleBadRequest(fmt.Sprintf("invalid repo '%s'", repo)) + return + } + request := models.RetagRequest{} ra.DecodeJSONReq(&request) srcImage, err := models.ParseImage(request.SrcImage) @@ -440,6 +446,11 @@ func (ra *RepositoryAPI) Retag() { return } + if !utils.ValidateTag(request.Tag) { + ra.HandleBadRequest(fmt.Sprintf("invalid tag '%s'", request.Tag)) + return + } + // Check whether source image exists exist, _, err := ra.checkExistence(fmt.Sprintf("%s/%s", srcImage.Project, srcImage.Repo), srcImage.Tag) if err != nil { @@ -452,7 +463,6 @@ func (ra *RepositoryAPI) Retag() { } // Check whether target project exists - project, repo := utils.ParseRepository(repoName) exist, err = ra.ProjectMgr.Exists(project) if err != nil { ra.ParseAndHandleError(fmt.Sprintf("failed to check the existence of project %s", project), err) @@ -476,7 +486,7 @@ func (ra *RepositoryAPI) Retag() { } } - // Check whether use has read permission to source project + // Check whether user has read permission to source project if !ra.SecurityCtx.HasReadPerm(srcImage.Project) { log.Errorf("user has no read permission to project '%s'", srcImage.Project) ra.HandleForbidden(fmt.Sprintf("%s has no read permission to project %s", ra.SecurityCtx.GetUsername(), srcImage.Project)) diff --git a/src/core/api/repository_test.go b/src/core/api/repository_test.go index 9e8bb1e3a..34649d245 100644 --- a/src/core/api/repository_test.go +++ b/src/core/api/repository_test.go @@ -440,5 +440,33 @@ func TestRetag(t *testing.T) { assert.Equal(int(409), httpStatusCode, "httpStatusCode should be 409") } + // -------------------case 7 : response code = 400------------------------// + fmt.Println("case 7 : response code = 400") + retagReq = &apilib.Retag{ + Tag: ".0.1", + SrcImage: "library/hello-world:latest", + Override: true, + } + code, err = apiTest.RetagImage(*admin, repo, retagReq) + if err != nil { + t.Errorf("failed to retag: %v", err) + } else { + assert.Equal(int(400), code, "response code should be 400") + } + + // -------------------case 8 : response code = 400------------------------// + fmt.Println("case 8 : response code = 400") + retagReq = &apilib.Retag{ + Tag: "v0.1", + SrcImage: "library/hello-world:latest", + Override: true, + } + code, err = apiTest.RetagImage(*admin, "library/Aaaa", retagReq) + if err != nil { + t.Errorf("failed to retag: %v", err) + } else { + assert.Equal(int(400), code, "response code should be 400") + } + fmt.Printf("\n") }