mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-25 18:21:26 +02:00 
			
		
		
		
	Related to https://github.com/go-gitea/gitea/pull/22262#discussion_r1059010774 Signed-off-by: Andrew Thornton <art27@cantab.net> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com> Co-authored-by: zeripath <art27@cantab.net> Co-authored-by: Lauris BH <lauris@nix.lv>
		
			
				
	
	
		
			154 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			154 lines
		
	
	
		
			3.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2022 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package pub
 | |
| 
 | |
| import (
 | |
| 	"archive/tar"
 | |
| 	"compress/gzip"
 | |
| 	"io"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"code.gitea.io/gitea/modules/util"
 | |
| 	"code.gitea.io/gitea/modules/validation"
 | |
| 
 | |
| 	"github.com/hashicorp/go-version"
 | |
| 	"gopkg.in/yaml.v3"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	ErrMissingPubspecFile  = util.NewInvalidArgumentErrorf("Pubspec file is missing")
 | |
| 	ErrPubspecFileTooLarge = util.NewInvalidArgumentErrorf("Pubspec file is too large")
 | |
| 	ErrInvalidName         = util.NewInvalidArgumentErrorf("package name is invalid")
 | |
| 	ErrInvalidVersion      = util.NewInvalidArgumentErrorf("package version is invalid")
 | |
| )
 | |
| 
 | |
| var namePattern = regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`)
 | |
| 
 | |
| // https://github.com/dart-lang/pub-dev/blob/4d582302a8d10152a5cd6129f65bf4f4dbca239d/pkg/pub_package_reader/lib/pub_package_reader.dart#L143
 | |
| const maxPubspecFileSize = 128 * 1024
 | |
| 
 | |
| // Package represents a Pub package
 | |
| type Package struct {
 | |
| 	Name     string
 | |
| 	Version  string
 | |
| 	Metadata *Metadata
 | |
| }
 | |
| 
 | |
| // Metadata represents the metadata of a Pub package
 | |
| type Metadata struct {
 | |
| 	Description      string      `json:"description,omitempty"`
 | |
| 	ProjectURL       string      `json:"project_url,omitempty"`
 | |
| 	RepositoryURL    string      `json:"repository_url,omitempty"`
 | |
| 	DocumentationURL string      `json:"documentation_url,omitempty"`
 | |
| 	Readme           string      `json:"readme,omitempty"`
 | |
| 	Pubspec          interface{} `json:"pubspec"`
 | |
| }
 | |
| 
 | |
| type pubspecPackage struct {
 | |
| 	Name          string `yaml:"name"`
 | |
| 	Version       string `yaml:"version"`
 | |
| 	Description   string `yaml:"description"`
 | |
| 	Homepage      string `yaml:"homepage"`
 | |
| 	Repository    string `yaml:"repository"`
 | |
| 	Documentation string `yaml:"documentation"`
 | |
| }
 | |
| 
 | |
| // ParsePackage parses the Pub package file
 | |
| func ParsePackage(r io.Reader) (*Package, error) {
 | |
| 	gzr, err := gzip.NewReader(r)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 	defer gzr.Close()
 | |
| 
 | |
| 	var p *Package
 | |
| 	var readme string
 | |
| 
 | |
| 	tr := tar.NewReader(gzr)
 | |
| 	for {
 | |
| 		hd, err := tr.Next()
 | |
| 		if err == io.EOF {
 | |
| 			break
 | |
| 		}
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		if hd.Typeflag != tar.TypeReg {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if hd.Name == "pubspec.yaml" {
 | |
| 			if hd.Size > maxPubspecFileSize {
 | |
| 				return nil, ErrPubspecFileTooLarge
 | |
| 			}
 | |
| 			p, err = ParsePubspecMetadata(tr)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 		} else if strings.ToLower(hd.Name) == "readme.md" {
 | |
| 			data, err := io.ReadAll(tr)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			readme = string(data)
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if p == nil {
 | |
| 		return nil, ErrMissingPubspecFile
 | |
| 	}
 | |
| 
 | |
| 	p.Metadata.Readme = readme
 | |
| 
 | |
| 	return p, nil
 | |
| }
 | |
| 
 | |
| // ParsePubspecMetadata parses a Pubspec file to retrieve the metadata of a Pub package
 | |
| func ParsePubspecMetadata(r io.Reader) (*Package, error) {
 | |
| 	buf, err := io.ReadAll(io.LimitReader(r, maxPubspecFileSize))
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	var p pubspecPackage
 | |
| 	if err := yaml.Unmarshal(buf, &p); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if !namePattern.MatchString(p.Name) {
 | |
| 		return nil, ErrInvalidName
 | |
| 	}
 | |
| 
 | |
| 	v, err := version.NewSemver(p.Version)
 | |
| 	if err != nil {
 | |
| 		return nil, ErrInvalidVersion
 | |
| 	}
 | |
| 
 | |
| 	if !validation.IsValidURL(p.Homepage) {
 | |
| 		p.Homepage = ""
 | |
| 	}
 | |
| 	if !validation.IsValidURL(p.Repository) {
 | |
| 		p.Repository = ""
 | |
| 	}
 | |
| 
 | |
| 	var pubspec interface{}
 | |
| 	if err := yaml.Unmarshal(buf, &pubspec); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	return &Package{
 | |
| 		Name:    p.Name,
 | |
| 		Version: v.String(),
 | |
| 		Metadata: &Metadata{
 | |
| 			Description:      p.Description,
 | |
| 			ProjectURL:       p.Homepage,
 | |
| 			RepositoryURL:    p.Repository,
 | |
| 			DocumentationURL: p.Documentation,
 | |
| 			Pubspec:          pubspec,
 | |
| 		},
 | |
| 	}, nil
 | |
| }
 |