148 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			148 lines
		
	
	
		
			3.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| // Copyright 2021 The Gitea Authors. All rights reserved.
 | |
| // Use of this source code is governed by a MIT-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| package composer
 | |
| 
 | |
| import (
 | |
| 	"archive/zip"
 | |
| 	"errors"
 | |
| 	"io"
 | |
| 	"regexp"
 | |
| 	"strings"
 | |
| 
 | |
| 	"code.gitea.io/gitea/modules/json"
 | |
| 	"code.gitea.io/gitea/modules/validation"
 | |
| 
 | |
| 	"github.com/hashicorp/go-version"
 | |
| )
 | |
| 
 | |
| // TypeProperty is the name of the property for Composer package types
 | |
| const TypeProperty = "composer.type"
 | |
| 
 | |
| var (
 | |
| 	// ErrMissingComposerFile indicates a missing composer.json file
 | |
| 	ErrMissingComposerFile = errors.New("composer.json file is missing")
 | |
| 	// ErrInvalidName indicates an invalid package name
 | |
| 	ErrInvalidName = errors.New("package name is invalid")
 | |
| 	// ErrInvalidVersion indicates an invalid package version
 | |
| 	ErrInvalidVersion = errors.New("package version is invalid")
 | |
| )
 | |
| 
 | |
| // Package represents a Composer package
 | |
| type Package struct {
 | |
| 	Name     string
 | |
| 	Version  string
 | |
| 	Type     string
 | |
| 	Metadata *Metadata
 | |
| }
 | |
| 
 | |
| // Metadata represents the metadata of a Composer package
 | |
| type Metadata struct {
 | |
| 	Description string                 `json:"description,omitempty"`
 | |
| 	Keywords    []string               `json:"keywords,omitempty"`
 | |
| 	Homepage    string                 `json:"homepage,omitempty"`
 | |
| 	License     Licenses               `json:"license,omitempty"`
 | |
| 	Authors     []Author               `json:"authors,omitempty"`
 | |
| 	Autoload    map[string]interface{} `json:"autoload,omitempty"`
 | |
| 	AutoloadDev map[string]interface{} `json:"autoload-dev,omitempty"`
 | |
| 	Extra       map[string]interface{} `json:"extra,omitempty"`
 | |
| 	Require     map[string]string      `json:"require,omitempty"`
 | |
| 	RequireDev  map[string]string      `json:"require-dev,omitempty"`
 | |
| 	Suggest     map[string]string      `json:"suggest,omitempty"`
 | |
| 	Provide     map[string]string      `json:"provide,omitempty"`
 | |
| }
 | |
| 
 | |
| // Licenses represents the licenses of a Composer package
 | |
| type Licenses []string
 | |
| 
 | |
| // UnmarshalJSON reads from a string or array
 | |
| func (l *Licenses) UnmarshalJSON(data []byte) error {
 | |
| 	switch data[0] {
 | |
| 	case '"':
 | |
| 		var value string
 | |
| 		if err := json.Unmarshal(data, &value); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		*l = Licenses{value}
 | |
| 	case '[':
 | |
| 		values := make([]string, 0, 5)
 | |
| 		if err := json.Unmarshal(data, &values); err != nil {
 | |
| 			return err
 | |
| 		}
 | |
| 		*l = Licenses(values)
 | |
| 	}
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| // Author represents an author
 | |
| type Author struct {
 | |
| 	Name     string `json:"name,omitempty"`
 | |
| 	Email    string `json:"email,omitempty"`
 | |
| 	Homepage string `json:"homepage,omitempty"`
 | |
| }
 | |
| 
 | |
| var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`)
 | |
| 
 | |
| // ParsePackage parses the metadata of a Composer package file
 | |
| func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
 | |
| 	archive, err := zip.NewReader(r, size)
 | |
| 	if err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	for _, file := range archive.File {
 | |
| 		if strings.Count(file.Name, "/") > 1 {
 | |
| 			continue
 | |
| 		}
 | |
| 		if strings.HasSuffix(strings.ToLower(file.Name), "composer.json") {
 | |
| 			f, err := archive.Open(file.Name)
 | |
| 			if err != nil {
 | |
| 				return nil, err
 | |
| 			}
 | |
| 			defer f.Close()
 | |
| 
 | |
| 			return ParseComposerFile(f)
 | |
| 		}
 | |
| 	}
 | |
| 	return nil, ErrMissingComposerFile
 | |
| }
 | |
| 
 | |
| // ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
 | |
| func ParseComposerFile(r io.Reader) (*Package, error) {
 | |
| 	var cj struct {
 | |
| 		Name    string `json:"name"`
 | |
| 		Version string `json:"version"`
 | |
| 		Type    string `json:"type"`
 | |
| 		Metadata
 | |
| 	}
 | |
| 	if err := json.NewDecoder(r).Decode(&cj); err != nil {
 | |
| 		return nil, err
 | |
| 	}
 | |
| 
 | |
| 	if !nameMatch.MatchString(cj.Name) {
 | |
| 		return nil, ErrInvalidName
 | |
| 	}
 | |
| 
 | |
| 	if cj.Version != "" {
 | |
| 		if _, err := version.NewSemver(cj.Version); err != nil {
 | |
| 			return nil, ErrInvalidVersion
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if !validation.IsValidURL(cj.Homepage) {
 | |
| 		cj.Homepage = ""
 | |
| 	}
 | |
| 
 | |
| 	if cj.Type == "" {
 | |
| 		cj.Type = "library"
 | |
| 	}
 | |
| 
 | |
| 	return &Package{
 | |
| 		Name:     cj.Name,
 | |
| 		Version:  cj.Version,
 | |
| 		Type:     cj.Type,
 | |
| 		Metadata: &cj.Metadata,
 | |
| 	}, nil
 | |
| }
 |