mirror of
				https://codeberg.org/forgejo/forgejo.git
				synced 2025-10-27 20:41:01 +00:00 
			
		
		
		
	- Massive replacement of changing `code.gitea.io/gitea` to `forgejo.org`. - Resolves forgejo/discussions#258 Reviewed-on: https://codeberg.org/forgejo/forgejo/pulls/7337 Reviewed-by: Earl Warren <earl-warren@noreply.codeberg.org> Reviewed-by: Michael Kriese <michael.kriese@gmx.de> Reviewed-by: Beowulf <beowulf@beocode.eu> Reviewed-by: Panagiotis "Ivory" Vasilopoulos <git@n0toose.net> Co-authored-by: Gusted <postmaster@gusted.xyz> Co-committed-by: Gusted <postmaster@gusted.xyz>
		
			
				
	
	
		
			242 lines
		
	
	
	
		
			6 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			242 lines
		
	
	
	
		
			6 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"
 | |
| 
 | |
| 	"forgejo.org/modules/json"
 | |
| 	"forgejo.org/modules/util"
 | |
| 	"forgejo.org/modules/validation"
 | |
| 	"forgejo.org/modules/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)
 | |
| }
 |