diff --git a/go.mod b/go.mod index b5341fa..d2e7208 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/git-pkgs/purl go 1.25.6 require ( - github.com/git-pkgs/vers v0.3.1 + github.com/git-pkgs/vers v0.6.0 github.com/package-url/packageurl-go v0.1.7 ) diff --git a/go.sum b/go.sum index beb0db0..8e48ab8 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,4 @@ -github.com/git-pkgs/vers v0.3.1 h1:jy/ht2wIRJI5zQrccm6GTeYr+hGFwe2z8LV1HOr4Wco= -github.com/git-pkgs/vers v0.3.1/go.mod h1:biTbSQK1qdbrsxDEKnqe3Jzclxz8vW6uDcwKjfUGcOo= +github.com/git-pkgs/vers v0.6.0 h1:droJw8+oSyl8/UoDj/96B9ZPggmxJDTl/JeixzfKzSc= +github.com/git-pkgs/vers v0.6.0/go.mod h1:biTbSQK1qdbrsxDEKnqe3Jzclxz8vW6uDcwKjfUGcOo= github.com/package-url/packageurl-go v0.1.7 h1:iFWg6tzAjLA6F/qX3M5nZaiMHJgc+p2zxVyr/fY+sZY= github.com/package-url/packageurl-go v0.1.7/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= diff --git a/makepurl.go b/makepurl.go index 37255f2..ac3cb8f 100644 --- a/makepurl.go +++ b/makepurl.go @@ -7,13 +7,16 @@ import ( packageurl "github.com/package-url/packageurl-go" ) -// CleanVersion extracts a version from a version constraint string. -// Uses the vers library to parse the constraint and extract the minimum bound. -// If parsing fails, returns the original string. +// CleanVersion returns plain versions unchanged. For version constraints, it +// uses the vers library to extract the minimum bound. If parsing fails, it +// returns the original string. func CleanVersion(version, scheme string) string { if version == "" { return "" } + if !hasConstraintSyntax(version, scheme) && vers.ValidWithScheme(version, scheme) { + return version + } r, err := vers.ParseNative(version, scheme) if err != nil || len(r.Intervals) == 0 { @@ -28,6 +31,29 @@ func CleanVersion(version, scheme string) string { return version } +func hasConstraintSyntax(version, scheme string) bool { + constraint := strings.TrimSpace(version) + if constraint == "" { + return false + } + + switch constraint[0] { + case '<', '>', '=', '!', '^', '~': + return true + } + + switch scheme { + case ecosystemMaven: + return constraint[0] == '[' || constraint[0] == '(' + case "conan": + return constraint == "*" || constraint == "*-" || + strings.HasSuffix(constraint, "-") || strings.Contains(constraint, "||") || + strings.Contains(constraint, ",") + } + + return false +} + // BuildPURLString builds a PURL string directly from ecosystem-native identifiers // without creating intermediate PURL structs. This is the fast path for manifest // parsing where we just need the string output. It returns an empty string when diff --git a/makepurl_test.go b/makepurl_test.go index fbf61c8..9d68d81 100644 --- a/makepurl_test.go +++ b/makepurl_test.go @@ -25,15 +25,26 @@ func TestCleanVersion(t *testing.T) { {">=1.0.0", "pypi", "1.0.0"}, {"~=1.4.2", "pypi", "1.4.2"}, + // maven constraints + {"[1.0,2.0)", "maven", "1.0"}, + // cargo constraints {"^1.0.0", "cargo", "1.0.0"}, + // conan constraints + {"^1.2.3", "conan", "1.2.3"}, + {"1.2.3-", "conan", "1.2.3"}, + // Plain versions pass through + {"1.0", "composer", "1.0"}, + {"1.0", "npm", "1.0"}, {"1.0.0", "npm", "1.0.0"}, {"v1.0.0", "go", "v1.0.0"}, + {" 1.0 ", "composer", " 1.0 "}, - // Empty + // Empty and whitespace-only {"", "npm", ""}, + {" ", "npm", " "}, } for _, tt := range tests { @@ -64,6 +75,7 @@ func TestBuildPURLString(t *testing.T) { {"with registry", "npm", "lodash", "1.0.0", "https://npm.example.com", "pkg:npm/lodash@1.0.0?repository_url=https:%2F%2Fnpm.example.com"}, {"default registry ignored", "npm", "lodash", "1.0.0", "https://registry.npmjs.org", "pkg:npm/lodash@1.0.0"}, {"composer", "packagist", "vendor/pkg", "1.0", "", "pkg:composer/vendor/pkg@1.0"}, + {"conan range prerelease marker", "conan", "openssl", "1.2.3-", "", "pkg:conan/openssl@1.2.3"}, {"composer normalization", "packagist", "Vendor/Package", "1.0", "", "pkg:composer/vendor/package@1.0"}, {"pypi normalization", "pypi", "Django_REST", "1.0.0", "", "pkg:pypi/django-rest@1.0.0"}, {"golang normalization", "golang", "GitHub.com/Foo/Bar", "v1.0.0", "", "pkg:golang/github.com/foo/bar@v1.0.0"},