mirror of
				https://github.com/go-gitea/gitea
				synced 2025-11-03 12:58:29 +00:00 
			
		
		
		
	https://github.com/go-gitea/gitea/pull/26194#discussion_r1277094525 There is no need to backport because these names are just used internal.
		
			
				
	
	
		
			244 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			244 lines
		
	
	
		
			6.0 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
						|
// SPDX-License-Identifier: MIT
 | 
						|
 | 
						|
package conda
 | 
						|
 | 
						|
import (
 | 
						|
	"archive/tar"
 | 
						|
	"archive/zip"
 | 
						|
	"compress/bzip2"
 | 
						|
	"io"
 | 
						|
	"strings"
 | 
						|
 | 
						|
	"code.gitea.io/gitea/modules/json"
 | 
						|
	"code.gitea.io/gitea/modules/util"
 | 
						|
	"code.gitea.io/gitea/modules/validation"
 | 
						|
 | 
						|
	"github.com/klauspost/compress/zstd"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	ErrInvalidStructure = util.SilentWrap{Message: "package structure is invalid", Err: util.ErrInvalidArgument}
 | 
						|
	ErrInvalidName      = util.SilentWrap{Message: "package name is invalid", Err: util.ErrInvalidArgument}
 | 
						|
	ErrInvalidVersion   = util.SilentWrap{Message: "package version is invalid", Err: util.ErrInvalidArgument}
 | 
						|
)
 | 
						|
 | 
						|
const (
 | 
						|
	PropertyName     = "conda.name"
 | 
						|
	PropertyChannel  = "conda.channel"
 | 
						|
	PropertySubdir   = "conda.subdir"
 | 
						|
	PropertyMetadata = "conda.metadata"
 | 
						|
)
 | 
						|
 | 
						|
// Package represents a Conda package
 | 
						|
type Package struct {
 | 
						|
	Name            string
 | 
						|
	Version         string
 | 
						|
	Subdir          string
 | 
						|
	VersionMetadata *VersionMetadata
 | 
						|
	FileMetadata    *FileMetadata
 | 
						|
}
 | 
						|
 | 
						|
// VersionMetadata represents the metadata of a Conda package
 | 
						|
type VersionMetadata struct {
 | 
						|
	Description      string `json:"description,omitempty"`
 | 
						|
	Summary          string `json:"summary,omitempty"`
 | 
						|
	ProjectURL       string `json:"project_url,omitempty"`
 | 
						|
	RepositoryURL    string `json:"repository_url,omitempty"`
 | 
						|
	DocumentationURL string `json:"documentation_url,omitempty"`
 | 
						|
	License          string `json:"license,omitempty"`
 | 
						|
	LicenseFamily    string `json:"license_family,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
// FileMetadata represents the metadata of a Conda package file
 | 
						|
type FileMetadata struct {
 | 
						|
	IsCondaPackage bool     `json:"is_conda"`
 | 
						|
	Architecture   string   `json:"architecture,omitempty"`
 | 
						|
	NoArch         string   `json:"noarch,omitempty"`
 | 
						|
	Build          string   `json:"build,omitempty"`
 | 
						|
	BuildNumber    int64    `json:"build_number,omitempty"`
 | 
						|
	Dependencies   []string `json:"dependencies,omitempty"`
 | 
						|
	Platform       string   `json:"platform,omitempty"`
 | 
						|
	Timestamp      int64    `json:"timestamp,omitempty"`
 | 
						|
}
 | 
						|
 | 
						|
type index struct {
 | 
						|
	Name          string   `json:"name"`
 | 
						|
	Version       string   `json:"version"`
 | 
						|
	Architecture  string   `json:"arch"`
 | 
						|
	NoArch        string   `json:"noarch"`
 | 
						|
	Build         string   `json:"build"`
 | 
						|
	BuildNumber   int64    `json:"build_number"`
 | 
						|
	Dependencies  []string `json:"depends"`
 | 
						|
	License       string   `json:"license"`
 | 
						|
	LicenseFamily string   `json:"license_family"`
 | 
						|
	Platform      string   `json:"platform"`
 | 
						|
	Subdir        string   `json:"subdir"`
 | 
						|
	Timestamp     int64    `json:"timestamp"`
 | 
						|
}
 | 
						|
 | 
						|
type about struct {
 | 
						|
	Description      string `json:"description"`
 | 
						|
	Summary          string `json:"summary"`
 | 
						|
	ProjectURL       string `json:"home"`
 | 
						|
	RepositoryURL    string `json:"dev_url"`
 | 
						|
	DocumentationURL string `json:"doc_url"`
 | 
						|
}
 | 
						|
 | 
						|
type ReaderAndReaderAt interface {
 | 
						|
	io.Reader
 | 
						|
	io.ReaderAt
 | 
						|
}
 | 
						|
 | 
						|
// ParsePackageBZ2 parses the Conda package file compressed with bzip2
 | 
						|
func ParsePackageBZ2(r io.Reader) (*Package, error) {
 | 
						|
	gzr := bzip2.NewReader(r)
 | 
						|
 | 
						|
	return parsePackageTar(gzr)
 | 
						|
}
 | 
						|
 | 
						|
// ParsePackageConda parses the Conda package file compressed with zip and zstd
 | 
						|
func ParsePackageConda(r io.ReaderAt, size int64) (*Package, error) {
 | 
						|
	zr, err := zip.NewReader(r, size)
 | 
						|
	if err != nil {
 | 
						|
		return nil, err
 | 
						|
	}
 | 
						|
 | 
						|
	for _, file := range zr.File {
 | 
						|
		if strings.HasPrefix(file.Name, "info-") && strings.HasSuffix(file.Name, ".tar.zst") {
 | 
						|
			f, err := zr.Open(file.Name)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			defer f.Close()
 | 
						|
 | 
						|
			dec, err := zstd.NewReader(f)
 | 
						|
			if err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
			defer dec.Close()
 | 
						|
 | 
						|
			p, err := parsePackageTar(dec)
 | 
						|
			if p != nil {
 | 
						|
				p.FileMetadata.IsCondaPackage = true
 | 
						|
			}
 | 
						|
			return p, err
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return nil, ErrInvalidStructure
 | 
						|
}
 | 
						|
 | 
						|
func parsePackageTar(r io.Reader) (*Package, error) {
 | 
						|
	var i *index
 | 
						|
	var a *about
 | 
						|
 | 
						|
	tr := tar.NewReader(r)
 | 
						|
	for {
 | 
						|
		hdr, err := tr.Next()
 | 
						|
		if err == io.EOF {
 | 
						|
			break
 | 
						|
		}
 | 
						|
		if err != nil {
 | 
						|
			return nil, err
 | 
						|
		}
 | 
						|
 | 
						|
		if hdr.Typeflag != tar.TypeReg {
 | 
						|
			continue
 | 
						|
		}
 | 
						|
 | 
						|
		if hdr.Name == "info/index.json" {
 | 
						|
			if err := json.NewDecoder(tr).Decode(&i); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
 | 
						|
			if !checkName(i.Name) {
 | 
						|
				return nil, ErrInvalidName
 | 
						|
			}
 | 
						|
 | 
						|
			if !checkVersion(i.Version) {
 | 
						|
				return nil, ErrInvalidVersion
 | 
						|
			}
 | 
						|
 | 
						|
			if a != nil {
 | 
						|
				break // stop loop if both files were found
 | 
						|
			}
 | 
						|
		} else if hdr.Name == "info/about.json" {
 | 
						|
			if err := json.NewDecoder(tr).Decode(&a); err != nil {
 | 
						|
				return nil, err
 | 
						|
			}
 | 
						|
 | 
						|
			if !validation.IsValidURL(a.ProjectURL) {
 | 
						|
				a.ProjectURL = ""
 | 
						|
			}
 | 
						|
			if !validation.IsValidURL(a.RepositoryURL) {
 | 
						|
				a.RepositoryURL = ""
 | 
						|
			}
 | 
						|
			if !validation.IsValidURL(a.DocumentationURL) {
 | 
						|
				a.DocumentationURL = ""
 | 
						|
			}
 | 
						|
 | 
						|
			if i != nil {
 | 
						|
				break // stop loop if both files were found
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if i == nil {
 | 
						|
		return nil, ErrInvalidStructure
 | 
						|
	}
 | 
						|
	if a == nil {
 | 
						|
		a = &about{}
 | 
						|
	}
 | 
						|
 | 
						|
	return &Package{
 | 
						|
		Name:    i.Name,
 | 
						|
		Version: i.Version,
 | 
						|
		Subdir:  i.Subdir,
 | 
						|
		VersionMetadata: &VersionMetadata{
 | 
						|
			License:          i.License,
 | 
						|
			LicenseFamily:    i.LicenseFamily,
 | 
						|
			Description:      a.Description,
 | 
						|
			Summary:          a.Summary,
 | 
						|
			ProjectURL:       a.ProjectURL,
 | 
						|
			RepositoryURL:    a.RepositoryURL,
 | 
						|
			DocumentationURL: a.DocumentationURL,
 | 
						|
		},
 | 
						|
		FileMetadata: &FileMetadata{
 | 
						|
			Architecture: i.Architecture,
 | 
						|
			NoArch:       i.NoArch,
 | 
						|
			Build:        i.Build,
 | 
						|
			BuildNumber:  i.BuildNumber,
 | 
						|
			Dependencies: i.Dependencies,
 | 
						|
			Platform:     i.Platform,
 | 
						|
			Timestamp:    i.Timestamp,
 | 
						|
		},
 | 
						|
	}, nil
 | 
						|
}
 | 
						|
 | 
						|
// https://github.com/conda/conda-build/blob/db9a728a9e4e6cfc895637ca3221117970fc2663/conda_build/metadata.py#L1393
 | 
						|
func checkName(name string) bool {
 | 
						|
	if name == "" {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	if name != strings.ToLower(name) {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return !checkBadCharacters(name, "!")
 | 
						|
}
 | 
						|
 | 
						|
// https://github.com/conda/conda-build/blob/db9a728a9e4e6cfc895637ca3221117970fc2663/conda_build/metadata.py#L1403
 | 
						|
func checkVersion(version string) bool {
 | 
						|
	if version == "" {
 | 
						|
		return false
 | 
						|
	}
 | 
						|
	return !checkBadCharacters(version, "-")
 | 
						|
}
 | 
						|
 | 
						|
func checkBadCharacters(s, additional string) bool {
 | 
						|
	if strings.ContainsAny(s, "=@#$%^&*:;\"'\\|<>?/ ") {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	return strings.ContainsAny(s, additional)
 | 
						|
}
 |