diff --git a/modules/packages/rubygems/metadata.go b/modules/packages/rubygems/metadata.go index 8a989bbe7f..2258034ba4 100644 --- a/modules/packages/rubygems/metadata.go +++ b/modules/packages/rubygems/metadata.go @@ -8,7 +8,6 @@ import ( "compress/gzip" "io" "regexp" - "strings" "sync" "gitea.dev/modules/util" @@ -26,8 +25,14 @@ var ( ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") ) -var versionMatcher = sync.OnceValue(func() *regexp.Regexp { - return regexp.MustCompile(`\A[0-9]+(?:\.[0-9a-zA-Z]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?\z`) +var globalVars = sync.OnceValue(func() (ret struct { + nameMatcher, versionMatcher *regexp.Regexp +}, +) { + // https://github.com/rubygems/rubygems/blob/master/lib/rubygems/specification.rb (VALID_NAME_PATTERN) + ret.nameMatcher = regexp.MustCompile(`\A[\w.-]+\z`) + ret.versionMatcher = regexp.MustCompile(`\A[0-9]+(?:\.[0-9a-zA-Z]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?\z`) + return ret }) // Package represents a RubyGems package @@ -175,11 +180,11 @@ func parseMetadataFile(r io.Reader) (*Package, error) { return nil, err } - if len(spec.Name) == 0 || strings.Contains(spec.Name, "/") { + if !globalVars().nameMatcher.MatchString(spec.Name) { return nil, ErrInvalidName } - if !versionMatcher().MatchString(spec.Version.Version) { + if !globalVars().versionMatcher.MatchString(spec.Version.Version) { return nil, ErrInvalidVersion } diff --git a/modules/packages/rubygems/metadata_test.go b/modules/packages/rubygems/metadata_test.go index da75edd04a..8917bd2b36 100644 --- a/modules/packages/rubygems/metadata_test.go +++ b/modules/packages/rubygems/metadata_test.go @@ -32,6 +32,17 @@ version: assert.NoError(t, err) assert.NotNil(t, rp) }) + + t.Run("InvalidName", func(t *testing.T) { + // a name carrying a newline would be re-emitted verbatim into the + // line-based compact index, letting an upload forge extra entries + for _, quotedName := range []string{`"evil\n1.0.0"`, `"a b"`, `"a/b"`, `""`} { + content := test.CompressGzip("name: " + quotedName + "\nversion:\n version: 1\n") + rp, err := parseMetadataFile(content) + assert.ErrorIs(t, err, ErrInvalidName, "name %s should be rejected", quotedName) + assert.Nil(t, rp) + } + }) } func TestParseMetadataFile(t *testing.T) {