Skip to content

Commit 6f47c45

Browse files
committed
fix
1 parent 0fb3be7 commit 6f47c45

File tree

3 files changed

+189
-53
lines changed

3 files changed

+189
-53
lines changed

modules/packages/composer/metadata.go

Lines changed: 123 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,13 @@
44
package composer
55

66
import (
7+
"archive/tar"
78
"archive/zip"
9+
"compress/bzip2"
10+
"compress/gzip"
11+
"errors"
812
"io"
13+
"io/fs"
914
"path"
1015
"regexp"
1116
"strings"
@@ -29,8 +34,10 @@ var (
2934
ErrInvalidVersion = util.NewInvalidArgumentErrorf("package version is invalid")
3035
)
3136

32-
// Package represents a Composer package
33-
type Package struct {
37+
// PackageInfo represents Composer package info
38+
type PackageInfo struct {
39+
Filename string
40+
3441
Name string
3542
Version string
3643
Type string
@@ -44,7 +51,7 @@ type Metadata struct {
4451
Description string `json:"description,omitempty"`
4552
Readme string `json:"readme,omitempty"`
4653
Keywords []string `json:"keywords,omitempty"`
47-
Comments Comments `json:"_comments,omitempty"`
54+
Comments Comments `json:"_comment,omitempty"`
4855
Homepage string `json:"homepage,omitempty"`
4956
License Licenses `json:"license,omitempty"`
5057
Authors []Author `json:"authors,omitempty"`
@@ -75,7 +82,7 @@ func (l *Licenses) UnmarshalJSON(data []byte) error {
7582
if err := json.Unmarshal(data, &values); err != nil {
7683
return err
7784
}
78-
*l = Licenses(values)
85+
*l = values
7986
}
8087
return nil
8188
}
@@ -97,7 +104,7 @@ func (c *Comments) UnmarshalJSON(data []byte) error {
97104
if err := json.Unmarshal(data, &values); err != nil {
98105
return err
99106
}
100-
*c = Comments(values)
107+
*c = values
101108
}
102109
return nil
103110
}
@@ -111,46 +118,131 @@ type Author struct {
111118

112119
var nameMatch = regexp.MustCompile(`\A[a-z0-9]([_\.-]?[a-z0-9]+)*/[a-z0-9](([_\.]?|-{0,2})[a-z0-9]+)*\z`)
113120

114-
// ParsePackage parses the metadata of a Composer package file
115-
func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
116-
archive, err := zip.NewReader(r, size)
121+
type ReadSeekAt interface {
122+
io.Reader
123+
io.ReaderAt
124+
io.Seeker
125+
Size() int64
126+
}
127+
128+
func readPackageFileZip(r ReadSeekAt, filename string, limit int) ([]byte, error) {
129+
archive, err := zip.NewReader(r, r.Size())
117130
if err != nil {
118131
return nil, err
119132
}
120133

121134
for _, file := range archive.File {
122-
if strings.Count(file.Name, "/") > 1 {
123-
continue
124-
}
125-
if strings.HasSuffix(strings.ToLower(file.Name), "composer.json") {
135+
filePath := path.Clean(file.Name)
136+
if util.AsciiEqualFold(filePath, filename) {
126137
f, err := archive.Open(file.Name)
127138
if err != nil {
128139
return nil, err
129140
}
130141
defer f.Close()
131142

132-
return ParseComposerFile(archive, path.Dir(file.Name), f)
143+
return util.ReadWithLimit(f, limit)
144+
}
145+
}
146+
return nil, fs.ErrNotExist
147+
}
148+
149+
func readPackageFileTar(r io.Reader, filename string, limit int) ([]byte, error) {
150+
tarReader := tar.NewReader(r)
151+
for {
152+
header, err := tarReader.Next()
153+
if err == io.EOF {
154+
break
155+
} else if err != nil {
156+
return nil, err
157+
}
158+
159+
filePath := path.Clean(header.Name)
160+
if util.AsciiEqualFold(filePath, filename) {
161+
return util.ReadWithLimit(tarReader, limit)
133162
}
134163
}
135-
return nil, ErrMissingComposerFile
164+
return nil, fs.ErrNotExist
136165
}
137166

138-
// ParseComposerFile parses a composer.json file to retrieve the metadata of a Composer package
139-
func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Package, error) {
167+
const (
168+
pkgExtZip = ".zip"
169+
pkgExtTarGz = ".tar.gz"
170+
pkgExtTarBz2 = ".tar.bz2"
171+
)
172+
173+
func detectPackageExtName(r ReadSeekAt) (string, error) {
174+
headBytes := make([]byte, 4)
175+
_, err := r.ReadAt(headBytes, 0)
176+
if err != nil {
177+
return "", err
178+
}
179+
_, err = r.Seek(0, io.SeekStart)
180+
if err != nil {
181+
return "", err
182+
}
183+
switch {
184+
case headBytes[0] == 'P' && headBytes[1] == 'K':
185+
return pkgExtZip, nil
186+
case string(headBytes[:3]) == "BZh":
187+
return pkgExtTarBz2, nil
188+
case headBytes[0] == 0x1f && headBytes[1] == 0x8b:
189+
return pkgExtTarGz, nil
190+
}
191+
return "", util.NewInvalidArgumentErrorf("not a valid package file")
192+
}
193+
194+
func readPackageFile(pkgExt string, r ReadSeekAt, filename string, limit int) ([]byte, error) {
195+
_, err := r.Seek(0, io.SeekStart)
196+
if err != nil {
197+
return nil, err
198+
}
199+
200+
switch pkgExt {
201+
case pkgExtZip:
202+
return readPackageFileZip(r, filename, limit)
203+
case pkgExtTarBz2:
204+
bzip2Reader := bzip2.NewReader(r)
205+
return readPackageFileTar(bzip2Reader, filename, limit)
206+
case pkgExtTarGz:
207+
gzReader, err := gzip.NewReader(r)
208+
if err != nil {
209+
return nil, err
210+
}
211+
return readPackageFileTar(gzReader, filename, limit)
212+
}
213+
return nil, util.NewInvalidArgumentErrorf("not a valid package file")
214+
}
215+
216+
// ParsePackage parses the metadata of a Composer package file
217+
func ParsePackage(r ReadSeekAt, optVersion ...string) (*PackageInfo, error) {
218+
pkgExt, err := detectPackageExtName(r)
219+
if err != nil {
220+
return nil, err
221+
}
222+
dataComposerJSON, err := readPackageFile(pkgExt, r, "composer.json", 10*1024*1024)
223+
if errors.Is(err, fs.ErrNotExist) {
224+
return nil, ErrMissingComposerFile
225+
} else if err != nil {
226+
return nil, err
227+
}
228+
140229
var cj struct {
141230
Name string `json:"name"`
142231
Version string `json:"version"`
143232
Type string `json:"type"`
144233
Metadata
145234
}
146-
if err := json.NewDecoder(r).Decode(&cj); err != nil {
235+
if err := json.Unmarshal(dataComposerJSON, &cj); err != nil {
147236
return nil, err
148237
}
149238

150239
if !nameMatch.MatchString(cj.Name) {
151240
return nil, ErrInvalidName
152241
}
153242

243+
if cj.Version == "" {
244+
cj.Version = util.OptionalArg(optVersion)
245+
}
154246
if cj.Version != "" {
155247
if _, err := version.NewSemver(cj.Version); err != nil {
156248
return nil, ErrInvalidVersion
@@ -168,17 +260,23 @@ func ParseComposerFile(archive *zip.Reader, pathPrefix string, r io.Reader) (*Pa
168260
if cj.Readme == "" {
169261
cj.Readme = "README.md"
170262
}
171-
f, err := archive.Open(path.Join(pathPrefix, cj.Readme))
172-
if err == nil {
173-
// 10kb limit for readme content
174-
buf, _ := io.ReadAll(io.LimitReader(f, 10*1024))
175-
cj.Readme = string(buf)
176-
_ = f.Close()
177-
} else {
263+
dataReadmeMd, _ := readPackageFile(pkgExt, r, cj.Readme, 10*1024)
264+
265+
// FIXME: legacy problem, the "Readme" field is abused, it should always be the path to the readme file
266+
if len(dataReadmeMd) == 0 {
178267
cj.Readme = ""
268+
} else {
269+
cj.Readme = string(dataReadmeMd)
179270
}
180271

181-
return &Package{
272+
// FIXME: legacy format: strings.ToLower(fmt.Sprintf("%s.%s.zip", strings.ReplaceAll(cp.Name, "/", "-"), cp.Version)), doesn't read good
273+
pkgFilename := strings.ReplaceAll(cj.Name, "/", "-")
274+
if cj.Version != "" {
275+
pkgFilename += "." + cj.Version
276+
}
277+
pkgFilename += pkgExt
278+
return &PackageInfo{
279+
Filename: pkgFilename,
182280
Name: cj.Name,
183281
Version: cj.Version,
184282
Type: cj.Type,

0 commit comments

Comments
 (0)