code.gitea.io/gitea@v1.19.3/modules/packages/pub/metadata.go (about)

     1  // Copyright 2022 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package pub
     5  
     6  import (
     7  	"archive/tar"
     8  	"compress/gzip"
     9  	"io"
    10  	"regexp"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/modules/util"
    14  	"code.gitea.io/gitea/modules/validation"
    15  
    16  	"github.com/hashicorp/go-version"
    17  	"gopkg.in/yaml.v3"
    18  )
    19  
    20  var (
    21  	ErrMissingPubspecFile  = util.NewInvalidArgumentErrorf("Pubspec file is missing")
    22  	ErrPubspecFileTooLarge = util.NewInvalidArgumentErrorf("Pubspec file is too large")
    23  	ErrInvalidName         = util.NewInvalidArgumentErrorf("package name is invalid")
    24  	ErrInvalidVersion      = util.NewInvalidArgumentErrorf("package version is invalid")
    25  )
    26  
    27  var namePattern = regexp.MustCompile(`\A[a-zA-Z_][a-zA-Z0-9_]*\z`)
    28  
    29  // https://github.com/dart-lang/pub-dev/blob/4d582302a8d10152a5cd6129f65bf4f4dbca239d/pkg/pub_package_reader/lib/pub_package_reader.dart#L143
    30  const maxPubspecFileSize = 128 * 1024
    31  
    32  // Package represents a Pub package
    33  type Package struct {
    34  	Name     string
    35  	Version  string
    36  	Metadata *Metadata
    37  }
    38  
    39  // Metadata represents the metadata of a Pub package
    40  type Metadata struct {
    41  	Description      string      `json:"description,omitempty"`
    42  	ProjectURL       string      `json:"project_url,omitempty"`
    43  	RepositoryURL    string      `json:"repository_url,omitempty"`
    44  	DocumentationURL string      `json:"documentation_url,omitempty"`
    45  	Readme           string      `json:"readme,omitempty"`
    46  	Pubspec          interface{} `json:"pubspec"`
    47  }
    48  
    49  type pubspecPackage struct {
    50  	Name          string `yaml:"name"`
    51  	Version       string `yaml:"version"`
    52  	Description   string `yaml:"description"`
    53  	Homepage      string `yaml:"homepage"`
    54  	Repository    string `yaml:"repository"`
    55  	Documentation string `yaml:"documentation"`
    56  }
    57  
    58  // ParsePackage parses the Pub package file
    59  func ParsePackage(r io.Reader) (*Package, error) {
    60  	gzr, err := gzip.NewReader(r)
    61  	if err != nil {
    62  		return nil, err
    63  	}
    64  	defer gzr.Close()
    65  
    66  	var p *Package
    67  	var readme string
    68  
    69  	tr := tar.NewReader(gzr)
    70  	for {
    71  		hd, err := tr.Next()
    72  		if err == io.EOF {
    73  			break
    74  		}
    75  		if err != nil {
    76  			return nil, err
    77  		}
    78  
    79  		if hd.Typeflag != tar.TypeReg {
    80  			continue
    81  		}
    82  
    83  		if hd.Name == "pubspec.yaml" {
    84  			if hd.Size > maxPubspecFileSize {
    85  				return nil, ErrPubspecFileTooLarge
    86  			}
    87  			p, err = ParsePubspecMetadata(tr)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  		} else if strings.ToLower(hd.Name) == "readme.md" {
    92  			data, err := io.ReadAll(tr)
    93  			if err != nil {
    94  				return nil, err
    95  			}
    96  			readme = string(data)
    97  		}
    98  	}
    99  
   100  	if p == nil {
   101  		return nil, ErrMissingPubspecFile
   102  	}
   103  
   104  	p.Metadata.Readme = readme
   105  
   106  	return p, nil
   107  }
   108  
   109  // ParsePubspecMetadata parses a Pubspec file to retrieve the metadata of a Pub package
   110  func ParsePubspecMetadata(r io.Reader) (*Package, error) {
   111  	buf, err := io.ReadAll(io.LimitReader(r, maxPubspecFileSize))
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  
   116  	var p pubspecPackage
   117  	if err := yaml.Unmarshal(buf, &p); err != nil {
   118  		return nil, err
   119  	}
   120  
   121  	if !namePattern.MatchString(p.Name) {
   122  		return nil, ErrInvalidName
   123  	}
   124  
   125  	v, err := version.NewSemver(p.Version)
   126  	if err != nil {
   127  		return nil, ErrInvalidVersion
   128  	}
   129  
   130  	if !validation.IsValidURL(p.Homepage) {
   131  		p.Homepage = ""
   132  	}
   133  	if !validation.IsValidURL(p.Repository) {
   134  		p.Repository = ""
   135  	}
   136  
   137  	var pubspec interface{}
   138  	if err := yaml.Unmarshal(buf, &pubspec); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return &Package{
   143  		Name:    p.Name,
   144  		Version: v.String(),
   145  		Metadata: &Metadata{
   146  			Description:      p.Description,
   147  			ProjectURL:       p.Homepage,
   148  			RepositoryURL:    p.Repository,
   149  			DocumentationURL: p.Documentation,
   150  			Pubspec:          pubspec,
   151  		},
   152  	}, nil
   153  }