code.gitea.io/gitea@v1.22.3/modules/packages/composer/metadata.go (about) 1 // Copyright 2021 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package composer 5 6 import ( 7 "archive/zip" 8 "io" 9 "regexp" 10 "strings" 11 12 "code.gitea.io/gitea/modules/json" 13 "code.gitea.io/gitea/modules/util" 14 "code.gitea.io/gitea/modules/validation" 15 16 "github.com/hashicorp/go-version" 17 ) 18 19 // TypeProperty is the name of the property for Composer package types 20 const TypeProperty = "composer.type" 21 22 var ( 23 // ErrMissingComposerFile indicates a missing composer.json file 24 ErrMissingComposerFile = util.NewInvalidArgumentErrorf("composer.json file is missing") 25 // ErrInvalidName indicates an invalid package name 26 ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") 27 // ErrInvalidVersion indicates an invalid package version 28 ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") 29 ) 30 31 // Package represents a Composer package 32 type Package struct { 33 Name string 34 Version string 35 Type string 36 Metadata *Metadata 37 } 38 39 // Metadata represents the metadata of a Composer package 40 type Metadata struct { 41 Description string `json:"description,omitempty"` 42 Keywords []string `json:"keywords,omitempty"` 43 Homepage string `json:"homepage,omitempty"` 44 License Licenses `json:"license,omitempty"` 45 Authors []Author `json:"authors,omitempty"` 46 Bin []string `json:"bin,omitempty"` 47 Autoload map[string]any `json:"autoload,omitempty"` 48 AutoloadDev map[string]any `json:"autoload-dev,omitempty"` 49 Extra map[string]any `json:"extra,omitempty"` 50 Require map[string]string `json:"require,omitempty"` 51 RequireDev map[string]string `json:"require-dev,omitempty"` 52 Suggest map[string]string `json:"suggest,omitempty"` 53 Provide map[string]string `json:"provide,omitempty"` 54 } 55 56 // Licenses represents the licenses of a Composer package 57 type Licenses []string 58 59 // UnmarshalJSON reads from a string or array 60 func (l *Licenses) UnmarshalJSON(data []byte) error { 61 switch data[0] { 62 case '"': 63 var value string 64 if err := json.Unmarshal(data, &value); err != nil { 65 return err 66 } 67 *l = Licenses{value} 68 case '[': 69 values := make([]string, 0, 5) 70 if err := json.Unmarshal(data, &values); err != nil { 71 return err 72 } 73 *l = Licenses(values) 74 } 75 return nil 76 } 77 78 // Author represents an author 79 type Author struct { 80 Name string `json:"name,omitempty"` 81 Email string `json:"email,omitempty"` 82 Homepage string `json:"homepage,omitempty"` 83 } 84 85 var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`) 86 87 // ParsePackage parses the metadata of a Composer package file 88 func ParsePackage(r io.ReaderAt, size int64) (*Package, error) { 89 archive, err := zip.NewReader(r, size) 90 if err != nil { 91 return nil, err 92 } 93 94 for _, file := range archive.File { 95 if strings.Count(file.Name, "/") > 1 { 96 continue 97 } 98 if strings.HasSuffix(strings.ToLower(file.Name), "composer.json") { 99 f, err := archive.Open(file.Name) 100 if err != nil { 101 return nil, err 102 } 103 defer f.Close() 104 105 return ParseComposerFile(f) 106 } 107 } 108 return nil, ErrMissingComposerFile 109 } 110 111 // ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package 112 func ParseComposerFile(r io.Reader) (*Package, error) { 113 var cj struct { 114 Name string `json:"name"` 115 Version string `json:"version"` 116 Type string `json:"type"` 117 Metadata 118 } 119 if err := json.NewDecoder(r).Decode(&cj); err != nil { 120 return nil, err 121 } 122 123 if !nameMatch.MatchString(cj.Name) { 124 return nil, ErrInvalidName 125 } 126 127 if cj.Version != "" { 128 if _, err := version.NewSemver(cj.Version); err != nil { 129 return nil, ErrInvalidVersion 130 } 131 } 132 133 if !validation.IsValidURL(cj.Homepage) { 134 cj.Homepage = "" 135 } 136 137 if cj.Type == "" { 138 cj.Type = "library" 139 } 140 141 return &Package{ 142 Name: cj.Name, 143 Version: cj.Version, 144 Type: cj.Type, 145 Metadata: &cj.Metadata, 146 }, nil 147 }