summaryrefslogtreecommitdiffstats
path: root/modules/packages/goproxy/metadata.go
blob: 40f7d205080370c22402a444e3ae411a417e0a78 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
// Copyright 2023 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT

package goproxy

import (
	"archive/zip"
	"fmt"
	"io"
	"path"
	"strings"

	"code.gitea.io/gitea/modules/util"
)

const (
	PropertyGoMod = "go.mod"

	maxGoModFileSize = 16 * 1024 * 1024 // https://go.dev/ref/mod#zip-path-size-constraints
)

var (
	ErrInvalidStructure  = util.NewInvalidArgumentErrorf("package has invalid structure")
	ErrGoModFileTooLarge = util.NewInvalidArgumentErrorf("go.mod file is too large")
)

type Package struct {
	Name    string
	Version string
	GoMod   string
}

// ParsePackage parses the Go package file
// https://go.dev/ref/mod#zip-files
func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
	archive, err := zip.NewReader(r, size)
	if err != nil {
		return nil, err
	}

	var p *Package

	for _, file := range archive.File {
		nameAndVersion := path.Dir(file.Name)

		parts := strings.SplitN(nameAndVersion, "@", 2)
		if len(parts) != 2 {
			continue
		}

		versionParts := strings.SplitN(parts[1], "/", 2)

		if p == nil {
			p = &Package{
				Name:    strings.TrimSuffix(nameAndVersion, "@"+parts[1]),
				Version: versionParts[0],
			}
		}

		if len(versionParts) > 1 {
			// files are expected in the "root" folder
			continue
		}

		if path.Base(file.Name) == "go.mod" {
			if file.UncompressedSize64 > maxGoModFileSize {
				return nil, ErrGoModFileTooLarge
			}

			f, err := archive.Open(file.Name)
			if err != nil {
				return nil, err
			}
			defer f.Close()

			bytes, err := io.ReadAll(&io.LimitedReader{R: f, N: maxGoModFileSize})
			if err != nil {
				return nil, err
			}

			p.GoMod = string(bytes)

			return p, nil
		}
	}

	if p == nil {
		return nil, ErrInvalidStructure
	}

	p.GoMod = fmt.Sprintf("module %s", p.Name)

	return p, nil
}