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 }