| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277 |
- // Copyright 2021 The Gitea Authors. All rights reserved.
- // SPDX-License-Identifier: MIT
-
- package nuget
-
- import (
- "archive/zip"
- "bytes"
- "encoding/xml"
- "fmt"
- "io"
- "path/filepath"
- "regexp"
- "strings"
-
- "code.gitea.io/gitea/modules/util"
- "code.gitea.io/gitea/modules/validation"
-
- "github.com/hashicorp/go-version"
- )
-
- var (
- // ErrMissingNuspecFile indicates a missing Nuspec file
- ErrMissingNuspecFile = util.NewInvalidArgumentErrorf("Nuspec file is missing")
- // ErrNuspecFileTooLarge indicates a Nuspec file which is too large
- ErrNuspecFileTooLarge = util.NewInvalidArgumentErrorf("Nuspec file is too large")
- // ErrNuspecInvalidID indicates an invalid id in the Nuspec file
- ErrNuspecInvalidID = util.NewInvalidArgumentErrorf("Nuspec file contains an invalid id")
- // ErrNuspecInvalidVersion indicates an invalid version in the Nuspec file
- ErrNuspecInvalidVersion = util.NewInvalidArgumentErrorf("Nuspec file contains an invalid version")
- )
-
- // PackageType specifies the package type the metadata describes
- type PackageType int
-
- const (
- // DependencyPackage represents a package (*.nupkg)
- DependencyPackage PackageType = iota + 1
- // SymbolsPackage represents a symbol package (*.snupkg)
- SymbolsPackage
-
- PropertySymbolID = "nuget.symbol.id"
- )
-
- var idmatch = regexp.MustCompile(`\A\w+(?:[.-]\w+)*\z`)
-
- const maxNuspecFileSize = 3 * 1024 * 1024
-
- // Package represents a Nuget package
- type Package struct {
- PackageType PackageType
- ID string
- Version string
- Metadata *Metadata
- NuspecContent *bytes.Buffer
- }
-
- // Metadata represents the metadata of a Nuget package
- type Metadata struct {
- Authors string `json:"authors,omitempty"`
- Copyright string `json:"copyright,omitempty"`
- Description string `json:"description,omitempty"`
- DevelopmentDependency bool `json:"development_dependency,omitempty"`
- IconURL string `json:"icon_url,omitempty"`
- Language string `json:"language,omitempty"`
- LicenseURL string `json:"license_url,omitempty"`
- MinClientVersion string `json:"min_client_version,omitempty"`
- Owners string `json:"owners,omitempty"`
- ProjectURL string `json:"project_url,omitempty"`
- Readme string `json:"readme,omitempty"`
- ReleaseNotes string `json:"release_notes,omitempty"`
- RepositoryURL string `json:"repository_url,omitempty"`
- RequireLicenseAcceptance bool `json:"require_license_acceptance"`
- Summary string `json:"summary,omitempty"`
- Tags string `json:"tags,omitempty"`
- Title string `json:"title,omitempty"`
-
- Dependencies map[string][]Dependency `json:"dependencies,omitempty"`
- }
-
- // Dependency represents a dependency of a Nuget package
- type Dependency struct {
- ID string `json:"id"`
- Version string `json:"version"`
- }
-
- // https://learn.microsoft.com/en-us/nuget/reference/nuspec
- // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd
- type nuspecPackage struct {
- Metadata struct {
- // required fields
- Authors string `xml:"authors"`
- Description string `xml:"description"`
- ID string `xml:"id"`
- Version string `xml:"version"`
-
- // optional fields
- Copyright string `xml:"copyright"`
- DevelopmentDependency bool `xml:"developmentDependency"`
- IconURL string `xml:"iconUrl"`
- Language string `xml:"language"`
- LicenseURL string `xml:"licenseUrl"`
- MinClientVersion string `xml:"minClientVersion,attr"`
- Owners string `xml:"owners"`
- ProjectURL string `xml:"projectUrl"`
- Readme string `xml:"readme"`
- ReleaseNotes string `xml:"releaseNotes"`
- RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
- Summary string `xml:"summary"`
- Tags string `xml:"tags"`
- Title string `xml:"title"`
-
- Dependencies struct {
- Dependency []struct {
- ID string `xml:"id,attr"`
- Version string `xml:"version,attr"`
- Exclude string `xml:"exclude,attr"`
- } `xml:"dependency"`
- Group []struct {
- TargetFramework string `xml:"targetFramework,attr"`
- Dependency []struct {
- ID string `xml:"id,attr"`
- Version string `xml:"version,attr"`
- Exclude string `xml:"exclude,attr"`
- } `xml:"dependency"`
- } `xml:"group"`
- } `xml:"dependencies"`
- PackageTypes struct {
- PackageType []struct {
- Name string `xml:"name,attr"`
- } `xml:"packageType"`
- } `xml:"packageTypes"`
- Repository struct {
- URL string `xml:"url,attr"`
- } `xml:"repository"`
- } `xml:"metadata"`
- }
-
- // ParsePackageMetaData parses the metadata of a Nuget package file
- func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
- archive, err := zip.NewReader(r, size)
- if err != nil {
- return nil, err
- }
-
- for _, file := range archive.File {
- if filepath.Dir(file.Name) != "." {
- continue
- }
- if strings.HasSuffix(strings.ToLower(file.Name), ".nuspec") {
- if file.UncompressedSize64 > maxNuspecFileSize {
- return nil, ErrNuspecFileTooLarge
- }
- f, err := archive.Open(file.Name)
- if err != nil {
- return nil, err
- }
- defer f.Close()
-
- return ParseNuspecMetaData(archive, f)
- }
- }
- return nil, ErrMissingNuspecFile
- }
-
- // ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
- func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
- var nuspecBuf bytes.Buffer
- var p nuspecPackage
- if err := xml.NewDecoder(io.TeeReader(r, &nuspecBuf)).Decode(&p); err != nil {
- return nil, err
- }
-
- if !idmatch.MatchString(p.Metadata.ID) {
- return nil, ErrNuspecInvalidID
- }
-
- v, err := version.NewSemver(p.Metadata.Version)
- if err != nil {
- return nil, ErrNuspecInvalidVersion
- }
-
- if !validation.IsValidURL(p.Metadata.ProjectURL) {
- p.Metadata.ProjectURL = ""
- }
-
- packageType := DependencyPackage
- for _, pt := range p.Metadata.PackageTypes.PackageType {
- if pt.Name == "SymbolsPackage" {
- packageType = SymbolsPackage
- break
- }
- }
-
- m := &Metadata{
- Authors: p.Metadata.Authors,
- Copyright: p.Metadata.Copyright,
- Description: p.Metadata.Description,
- DevelopmentDependency: p.Metadata.DevelopmentDependency,
- IconURL: p.Metadata.IconURL,
- Language: p.Metadata.Language,
- LicenseURL: p.Metadata.LicenseURL,
- MinClientVersion: p.Metadata.MinClientVersion,
- Owners: p.Metadata.Owners,
- ProjectURL: p.Metadata.ProjectURL,
- ReleaseNotes: p.Metadata.ReleaseNotes,
- RepositoryURL: p.Metadata.Repository.URL,
- RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
- Summary: p.Metadata.Summary,
- Tags: p.Metadata.Tags,
- Title: p.Metadata.Title,
-
- Dependencies: make(map[string][]Dependency),
- }
-
- if p.Metadata.Readme != "" {
- f, err := archive.Open(p.Metadata.Readme)
- if err == nil {
- buf, _ := io.ReadAll(f)
- m.Readme = string(buf)
- _ = f.Close()
- }
- }
-
- if len(p.Metadata.Dependencies.Dependency) > 0 {
- deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
- for _, dep := range p.Metadata.Dependencies.Dependency {
- if dep.ID == "" || dep.Version == "" {
- continue
- }
- deps = append(deps, Dependency{
- ID: dep.ID,
- Version: dep.Version,
- })
- }
- m.Dependencies[""] = deps
- }
- for _, group := range p.Metadata.Dependencies.Group {
- deps := make([]Dependency, 0, len(group.Dependency))
- for _, dep := range group.Dependency {
- if dep.ID == "" || dep.Version == "" {
- continue
- }
- deps = append(deps, Dependency{
- ID: dep.ID,
- Version: dep.Version,
- })
- }
- if len(deps) > 0 {
- m.Dependencies[group.TargetFramework] = deps
- }
- }
- return &Package{
- PackageType: packageType,
- ID: p.Metadata.ID,
- Version: toNormalizedVersion(v),
- Metadata: m,
- NuspecContent: &nuspecBuf,
- }, nil
- }
-
- // https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers
- // https://github.com/NuGet/NuGet.Client/blob/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Versioning/VersionFormatter.cs#L121
- func toNormalizedVersion(v *version.Version) string {
- var buf bytes.Buffer
- segments := v.Segments64()
- _, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
- if len(segments) > 3 && segments[3] > 0 {
- _, _ = fmt.Fprintf(&buf, ".%d", segments[3])
- }
- pre := v.Prerelease()
- if pre != "" {
- _, _ = fmt.Fprint(&buf, "-", pre)
- }
- return buf.String()
- }
|