code.gitea.io/gitea@v1.22.3/modules/packages/alpine/metadata.go (about) 1 // Copyright 2023 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package alpine 5 6 import ( 7 "archive/tar" 8 "bufio" 9 "compress/gzip" 10 "crypto/sha1" 11 "encoding/base64" 12 "io" 13 "strconv" 14 "strings" 15 16 "code.gitea.io/gitea/modules/util" 17 "code.gitea.io/gitea/modules/validation" 18 ) 19 20 var ( 21 ErrMissingPKGINFOFile = util.NewInvalidArgumentErrorf("PKGINFO file is missing") 22 ErrInvalidName = util.NewInvalidArgumentErrorf("package name is invalid") 23 ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid") 24 ) 25 26 const ( 27 PropertyMetadata = "alpine.metadata" 28 PropertyBranch = "alpine.branch" 29 PropertyRepository = "alpine.repository" 30 PropertyArchitecture = "alpine.architecture" 31 32 SettingKeyPrivate = "alpine.key.private" 33 SettingKeyPublic = "alpine.key.public" 34 35 RepositoryPackage = "_alpine" 36 RepositoryVersion = "_repository" 37 38 NoArch = "noarch" 39 ) 40 41 // https://wiki.alpinelinux.org/wiki/Apk_spec 42 43 // Package represents an Alpine package 44 type Package struct { 45 Name string 46 Version string 47 VersionMetadata VersionMetadata 48 FileMetadata FileMetadata 49 } 50 51 // Metadata of an Alpine package 52 type VersionMetadata struct { 53 Description string `json:"description,omitempty"` 54 License string `json:"license,omitempty"` 55 ProjectURL string `json:"project_url,omitempty"` 56 Maintainer string `json:"maintainer,omitempty"` 57 } 58 59 type FileMetadata struct { 60 Checksum string `json:"checksum"` 61 Packager string `json:"packager,omitempty"` 62 BuildDate int64 `json:"build_date,omitempty"` 63 Size int64 `json:"size,omitempty"` 64 Architecture string `json:"architecture,omitempty"` 65 Origin string `json:"origin,omitempty"` 66 CommitHash string `json:"commit_hash,omitempty"` 67 InstallIf string `json:"install_if,omitempty"` 68 Provides []string `json:"provides,omitempty"` 69 Dependencies []string `json:"dependencies,omitempty"` 70 ProviderPriority int64 `json:"provider_priority,omitempty"` 71 } 72 73 // ParsePackage parses the Alpine package file 74 func ParsePackage(r io.Reader) (*Package, error) { 75 // Alpine packages are concated .tar.gz streams. Usually the first stream contains the package metadata. 76 77 br := bufio.NewReader(r) // needed for gzip Multistream 78 79 h := sha1.New() 80 81 gzr, err := gzip.NewReader(&teeByteReader{br, h}) 82 if err != nil { 83 return nil, err 84 } 85 defer gzr.Close() 86 87 for { 88 gzr.Multistream(false) 89 90 tr := tar.NewReader(gzr) 91 for { 92 hd, err := tr.Next() 93 if err == io.EOF { 94 break 95 } 96 if err != nil { 97 return nil, err 98 } 99 100 if hd.Name == ".PKGINFO" { 101 p, err := ParsePackageInfo(tr) 102 if err != nil { 103 return nil, err 104 } 105 106 // drain the reader 107 for { 108 if _, err := tr.Next(); err != nil { 109 break 110 } 111 } 112 113 p.FileMetadata.Checksum = "Q1" + base64.StdEncoding.EncodeToString(h.Sum(nil)) 114 115 return p, nil 116 } 117 } 118 119 h = sha1.New() 120 121 err = gzr.Reset(&teeByteReader{br, h}) 122 if err == io.EOF { 123 break 124 } 125 if err != nil { 126 return nil, err 127 } 128 } 129 130 return nil, ErrMissingPKGINFOFile 131 } 132 133 // ParsePackageInfo parses a PKGINFO file to retrieve the metadata of an Alpine package 134 func ParsePackageInfo(r io.Reader) (*Package, error) { 135 p := &Package{} 136 137 scanner := bufio.NewScanner(r) 138 for scanner.Scan() { 139 line := scanner.Text() 140 141 if strings.HasPrefix(line, "#") { 142 continue 143 } 144 145 i := strings.IndexRune(line, '=') 146 if i == -1 { 147 continue 148 } 149 150 key := strings.TrimSpace(line[:i]) 151 value := strings.TrimSpace(line[i+1:]) 152 153 switch key { 154 case "pkgname": 155 p.Name = value 156 case "pkgver": 157 p.Version = value 158 case "pkgdesc": 159 p.VersionMetadata.Description = value 160 case "url": 161 p.VersionMetadata.ProjectURL = value 162 case "builddate": 163 n, err := strconv.ParseInt(value, 10, 64) 164 if err == nil { 165 p.FileMetadata.BuildDate = n 166 } 167 case "size": 168 n, err := strconv.ParseInt(value, 10, 64) 169 if err == nil { 170 p.FileMetadata.Size = n 171 } 172 case "arch": 173 p.FileMetadata.Architecture = value 174 case "origin": 175 p.FileMetadata.Origin = value 176 case "commit": 177 p.FileMetadata.CommitHash = value 178 case "maintainer": 179 p.VersionMetadata.Maintainer = value 180 case "packager": 181 p.FileMetadata.Packager = value 182 case "license": 183 p.VersionMetadata.License = value 184 case "install_if": 185 p.FileMetadata.InstallIf = value 186 case "provides": 187 if value != "" { 188 p.FileMetadata.Provides = append(p.FileMetadata.Provides, value) 189 } 190 case "depend": 191 if value != "" { 192 p.FileMetadata.Dependencies = append(p.FileMetadata.Dependencies, value) 193 } 194 case "provider_priority": 195 n, err := strconv.ParseInt(value, 10, 64) 196 if err == nil { 197 p.FileMetadata.ProviderPriority = n 198 } 199 } 200 } 201 if err := scanner.Err(); err != nil { 202 return nil, err 203 } 204 205 if p.Name == "" { 206 return nil, ErrInvalidName 207 } 208 209 if p.Version == "" { 210 return nil, ErrInvalidVersion 211 } 212 213 if !validation.IsValidURL(p.VersionMetadata.ProjectURL) { 214 p.VersionMetadata.ProjectURL = "" 215 } 216 217 return p, nil 218 } 219 220 // Same as io.TeeReader but implements io.ByteReader 221 type teeByteReader struct { 222 r *bufio.Reader 223 w io.Writer 224 } 225 226 func (t *teeByteReader) Read(p []byte) (int, error) { 227 n, err := t.r.Read(p) 228 if n > 0 { 229 if n, err := t.w.Write(p[:n]); err != nil { 230 return n, err 231 } 232 } 233 return n, err 234 } 235 236 func (t *teeByteReader) ReadByte() (byte, error) { 237 b, err := t.r.ReadByte() 238 if err == nil { 239 if _, err := t.w.Write([]byte{b}); err != nil { 240 return 0, err 241 } 242 } 243 return b, err 244 }