code.gitea.io/gitea@v1.19.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 Autoload map[string]interface{} `json:"autoload,omitempty"` 47 AutoloadDev map[string]interface{} `json:"autoload-dev,omitempty"` 48 Extra map[string]interface{} `json:"extra,omitempty"` 49 Require map[string]string `json:"require,omitempty"` 50 RequireDev map[string]string `json:"require-dev,omitempty"` 51 Suggest map[string]string `json:"suggest,omitempty"` 52 Provide map[string]string `json:"provide,omitempty"` 53 } 54 55 // Licenses represents the licenses of a Composer package 56 type Licenses []string 57 58 // UnmarshalJSON reads from a string or array 59 func (l *Licenses) UnmarshalJSON(data []byte) error { 60 switch data[0] { 61 case '"': 62 var value string 63 if err := json.Unmarshal(data, &value); err != nil { 64 return err 65 } 66 *l = Licenses{value} 67 case '[': 68 values := make([]string, 0, 5) 69 if err := json.Unmarshal(data, &values); err != nil { 70 return err 71 } 72 *l = Licenses(values) 73 } 74 return nil 75 } 76 77 // Author represents an author 78 type Author struct { 79 Name string `json:"name,omitempty"` 80 Email string `json:"email,omitempty"` 81 Homepage string `json:"homepage,omitempty"` 82 } 83 84 var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`) 85 86 // ParsePackage parses the metadata of a Composer package file 87 func ParsePackage(r io.ReaderAt, size int64) (*Package, error) { 88 archive, err := zip.NewReader(r, size) 89 if err != nil { 90 return nil, err 91 } 92 93 for _, file := range archive.File { 94 if strings.Count(file.Name, "/") > 1 { 95 continue 96 } 97 if strings.HasSuffix(strings.ToLower(file.Name), "composer.json") { 98 f, err := archive.Open(file.Name) 99 if err != nil { 100 return nil, err 101 } 102 defer f.Close() 103 104 return ParseComposerFile(f) 105 } 106 } 107 return nil, ErrMissingComposerFile 108 } 109 110 // ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package 111 func ParseComposerFile(r io.Reader) (*Package, error) { 112 var cj struct { 113 Name string `json:"name"` 114 Version string `json:"version"` 115 Type string `json:"type"` 116 Metadata 117 } 118 if err := json.NewDecoder(r).Decode(&cj); err != nil { 119 return nil, err 120 } 121 122 if !nameMatch.MatchString(cj.Name) { 123 return nil, ErrInvalidName 124 } 125 126 if cj.Version != "" { 127 if _, err := version.NewSemver(cj.Version); err != nil { 128 return nil, ErrInvalidVersion 129 } 130 } 131 132 if !validation.IsValidURL(cj.Homepage) { 133 cj.Homepage = "" 134 } 135 136 if cj.Type == "" { 137 cj.Type = "library" 138 } 139 140 return &Package{ 141 Name: cj.Name, 142 Version: cj.Version, 143 Type: cj.Type, 144 Metadata: &cj.Metadata, 145 }, nil 146 }