mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 19:06:18 +01:00 
			
		
		
		
	Fixes #35159 Swift Package Manager expects an 'author.name' field in package metadata, but Gitea was only providing schema.org format fields (givenName, middleName, familyName). This caused SPM to fail with keyNotFound error when fetching package metadata. Changes: - Add 'name' field to Person struct (inherited from https://schema.org/Thing) - Populate 'name' field in API response using existing String() method - Maintains backward compatibility with existing schema.org fields - Provides both formats for maximum compatibility The fix ensures Swift Package Manager can successfully resolve packages while preserving full schema.org compliance.
		
			
				
	
	
		
			222 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			222 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2023 The Gitea Authors. All rights reserved.
 | |
| // SPDX-License-Identifier: MIT
 | |
| 
 | |
| package swift
 | |
| 
 | |
| import (
 | |
| 	"archive/zip"
 | |
| 	"fmt"
 | |
| 	"io"
 | |
| 	"path"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"code.gitea.io/gitea/modules/json"
 | |
| 	"code.gitea.io/gitea/modules/util"
 | |
| 	"code.gitea.io/gitea/modules/validation"
 | |
| 
 | |
| 	"github.com/hashicorp/go-version"
 | |
| )
 | |
| 
 | |
| var (
 | |
| 	ErrMissingManifestFile    = util.NewInvalidArgumentErrorf("Package.swift file is missing")
 | |
| 	ErrManifestFileTooLarge   = util.NewInvalidArgumentErrorf("Package.swift file is too large")
 | |
| 	ErrInvalidManifestVersion = util.NewInvalidArgumentErrorf("manifest version is invalid")
 | |
| 
 | |
| 	manifestPattern     = regexp.MustCompile(`\APackage(?:@swift-(\d+(?:\.\d+)?(?:\.\d+)?))?\.swift\z`)
 | |
| 	toolsVersionPattern = regexp.MustCompile(`\A// swift-tools-version:(\d+(?:\.\d+)?(?:\.\d+)?)`)
 | |
| )
 | |
| 
 | |
| const (
 | |
| 	maxManifestFileSize = 128 * 1024
 | |
| 
 | |
| 	PropertyScope         = "swift.scope"
 | |
| 	PropertyName          = "swift.name"
 | |
| 	PropertyRepositoryURL = "swift.repository_url"
 | |
| )
 | |
| 
 | |
| // Package represents a Swift package
 | |
| type Package struct {
 | |
| 	RepositoryURLs []string
 | |
| 	Metadata       *Metadata
 | |
| }
 | |
| 
 | |
| // Metadata represents the metadata of a Swift package
 | |
| type Metadata struct {
 | |
| 	Description   string               `json:"description,omitempty"`
 | |
| 	Keywords      []string             `json:"keywords,omitempty"`
 | |
| 	RepositoryURL string               `json:"repository_url,omitempty"`
 | |
| 	License       string               `json:"license,omitempty"`
 | |
| 	Author        Person               `json:"author"`
 | |
| 	Manifests     map[string]*Manifest `json:"manifests,omitempty"`
 | |
| }
 | |
| 
 | |
| // Manifest represents a Package.swift file
 | |
| type Manifest struct {
 | |
| 	Content      string `json:"content"`
 | |
| 	ToolsVersion string `json:"tools_version,omitempty"`
 | |
| }
 | |
| 
 | |
| // https://schema.org/SoftwareSourceCode
 | |
| type SoftwareSourceCode struct {
 | |
| 	Context             []string            `json:"@context"`
 | |
| 	Type                string              `json:"@type"`
 | |
| 	Name                string              `json:"name"`
 | |
| 	Version             string              `json:"version"`
 | |
| 	Description         string              `json:"description,omitempty"`
 | |
| 	Keywords            []string            `json:"keywords,omitempty"`
 | |
| 	CodeRepository      string              `json:"codeRepository,omitempty"`
 | |
| 	License             string              `json:"license,omitempty"`
 | |
| 	Author              Person              `json:"author"`
 | |
| 	ProgrammingLanguage ProgrammingLanguage `json:"programmingLanguage"`
 | |
| 	RepositoryURLs      []string            `json:"repositoryURLs,omitempty"`
 | |
| }
 | |
| 
 | |
| // https://schema.org/ProgrammingLanguage
 | |
| type ProgrammingLanguage struct {
 | |
| 	Type string `json:"@type"`
 | |
| 	Name string `json:"name"`
 | |
| 	URL  string `json:"url"`
 | |
| }
 | |
| 
 | |
| // https://schema.org/Person
 | |
| type Person struct {
 | |
| 	Type       string `json:"@type,omitempty"`
 | |
| 	Name       string `json:"name,omitempty"` // inherited from https://schema.org/Thing
 | |
| 	GivenName  string `json:"givenName,omitempty"`
 | |
| 	MiddleName string `json:"middleName,omitempty"`
 | |
| 	FamilyName string `json:"familyName,omitempty"`
 | |
| }
 | |
| 
 | |
| func (p Person) String() string {
 | |
| 	var sb strings.Builder
 | |
| 	if p.GivenName != "" {
 | |
| 		sb.WriteString(p.GivenName)
 | |
| 	}
 | |
| 	if p.MiddleName != "" {
 | |
| 		if sb.Len() > 0 {
 | |
| 			sb.WriteRune(' ')
 | |
| 		}
 | |
| 		sb.WriteString(p.MiddleName)
 | |
| 	}
 | |
| 	if p.FamilyName != "" {
 | |
| 		if sb.Len() > 0 {
 | |
| 			sb.WriteRune(' ')
 | |
| 		}
 | |
| 		sb.WriteString(p.FamilyName)
 | |
| 	}
 | |
| 	return sb.String()
 | |
| }
 | |
| 
 | |
| // ParsePackage parses the Swift package upload
 | |
| func ParsePackage(sr io.ReaderAt, size int64, mr io.Reader) (*Package, error) {
 | |
| 	zr, err := zip.NewReader(sr, size)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	p := &Package{
 | |
| 		Metadata: &Metadata{
 | |
| 			Manifests: make(map[string]*Manifest),
 | |
| 		},
 | |
| 	}
 | |
| 
 | |
| 	for _, file := range zr.File {
 | |
| 		manifestMatch := manifestPattern.FindStringSubmatch(path.Base(file.Name))
 | |
| 		if len(manifestMatch) == 0 {
 | |
| 			continue
 | |
| 		}
 | |
| 
 | |
| 		if file.UncompressedSize64 > maxManifestFileSize {
 | |
| 			return nil, ErrManifestFileTooLarge
 | |
| 		}
 | |
| 
 | |
| 		f, err := zr.Open(file.Name)
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		content, err := io.ReadAll(f)
 | |
| 
 | |
| 		if err := f.Close(); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		if err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		swiftVersion := ""
 | |
| 		if len(manifestMatch) == 2 && manifestMatch[1] != "" {
 | |
| 			v, err := version.NewSemver(manifestMatch[1])
 | |
| 			if err != nil {
 | |
| 				return nil, ErrInvalidManifestVersion
 | |
| 			}
 | |
| 			swiftVersion = TrimmedVersionString(v)
 | |
| 		}
 | |
| 
 | |
| 		manifest := &Manifest{
 | |
| 			Content: string(content),
 | |
| 		}
 | |
| 
 | |
| 		toolsMatch := toolsVersionPattern.FindStringSubmatch(manifest.Content)
 | |
| 		if len(toolsMatch) == 2 {
 | |
| 			v, err := version.NewSemver(toolsMatch[1])
 | |
| 			if err != nil {
 | |
| 				return nil, ErrInvalidManifestVersion
 | |
| 			}
 | |
| 
 | |
| 			manifest.ToolsVersion = TrimmedVersionString(v)
 | |
| 		}
 | |
| 
 | |
| 		p.Metadata.Manifests[swiftVersion] = manifest
 | |
| 	}
 | |
| 
 | |
| 	if _, found := p.Metadata.Manifests[""]; !found {
 | |
| 		return nil, ErrMissingManifestFile
 | |
| 	}
 | |
| 
 | |
| 	if mr != nil {
 | |
| 		var ssc *SoftwareSourceCode
 | |
| 		if err := json.NewDecoder(mr).Decode(&ssc); err != nil {
 | |
| 			return nil, err
 | |
| 		}
 | |
| 
 | |
| 		p.Metadata.Description = ssc.Description
 | |
| 		p.Metadata.Keywords = ssc.Keywords
 | |
| 		p.Metadata.License = ssc.License
 | |
| 		author := Person{
 | |
| 			Name:       ssc.Author.Name,
 | |
| 			GivenName:  ssc.Author.GivenName,
 | |
| 			MiddleName: ssc.Author.MiddleName,
 | |
| 			FamilyName: ssc.Author.FamilyName,
 | |
| 		}
 | |
| 		// If Name is not provided, generate it from individual name components
 | |
| 		if author.Name == "" {
 | |
| 			author.Name = author.String()
 | |
| 		}
 | |
| 		p.Metadata.Author = author
 | |
| 
 | |
| 		p.Metadata.RepositoryURL = ssc.CodeRepository
 | |
| 		if !validation.IsValidURL(p.Metadata.RepositoryURL) {
 | |
| 			p.Metadata.RepositoryURL = ""
 | |
| 		}
 | |
| 
 | |
| 		p.RepositoryURLs = ssc.RepositoryURLs
 | |
| 	}
 | |
| 
 | |
| 	return p, nil
 | |
| }
 | |
| 
 | |
| // TrimmedVersionString returns the version string without the patch segment if it is zero
 | |
| func TrimmedVersionString(v *version.Version) string {
 | |
| 	segments := v.Segments64()
 | |
| 
 | |
| 	var b strings.Builder
 | |
| 	fmt.Fprintf(&b, "%d.%d", segments[0], segments[1])
 | |
| 	if segments[2] != 0 {
 | |
| 		fmt.Fprintf(&b, ".%d", segments[2])
 | |
| 	}
 | |
| 	return b.String()
 | |
| }
 |