forked from Shiloh/githaven
e4eaa68a2b
I don't see why we have to use two versions of yaml. The difference between the two versions has nothing to do with our usage.
222 lines
6.8 KiB
Go
222 lines
6.8 KiB
Go
// Copyright 2021 The Gitea Authors. All rights reserved.
|
|
// Use of this source code is governed by a MIT-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package rubygems
|
|
|
|
import (
|
|
"archive/tar"
|
|
"compress/gzip"
|
|
"errors"
|
|
"io"
|
|
"regexp"
|
|
"strings"
|
|
|
|
"code.gitea.io/gitea/modules/validation"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
var (
|
|
// ErrMissingMetadataFile indicates a missing metadata.gz file
|
|
ErrMissingMetadataFile = errors.New("Metadata file is missing")
|
|
// ErrInvalidName indicates an invalid id in the metadata.gz file
|
|
ErrInvalidName = errors.New("Metadata file contains an invalid name")
|
|
// ErrInvalidVersion indicates an invalid version in the metadata.gz file
|
|
ErrInvalidVersion = errors.New("Metadata file contains an invalid version")
|
|
)
|
|
|
|
var versionMatcher = regexp.MustCompile(`\A[0-9]+(?:\.[0-9a-zA-Z]+)*(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?\z`)
|
|
|
|
// Package represents a RubyGems package
|
|
type Package struct {
|
|
Name string
|
|
Version string
|
|
Metadata *Metadata
|
|
}
|
|
|
|
// Metadata represents the metadata of a RubyGems package
|
|
type Metadata struct {
|
|
Platform string `json:"platform,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Summary string `json:"summary,omitempty"`
|
|
Authors []string `json:"authors,omitempty"`
|
|
Licenses []string `json:"licenses,omitempty"`
|
|
RequiredRubyVersion []VersionRequirement `json:"required_ruby_version,omitempty"`
|
|
RequiredRubygemsVersion []VersionRequirement `json:"required_rubygems_version,omitempty"`
|
|
ProjectURL string `json:"project_url,omitempty"`
|
|
RuntimeDependencies []Dependency `json:"runtime_dependencies,omitempty"`
|
|
DevelopmentDependencies []Dependency `json:"development_dependencies,omitempty"`
|
|
}
|
|
|
|
// VersionRequirement represents a version restriction
|
|
type VersionRequirement struct {
|
|
Restriction string `json:"restriction"`
|
|
Version string `json:"version"`
|
|
}
|
|
|
|
// Dependency represents a dependency of a RubyGems package
|
|
type Dependency struct {
|
|
Name string `json:"name"`
|
|
Version []VersionRequirement `json:"version"`
|
|
}
|
|
|
|
type gemspec struct {
|
|
Name string `yaml:"name"`
|
|
Version struct {
|
|
Version string `yaml:"version"`
|
|
} `yaml:"version"`
|
|
Platform string `yaml:"platform"`
|
|
Authors []string `yaml:"authors"`
|
|
Autorequire interface{} `yaml:"autorequire"`
|
|
Bindir string `yaml:"bindir"`
|
|
CertChain []interface{} `yaml:"cert_chain"`
|
|
Date string `yaml:"date"`
|
|
Dependencies []struct {
|
|
Name string `yaml:"name"`
|
|
Requirement requirement `yaml:"requirement"`
|
|
Type string `yaml:"type"`
|
|
Prerelease bool `yaml:"prerelease"`
|
|
VersionRequirements requirement `yaml:"version_requirements"`
|
|
} `yaml:"dependencies"`
|
|
Description string `yaml:"description"`
|
|
Executables []string `yaml:"executables"`
|
|
Extensions []interface{} `yaml:"extensions"`
|
|
ExtraRdocFiles []string `yaml:"extra_rdoc_files"`
|
|
Files []string `yaml:"files"`
|
|
Homepage string `yaml:"homepage"`
|
|
Licenses []string `yaml:"licenses"`
|
|
Metadata struct {
|
|
BugTrackerURI string `yaml:"bug_tracker_uri"`
|
|
ChangelogURI string `yaml:"changelog_uri"`
|
|
DocumentationURI string `yaml:"documentation_uri"`
|
|
SourceCodeURI string `yaml:"source_code_uri"`
|
|
} `yaml:"metadata"`
|
|
PostInstallMessage interface{} `yaml:"post_install_message"`
|
|
RdocOptions []interface{} `yaml:"rdoc_options"`
|
|
RequirePaths []string `yaml:"require_paths"`
|
|
RequiredRubyVersion requirement `yaml:"required_ruby_version"`
|
|
RequiredRubygemsVersion requirement `yaml:"required_rubygems_version"`
|
|
Requirements []interface{} `yaml:"requirements"`
|
|
RubygemsVersion string `yaml:"rubygems_version"`
|
|
SigningKey interface{} `yaml:"signing_key"`
|
|
SpecificationVersion int `yaml:"specification_version"`
|
|
Summary string `yaml:"summary"`
|
|
TestFiles []interface{} `yaml:"test_files"`
|
|
}
|
|
|
|
type requirement struct {
|
|
Requirements [][]interface{} `yaml:"requirements"`
|
|
}
|
|
|
|
// AsVersionRequirement converts into []VersionRequirement
|
|
func (r requirement) AsVersionRequirement() []VersionRequirement {
|
|
requirements := make([]VersionRequirement, 0, len(r.Requirements))
|
|
for _, req := range r.Requirements {
|
|
if len(req) != 2 {
|
|
continue
|
|
}
|
|
restriction, ok := req[0].(string)
|
|
if !ok {
|
|
continue
|
|
}
|
|
vm, ok := req[1].(map[string]interface{})
|
|
if !ok {
|
|
continue
|
|
}
|
|
versionInt, ok := vm["version"]
|
|
if !ok {
|
|
continue
|
|
}
|
|
version, ok := versionInt.(string)
|
|
if !ok || version == "0" {
|
|
continue
|
|
}
|
|
|
|
requirements = append(requirements, VersionRequirement{
|
|
Restriction: restriction,
|
|
Version: version,
|
|
})
|
|
}
|
|
return requirements
|
|
}
|
|
|
|
// ParsePackageMetaData parses the metadata of a Gem package file
|
|
func ParsePackageMetaData(r io.Reader) (*Package, error) {
|
|
archive := tar.NewReader(r)
|
|
for {
|
|
hdr, err := archive.Next()
|
|
if err == io.EOF {
|
|
break
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if hdr.Name == "metadata.gz" {
|
|
return parseMetadataFile(archive)
|
|
}
|
|
}
|
|
|
|
return nil, ErrMissingMetadataFile
|
|
}
|
|
|
|
func parseMetadataFile(r io.Reader) (*Package, error) {
|
|
zr, err := gzip.NewReader(r)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer zr.Close()
|
|
|
|
var spec gemspec
|
|
if err := yaml.NewDecoder(zr).Decode(&spec); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(spec.Name) == 0 || strings.Contains(spec.Name, "/") {
|
|
return nil, ErrInvalidName
|
|
}
|
|
|
|
if !versionMatcher.MatchString(spec.Version.Version) {
|
|
return nil, ErrInvalidVersion
|
|
}
|
|
|
|
if !validation.IsValidURL(spec.Homepage) {
|
|
spec.Homepage = ""
|
|
}
|
|
if !validation.IsValidURL(spec.Metadata.SourceCodeURI) {
|
|
spec.Metadata.SourceCodeURI = ""
|
|
}
|
|
|
|
m := &Metadata{
|
|
Platform: spec.Platform,
|
|
Description: spec.Description,
|
|
Summary: spec.Summary,
|
|
Authors: spec.Authors,
|
|
Licenses: spec.Licenses,
|
|
ProjectURL: spec.Homepage,
|
|
RequiredRubyVersion: spec.RequiredRubyVersion.AsVersionRequirement(),
|
|
RequiredRubygemsVersion: spec.RequiredRubygemsVersion.AsVersionRequirement(),
|
|
DevelopmentDependencies: make([]Dependency, 0, 5),
|
|
RuntimeDependencies: make([]Dependency, 0, 5),
|
|
}
|
|
|
|
for _, gemdep := range spec.Dependencies {
|
|
dep := Dependency{
|
|
Name: gemdep.Name,
|
|
Version: gemdep.Requirement.AsVersionRequirement(),
|
|
}
|
|
if gemdep.Type == ":runtime" {
|
|
m.RuntimeDependencies = append(m.RuntimeDependencies, dep)
|
|
} else {
|
|
m.DevelopmentDependencies = append(m.DevelopmentDependencies, dep)
|
|
}
|
|
}
|
|
|
|
return &Package{
|
|
Name: spec.Name,
|
|
Version: spec.Version.Version,
|
|
Metadata: m,
|
|
}, nil
|
|
}
|