mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 00:20:25 +08:00 
			
		
		
		
	Add Cargo package registry (#21888)
This PR implements a [Cargo registry](https://doc.rust-lang.org/cargo/) to manage Rust packages. This package type was a little bit more complicated because Cargo needs an additional Git repository to store its package index. Screenshots:    --------- Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
		
							
								
								
									
										169
									
								
								modules/packages/cargo/parser.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										169
									
								
								modules/packages/cargo/parser.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,169 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package cargo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"io"
 | 
			
		||||
	"regexp"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/modules/json"
 | 
			
		||||
	"code.gitea.io/gitea/modules/validation"
 | 
			
		||||
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const PropertyYanked = "cargo.yanked"
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ErrInvalidName    = errors.New("package name is invalid")
 | 
			
		||||
	ErrInvalidVersion = errors.New("package version is invalid")
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// Package represents a Cargo package
 | 
			
		||||
type Package struct {
 | 
			
		||||
	Name        string
 | 
			
		||||
	Version     string
 | 
			
		||||
	Metadata    *Metadata
 | 
			
		||||
	Content     io.Reader
 | 
			
		||||
	ContentSize int64
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Metadata represents the metadata of a Cargo package
 | 
			
		||||
type Metadata struct {
 | 
			
		||||
	Dependencies     []*Dependency       `json:"dependencies,omitempty"`
 | 
			
		||||
	Features         map[string][]string `json:"features,omitempty"`
 | 
			
		||||
	Authors          []string            `json:"authors,omitempty"`
 | 
			
		||||
	Description      string              `json:"description,omitempty"`
 | 
			
		||||
	DocumentationURL string              `json:"documentation_url,omitempty"`
 | 
			
		||||
	ProjectURL       string              `json:"project_url,omitempty"`
 | 
			
		||||
	Readme           string              `json:"readme,omitempty"`
 | 
			
		||||
	Keywords         []string            `json:"keywords,omitempty"`
 | 
			
		||||
	Categories       []string            `json:"categories,omitempty"`
 | 
			
		||||
	License          string              `json:"license,omitempty"`
 | 
			
		||||
	RepositoryURL    string              `json:"repository_url,omitempty"`
 | 
			
		||||
	Links            string              `json:"links,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type Dependency struct {
 | 
			
		||||
	Name            string   `json:"name"`
 | 
			
		||||
	Req             string   `json:"req"`
 | 
			
		||||
	Features        []string `json:"features"`
 | 
			
		||||
	Optional        bool     `json:"optional"`
 | 
			
		||||
	DefaultFeatures bool     `json:"default_features"`
 | 
			
		||||
	Target          *string  `json:"target"`
 | 
			
		||||
	Kind            string   `json:"kind"`
 | 
			
		||||
	Registry        *string  `json:"registry"`
 | 
			
		||||
	Package         *string  `json:"package"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var nameMatch = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9-_]{0,63}\z`)
 | 
			
		||||
 | 
			
		||||
// ParsePackage reads the metadata and content of a package
 | 
			
		||||
func ParsePackage(r io.Reader) (*Package, error) {
 | 
			
		||||
	var size uint32
 | 
			
		||||
	if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p, err := parsePackage(io.LimitReader(r, int64(size)))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if err := binary.Read(r, binary.LittleEndian, &size); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	p.Content = io.LimitReader(r, int64(size))
 | 
			
		||||
	p.ContentSize = int64(size)
 | 
			
		||||
 | 
			
		||||
	return p, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func parsePackage(r io.Reader) (*Package, error) {
 | 
			
		||||
	var meta struct {
 | 
			
		||||
		Name string `json:"name"`
 | 
			
		||||
		Vers string `json:"vers"`
 | 
			
		||||
		Deps []struct {
 | 
			
		||||
			Name               string   `json:"name"`
 | 
			
		||||
			VersionReq         string   `json:"version_req"`
 | 
			
		||||
			Features           []string `json:"features"`
 | 
			
		||||
			Optional           bool     `json:"optional"`
 | 
			
		||||
			DefaultFeatures    bool     `json:"default_features"`
 | 
			
		||||
			Target             *string  `json:"target"`
 | 
			
		||||
			Kind               string   `json:"kind"`
 | 
			
		||||
			Registry           *string  `json:"registry"`
 | 
			
		||||
			ExplicitNameInToml string   `json:"explicit_name_in_toml"`
 | 
			
		||||
		} `json:"deps"`
 | 
			
		||||
		Features      map[string][]string `json:"features"`
 | 
			
		||||
		Authors       []string            `json:"authors"`
 | 
			
		||||
		Description   string              `json:"description"`
 | 
			
		||||
		Documentation string              `json:"documentation"`
 | 
			
		||||
		Homepage      string              `json:"homepage"`
 | 
			
		||||
		Readme        string              `json:"readme"`
 | 
			
		||||
		ReadmeFile    string              `json:"readme_file"`
 | 
			
		||||
		Keywords      []string            `json:"keywords"`
 | 
			
		||||
		Categories    []string            `json:"categories"`
 | 
			
		||||
		License       string              `json:"license"`
 | 
			
		||||
		LicenseFile   string              `json:"license_file"`
 | 
			
		||||
		Repository    string              `json:"repository"`
 | 
			
		||||
		Links         string              `json:"links"`
 | 
			
		||||
	}
 | 
			
		||||
	if err := json.NewDecoder(r).Decode(&meta); err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !nameMatch.MatchString(meta.Name) {
 | 
			
		||||
		return nil, ErrInvalidName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if _, err := version.NewSemver(meta.Vers); err != nil {
 | 
			
		||||
		return nil, ErrInvalidVersion
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !validation.IsValidURL(meta.Homepage) {
 | 
			
		||||
		meta.Homepage = ""
 | 
			
		||||
	}
 | 
			
		||||
	if !validation.IsValidURL(meta.Documentation) {
 | 
			
		||||
		meta.Documentation = ""
 | 
			
		||||
	}
 | 
			
		||||
	if !validation.IsValidURL(meta.Repository) {
 | 
			
		||||
		meta.Repository = ""
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	dependencies := make([]*Dependency, 0, len(meta.Deps))
 | 
			
		||||
	for _, dep := range meta.Deps {
 | 
			
		||||
		dependencies = append(dependencies, &Dependency{
 | 
			
		||||
			Name:            dep.Name,
 | 
			
		||||
			Req:             dep.VersionReq,
 | 
			
		||||
			Features:        dep.Features,
 | 
			
		||||
			Optional:        dep.Optional,
 | 
			
		||||
			DefaultFeatures: dep.DefaultFeatures,
 | 
			
		||||
			Target:          dep.Target,
 | 
			
		||||
			Kind:            dep.Kind,
 | 
			
		||||
			Registry:        dep.Registry,
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &Package{
 | 
			
		||||
		Name:    meta.Name,
 | 
			
		||||
		Version: meta.Vers,
 | 
			
		||||
		Metadata: &Metadata{
 | 
			
		||||
			Dependencies:     dependencies,
 | 
			
		||||
			Features:         meta.Features,
 | 
			
		||||
			Authors:          meta.Authors,
 | 
			
		||||
			Description:      meta.Description,
 | 
			
		||||
			DocumentationURL: meta.Documentation,
 | 
			
		||||
			ProjectURL:       meta.Homepage,
 | 
			
		||||
			Readme:           meta.Readme,
 | 
			
		||||
			Keywords:         meta.Keywords,
 | 
			
		||||
			Categories:       meta.Categories,
 | 
			
		||||
			License:          meta.License,
 | 
			
		||||
			RepositoryURL:    meta.Repository,
 | 
			
		||||
			Links:            meta.Links,
 | 
			
		||||
		},
 | 
			
		||||
	}, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										86
									
								
								modules/packages/cargo/parser_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								modules/packages/cargo/parser_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,86 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// SPDX-License-Identifier: MIT
 | 
			
		||||
 | 
			
		||||
package cargo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/binary"
 | 
			
		||||
	"io"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"testing"
 | 
			
		||||
 | 
			
		||||
	"github.com/stretchr/testify/assert"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	description = "Package Description"
 | 
			
		||||
	author      = "KN4CK3R"
 | 
			
		||||
	homepage    = "https://gitea.io/"
 | 
			
		||||
	license     = "MIT"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParsePackage(t *testing.T) {
 | 
			
		||||
	createPackage := func(name, version string) io.Reader {
 | 
			
		||||
		metadata := `{
 | 
			
		||||
   "name":"` + name + `",
 | 
			
		||||
   "vers":"` + version + `",
 | 
			
		||||
   "description":"` + description + `",
 | 
			
		||||
   "authors": ["` + author + `"],
 | 
			
		||||
   "deps":[
 | 
			
		||||
      {
 | 
			
		||||
         "name":"dep",
 | 
			
		||||
         "version_req":"1.0"
 | 
			
		||||
      }
 | 
			
		||||
   ],
 | 
			
		||||
   "homepage":"` + homepage + `",
 | 
			
		||||
   "license":"` + license + `"
 | 
			
		||||
}`
 | 
			
		||||
 | 
			
		||||
		var buf bytes.Buffer
 | 
			
		||||
		binary.Write(&buf, binary.LittleEndian, uint32(len(metadata)))
 | 
			
		||||
		buf.WriteString(metadata)
 | 
			
		||||
		binary.Write(&buf, binary.LittleEndian, uint32(4))
 | 
			
		||||
		buf.WriteString("test")
 | 
			
		||||
		return &buf
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	t.Run("InvalidName", func(t *testing.T) {
 | 
			
		||||
		for _, name := range []string{"", "0test", "-test", "_test", strings.Repeat("a", 65)} {
 | 
			
		||||
			data := createPackage(name, "1.0.0")
 | 
			
		||||
 | 
			
		||||
			cp, err := ParsePackage(data)
 | 
			
		||||
			assert.Nil(t, cp)
 | 
			
		||||
			assert.ErrorIs(t, err, ErrInvalidName)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("InvalidVersion", func(t *testing.T) {
 | 
			
		||||
		for _, version := range []string{"", "1.", "-1.0", "1.0.0/1"} {
 | 
			
		||||
			data := createPackage("test", version)
 | 
			
		||||
 | 
			
		||||
			cp, err := ParsePackage(data)
 | 
			
		||||
			assert.Nil(t, cp)
 | 
			
		||||
			assert.ErrorIs(t, err, ErrInvalidVersion)
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Valid", func(t *testing.T) {
 | 
			
		||||
		data := createPackage("test", "1.0.0")
 | 
			
		||||
 | 
			
		||||
		cp, err := ParsePackage(data)
 | 
			
		||||
		assert.NotNil(t, cp)
 | 
			
		||||
		assert.NoError(t, err)
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, "test", cp.Name)
 | 
			
		||||
		assert.Equal(t, "1.0.0", cp.Version)
 | 
			
		||||
		assert.Equal(t, description, cp.Metadata.Description)
 | 
			
		||||
		assert.Equal(t, []string{author}, cp.Metadata.Authors)
 | 
			
		||||
		assert.Len(t, cp.Metadata.Dependencies, 1)
 | 
			
		||||
		assert.Equal(t, "dep", cp.Metadata.Dependencies[0].Name)
 | 
			
		||||
		assert.Equal(t, homepage, cp.Metadata.ProjectURL)
 | 
			
		||||
		assert.Equal(t, license, cp.Metadata.License)
 | 
			
		||||
		content, _ := io.ReadAll(cp.Content)
 | 
			
		||||
		assert.Equal(t, "test", string(content))
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
@@ -211,6 +211,7 @@ func CreateRepository(doer, u *user_model.User, opts CreateRepoOptions) (*repo_m
 | 
			
		||||
		IsEmpty:                         !opts.AutoInit,
 | 
			
		||||
		TrustModel:                      opts.TrustModel,
 | 
			
		||||
		IsMirror:                        opts.IsMirror,
 | 
			
		||||
		DefaultBranch:                   opts.DefaultBranch,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var rollbackRepo *repo_model.Repository
 | 
			
		||||
 
 | 
			
		||||
@@ -25,6 +25,7 @@ var (
 | 
			
		||||
 | 
			
		||||
		LimitTotalOwnerCount int64
 | 
			
		||||
		LimitTotalOwnerSize  int64
 | 
			
		||||
		LimitSizeCargo       int64
 | 
			
		||||
		LimitSizeComposer    int64
 | 
			
		||||
		LimitSizeConan       int64
 | 
			
		||||
		LimitSizeConda       int64
 | 
			
		||||
@@ -65,6 +66,7 @@ func newPackages() {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
 | 
			
		||||
	Packages.LimitSizeCargo = mustBytes(sec, "LIMIT_SIZE_CARGO")
 | 
			
		||||
	Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER")
 | 
			
		||||
	Packages.LimitSizeConan = mustBytes(sec, "LIMIT_SIZE_CONAN")
 | 
			
		||||
	Packages.LimitSizeConda = mustBytes(sec, "LIMIT_SIZE_CONDA")
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user