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