code.gitea.io/gitea@v1.22.3/modules/packages/goproxy/metadata.go (about)

     1  // Copyright 2023 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package goproxy
     5  
     6  import (
     7  	"archive/zip"
     8  	"fmt"
     9  	"io"
    10  	"path"
    11  	"strings"
    12  
    13  	"code.gitea.io/gitea/modules/util"
    14  )
    15  
    16  const (
    17  	PropertyGoMod = "go.mod"
    18  
    19  	maxGoModFileSize = 16 * 1024 * 1024 // https://go.dev/ref/mod#zip-path-size-constraints
    20  )
    21  
    22  var (
    23  	ErrInvalidStructure  = util.NewInvalidArgumentErrorf("package has invalid structure")
    24  	ErrGoModFileTooLarge = util.NewInvalidArgumentErrorf("go.mod file is too large")
    25  )
    26  
    27  type Package struct {
    28  	Name    string
    29  	Version string
    30  	GoMod   string
    31  }
    32  
    33  // ParsePackage parses the Go package file
    34  // https://go.dev/ref/mod#zip-files
    35  func ParsePackage(r io.ReaderAt, size int64) (*Package, error) {
    36  	archive, err := zip.NewReader(r, size)
    37  	if err != nil {
    38  		return nil, err
    39  	}
    40  
    41  	var p *Package
    42  
    43  	for _, file := range archive.File {
    44  		nameAndVersion := path.Dir(file.Name)
    45  
    46  		parts := strings.SplitN(nameAndVersion, "@", 2)
    47  		if len(parts) != 2 {
    48  			continue
    49  		}
    50  
    51  		versionParts := strings.SplitN(parts[1], "/", 2)
    52  
    53  		if p == nil {
    54  			p = &Package{
    55  				Name:    strings.TrimSuffix(nameAndVersion, "@"+parts[1]),
    56  				Version: versionParts[0],
    57  			}
    58  		}
    59  
    60  		if len(versionParts) > 1 {
    61  			// files are expected in the "root" folder
    62  			continue
    63  		}
    64  
    65  		if path.Base(file.Name) == "go.mod" {
    66  			if file.UncompressedSize64 > maxGoModFileSize {
    67  				return nil, ErrGoModFileTooLarge
    68  			}
    69  
    70  			f, err := archive.Open(file.Name)
    71  			if err != nil {
    72  				return nil, err
    73  			}
    74  			defer f.Close()
    75  
    76  			bytes, err := io.ReadAll(&io.LimitedReader{R: f, N: maxGoModFileSize})
    77  			if err != nil {
    78  				return nil, err
    79  			}
    80  
    81  			p.GoMod = string(bytes)
    82  
    83  			return p, nil
    84  		}
    85  	}
    86  
    87  	if p == nil {
    88  		return nil, ErrInvalidStructure
    89  	}
    90  
    91  	p.GoMod = fmt.Sprintf("module %s", p.Name)
    92  
    93  	return p, nil
    94  }