|  | 
|  | 1 | +// Copyright 2022 The Gitea Authors. All rights reserved. | 
|  | 2 | +// SPDX-License-Identifier: MIT | 
|  | 3 | + | 
|  | 4 | +package cargo | 
|  | 5 | + | 
|  | 6 | +import ( | 
|  | 7 | +	"encoding/binary" | 
|  | 8 | +	"errors" | 
|  | 9 | +	"io" | 
|  | 10 | +	"regexp" | 
|  | 11 | + | 
|  | 12 | +	"code.gitea.io/gitea/modules/json" | 
|  | 13 | +	"code.gitea.io/gitea/modules/validation" | 
|  | 14 | + | 
|  | 15 | +	"github.com/hashicorp/go-version" | 
|  | 16 | +) | 
|  | 17 | + | 
|  | 18 | +const PropertyYanked = "cargo.yanked" | 
|  | 19 | + | 
|  | 20 | +var ( | 
|  | 21 | +	ErrInvalidName    = errors.New("package name is invalid") | 
|  | 22 | +	ErrInvalidVersion = errors.New("package version is invalid") | 
|  | 23 | +) | 
|  | 24 | + | 
|  | 25 | +// Package represents a Cargo package | 
|  | 26 | +type Package struct { | 
|  | 27 | +	Name        string | 
|  | 28 | +	Version     string | 
|  | 29 | +	Metadata    *Metadata | 
|  | 30 | +	Content     io.Reader | 
|  | 31 | +	ContentSize int64 | 
|  | 32 | +} | 
|  | 33 | + | 
|  | 34 | +// Metadata represents the metadata of a Cargo package | 
|  | 35 | +type Metadata struct { | 
|  | 36 | +	Dependencies     []*Dependency       `json:"dependencies,omitempty"` | 
|  | 37 | +	Features         map[string][]string `json:"features,omitempty"` | 
|  | 38 | +	Authors          []string            `json:"authors,omitempty"` | 
|  | 39 | +	Description      string              `json:"description,omitempty"` | 
|  | 40 | +	DocumentationURL string              `json:"documentation_url,omitempty"` | 
|  | 41 | +	ProjectURL       string              `json:"project_url,omitempty"` | 
|  | 42 | +	Readme           string              `json:"readme,omitempty"` | 
|  | 43 | +	Keywords         []string            `json:"keywords,omitempty"` | 
|  | 44 | +	Categories       []string            `json:"categories,omitempty"` | 
|  | 45 | +	License          string              `json:"license,omitempty"` | 
|  | 46 | +	RepositoryURL    string              `json:"repository_url,omitempty"` | 
|  | 47 | +	Links            string              `json:"links,omitempty"` | 
|  | 48 | +} | 
|  | 49 | + | 
|  | 50 | +type Dependency struct { | 
|  | 51 | +	Name            string   `json:"name"` | 
|  | 52 | +	Req             string   `json:"req"` | 
|  | 53 | +	Features        []string `json:"features"` | 
|  | 54 | +	Optional        bool     `json:"optional"` | 
|  | 55 | +	DefaultFeatures bool     `json:"default_features"` | 
|  | 56 | +	Target          *string  `json:"target"` | 
|  | 57 | +	Kind            string   `json:"kind"` | 
|  | 58 | +	Registry        *string  `json:"registry"` | 
|  | 59 | +	Package         *string  `json:"package"` | 
|  | 60 | +} | 
|  | 61 | + | 
|  | 62 | +var nameMatch = regexp.MustCompile(`\A[a-zA-Z][a-zA-Z0-9-_]{0,63}\z`) | 
|  | 63 | + | 
|  | 64 | +// ParsePackage reads the metadata and content of a package | 
|  | 65 | +func ParsePackage(r io.Reader) (*Package, error) { | 
|  | 66 | +	var size uint32 | 
|  | 67 | +	if err := binary.Read(r, binary.LittleEndian, &size); err != nil { | 
|  | 68 | +		return nil, err | 
|  | 69 | +	} | 
|  | 70 | + | 
|  | 71 | +	p, err := parsePackage(io.LimitReader(r, int64(size))) | 
|  | 72 | +	if err != nil { | 
|  | 73 | +		return nil, err | 
|  | 74 | +	} | 
|  | 75 | + | 
|  | 76 | +	if err := binary.Read(r, binary.LittleEndian, &size); err != nil { | 
|  | 77 | +		return nil, err | 
|  | 78 | +	} | 
|  | 79 | + | 
|  | 80 | +	p.Content = io.LimitReader(r, int64(size)) | 
|  | 81 | +	p.ContentSize = int64(size) | 
|  | 82 | + | 
|  | 83 | +	return p, nil | 
|  | 84 | +} | 
|  | 85 | + | 
|  | 86 | +func parsePackage(r io.Reader) (*Package, error) { | 
|  | 87 | +	var meta struct { | 
|  | 88 | +		Name string `json:"name"` | 
|  | 89 | +		Vers string `json:"vers"` | 
|  | 90 | +		Deps []struct { | 
|  | 91 | +			Name               string   `json:"name"` | 
|  | 92 | +			VersionReq         string   `json:"version_req"` | 
|  | 93 | +			Features           []string `json:"features"` | 
|  | 94 | +			Optional           bool     `json:"optional"` | 
|  | 95 | +			DefaultFeatures    bool     `json:"default_features"` | 
|  | 96 | +			Target             *string  `json:"target"` | 
|  | 97 | +			Kind               string   `json:"kind"` | 
|  | 98 | +			Registry           *string  `json:"registry"` | 
|  | 99 | +			ExplicitNameInToml string   `json:"explicit_name_in_toml"` | 
|  | 100 | +		} `json:"deps"` | 
|  | 101 | +		Features      map[string][]string `json:"features"` | 
|  | 102 | +		Authors       []string            `json:"authors"` | 
|  | 103 | +		Description   string              `json:"description"` | 
|  | 104 | +		Documentation string              `json:"documentation"` | 
|  | 105 | +		Homepage      string              `json:"homepage"` | 
|  | 106 | +		Readme        string              `json:"readme"` | 
|  | 107 | +		ReadmeFile    string              `json:"readme_file"` | 
|  | 108 | +		Keywords      []string            `json:"keywords"` | 
|  | 109 | +		Categories    []string            `json:"categories"` | 
|  | 110 | +		License       string              `json:"license"` | 
|  | 111 | +		LicenseFile   string              `json:"license_file"` | 
|  | 112 | +		Repository    string              `json:"repository"` | 
|  | 113 | +		Links         string              `json:"links"` | 
|  | 114 | +	} | 
|  | 115 | +	if err := json.NewDecoder(r).Decode(&meta); err != nil { | 
|  | 116 | +		return nil, err | 
|  | 117 | +	} | 
|  | 118 | + | 
|  | 119 | +	if !nameMatch.MatchString(meta.Name) { | 
|  | 120 | +		return nil, ErrInvalidName | 
|  | 121 | +	} | 
|  | 122 | + | 
|  | 123 | +	if _, err := version.NewSemver(meta.Vers); err != nil { | 
|  | 124 | +		return nil, ErrInvalidVersion | 
|  | 125 | +	} | 
|  | 126 | + | 
|  | 127 | +	if !validation.IsValidURL(meta.Homepage) { | 
|  | 128 | +		meta.Homepage = "" | 
|  | 129 | +	} | 
|  | 130 | +	if !validation.IsValidURL(meta.Documentation) { | 
|  | 131 | +		meta.Documentation = "" | 
|  | 132 | +	} | 
|  | 133 | +	if !validation.IsValidURL(meta.Repository) { | 
|  | 134 | +		meta.Repository = "" | 
|  | 135 | +	} | 
|  | 136 | + | 
|  | 137 | +	dependencies := make([]*Dependency, 0, len(meta.Deps)) | 
|  | 138 | +	for _, dep := range meta.Deps { | 
|  | 139 | +		dependencies = append(dependencies, &Dependency{ | 
|  | 140 | +			Name:            dep.Name, | 
|  | 141 | +			Req:             dep.VersionReq, | 
|  | 142 | +			Features:        dep.Features, | 
|  | 143 | +			Optional:        dep.Optional, | 
|  | 144 | +			DefaultFeatures: dep.DefaultFeatures, | 
|  | 145 | +			Target:          dep.Target, | 
|  | 146 | +			Kind:            dep.Kind, | 
|  | 147 | +			Registry:        dep.Registry, | 
|  | 148 | +		}) | 
|  | 149 | +	} | 
|  | 150 | + | 
|  | 151 | +	return &Package{ | 
|  | 152 | +		Name:    meta.Name, | 
|  | 153 | +		Version: meta.Vers, | 
|  | 154 | +		Metadata: &Metadata{ | 
|  | 155 | +			Dependencies:     dependencies, | 
|  | 156 | +			Features:         meta.Features, | 
|  | 157 | +			Authors:          meta.Authors, | 
|  | 158 | +			Description:      meta.Description, | 
|  | 159 | +			DocumentationURL: meta.Documentation, | 
|  | 160 | +			ProjectURL:       meta.Homepage, | 
|  | 161 | +			Readme:           meta.Readme, | 
|  | 162 | +			Keywords:         meta.Keywords, | 
|  | 163 | +			Categories:       meta.Categories, | 
|  | 164 | +			License:          meta.License, | 
|  | 165 | +			RepositoryURL:    meta.Repository, | 
|  | 166 | +			Links:            meta.Links, | 
|  | 167 | +		}, | 
|  | 168 | +	}, nil | 
|  | 169 | +} | 
0 commit comments