cmd/go: validating version format in mod edit Version strings set by -retract and -exclude are not canonicalized by go mod commands. This change adds validation to go mod edit to prevent invalid version strings from being added to the go.mod file. For golang/go#43280 Change-Id: I3708b7a09111a56effac1fe1165122772e3f2d75 Reviewed-on: https://go-review.googlesource.com/c/mod/+/279394 Run-TryBot: Bryan C. Mills <bcmills@google.com> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Jay Conrod <jayconrod@google.com> Reviewed-by: Bryan C. Mills <bcmills@google.com> Trust: Michael Matloob <matloob@golang.org>
diff --git a/modfile/rule.go b/modfile/rule.go index 83398dd..c6a189d 100644 --- a/modfile/rule.go +++ b/modfile/rule.go
@@ -832,7 +832,16 @@ return nil } +// AddExclude adds a exclude statement to the mod file. Errors if the provided +// version is not a canonical version string func (f *File) AddExclude(path, vers string) error { + if !isCanonicalVersion(vers) { + return &module.InvalidVersionError{ + Version: vers, + Err: errors.New("must be of the form v1.2.3"), + } + } + var hint *Line for _, x := range f.Exclude { if x.Mod.Path == path && x.Mod.Version == vers { @@ -904,7 +913,22 @@ return nil } +// AddRetract adds a retract statement to the mod file. Errors if the provided +// version interval does not consist of canonical version strings func (f *File) AddRetract(vi VersionInterval, rationale string) error { + if !isCanonicalVersion(vi.High) { + return &module.InvalidVersionError{ + Version: vi.High, + Err: errors.New("must be of the form v1.2.3"), + } + } + if !isCanonicalVersion(vi.Low) { + return &module.InvalidVersionError{ + Version: vi.Low, + Err: errors.New("must be of the form v1.2.3"), + } + } + r := &Retract{ VersionInterval: vi, } @@ -1061,3 +1085,9 @@ } return semver.Compare(vii.High, vij.High) > 0 } + +// isCanonicalVersion tests if the provided version string represents a valid +// canonical version. +func isCanonicalVersion(vers string) bool { + return vers != "" && semver.Canonical(vers) == vers +}
diff --git a/modfile/rule_test.go b/modfile/rule_test.go index fbf144d..03123ed 100644 --- a/modfile/rule_test.go +++ b/modfile/rule_test.go
@@ -568,6 +568,48 @@ }, } +var addRetractValidateVersionTests = []struct { + dsc, low, high string +}{ + { + "blank_version", + "", + "", + }, + { + "missing_prefix", + "1.0.0", + "1.0.0", + }, + { + "non_canonical", + "v1.2", + "v1.2", + }, + { + "invalid_range", + "v1.2.3", + "v1.3", + }, +} + +var addExcludeValidateVersionTests = []struct { + dsc, ver string +}{ + { + "blank_version", + "", + }, + { + "missing_prefix", + "1.0.0", + }, + { + "non_canonical", + "v1.2", + }, +} + func TestAddRequire(t *testing.T) { for _, tt := range addRequireTests { t.Run(tt.desc, func(t *testing.T) { @@ -699,3 +741,31 @@ return f } + +func TestAddRetractValidateVersion(t *testing.T) { + for _, tt := range addRetractValidateVersionTests { + t.Run(tt.dsc, func(t *testing.T) { + f, err := Parse("in", []byte("module m"), nil) + if err != nil { + t.Fatal(err) + } + if err = f.AddRetract(VersionInterval{Low: tt.low, High: tt.high}, ""); err == nil { + t.Fatal("expected AddRetract to complain about version format") + } + }) + } +} + +func TestAddExcludeValidateVersion(t *testing.T) { + for _, tt := range addExcludeValidateVersionTests { + t.Run(tt.dsc, func(t *testing.T) { + f, err := Parse("in", []byte("module m"), nil) + if err != nil { + t.Fatal(err) + } + if err = f.AddExclude("aa", tt.ver); err == nil { + t.Fatal("expected AddExclude to complain about version format") + } + }) + } +}