[GITEA] feat(nuget): basic manifest download
Refs: https://codeberg.org/forgejo/forgejo/pulls/2222 (cherry picked from commit 5f837efc15f3d1e0d7fbed7fc569251143266584) fix: write xml header (cherry picked from commit a715984a42be9da81c48106d5eae244098ac1108) fix: optional elements and xml schema (cherry picked from commit 6ea6895a3616246e7282aa20d8f010fa931b60ea) fix: pass all other requests to file search (cherry picked from commit 9bfc74833a3b657453b4519573598432a87e3e3c) test: add integration test (cherry picked from commit b798f4ce86daa78e694c5c142e6f5f44938e6cb6) fix: use xmlResponse (cherry picked from commit 7f76df0b246c64fac0eeb115642c8cb6eb676f36) (cherry picked from commit e18d574ca40905aec52fbbe8247ba83fd01874dd)
This commit is contained in:
		
					parent
					
						
							
								bfe87e30af
							
						
					
				
			
			
				commit
				
					
						97cc955101
					
				
			
		
					 3 changed files with 170 additions and 64 deletions
				
			
		| 
						 | 
				
			
			@ -71,34 +71,50 @@ type Dependency struct {
 | 
			
		|||
	Version string `json:"version"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspecPackageType struct {
 | 
			
		||||
	Name string `xml:"name,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspecPackageTypes struct {
 | 
			
		||||
	PackageType []nuspecPackageType `xml:"packageType"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspecRepository struct {
 | 
			
		||||
	URL  string `xml:"url,attr,omitempty"`
 | 
			
		||||
	Type string `xml:"type,attr,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
type nuspecDependency struct {
 | 
			
		||||
	ID      string `xml:"id,attr"`
 | 
			
		||||
	Version string `xml:"version,attr"`
 | 
			
		||||
	Exclude string `xml:"exclude,attr,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspecGroup struct {
 | 
			
		||||
	TargetFramework string             `xml:"targetFramework,attr"`
 | 
			
		||||
	Dependency      []nuspecDependency `xml:"dependency"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspecDependencies struct {
 | 
			
		||||
	Group []nuspecGroup `xml:"group"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspeceMetadata struct {
 | 
			
		||||
	ID                       string              `xml:"id"`
 | 
			
		||||
	Version                  string              `xml:"version"`
 | 
			
		||||
	Authors                  string              `xml:"authors"`
 | 
			
		||||
	RequireLicenseAcceptance bool                `xml:"requireLicenseAcceptance,omitempty"`
 | 
			
		||||
	ProjectURL               string              `xml:"projectUrl,omitempty"`
 | 
			
		||||
	Description              string              `xml:"description"`
 | 
			
		||||
	ReleaseNotes             string              `xml:"releaseNotes,omitempty"`
 | 
			
		||||
	PackageTypes             *nuspecPackageTypes `xml:"packageTypes,omitempty"`
 | 
			
		||||
	Repository               *nuspecRepository   `xml:"repository,omitempty"`
 | 
			
		||||
	Dependencies             *nuspecDependencies `xml:"dependencies,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type nuspecPackage struct {
 | 
			
		||||
	Metadata struct {
 | 
			
		||||
		ID                       string `xml:"id"`
 | 
			
		||||
		Version                  string `xml:"version"`
 | 
			
		||||
		Authors                  string `xml:"authors"`
 | 
			
		||||
		RequireLicenseAcceptance bool   `xml:"requireLicenseAcceptance"`
 | 
			
		||||
		ProjectURL               string `xml:"projectUrl"`
 | 
			
		||||
		Description              string `xml:"description"`
 | 
			
		||||
		ReleaseNotes             string `xml:"releaseNotes"`
 | 
			
		||||
		PackageTypes             struct {
 | 
			
		||||
			PackageType []struct {
 | 
			
		||||
				Name string `xml:"name,attr"`
 | 
			
		||||
			} `xml:"packageType"`
 | 
			
		||||
		} `xml:"packageTypes"`
 | 
			
		||||
		Repository struct {
 | 
			
		||||
			URL string `xml:"url,attr"`
 | 
			
		||||
		} `xml:"repository"`
 | 
			
		||||
		Dependencies struct {
 | 
			
		||||
			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"`
 | 
			
		||||
	} `xml:"metadata"`
 | 
			
		||||
	XMLName  xml.Name        `xml:"package"`
 | 
			
		||||
	Xmlns    string          `xml:"xmlns,attr"`
 | 
			
		||||
	Metadata nuspeceMetadata `xml:"metadata"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ParsePackageMetaData parses the metadata of a Nuget package file
 | 
			
		||||
| 
						 | 
				
			
			@ -149,10 +165,12 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	packageType := DependencyPackage
 | 
			
		||||
	for _, pt := range p.Metadata.PackageTypes.PackageType {
 | 
			
		||||
		if pt.Name == "SymbolsPackage" {
 | 
			
		||||
			packageType = SymbolsPackage
 | 
			
		||||
			break
 | 
			
		||||
	if p.Metadata.PackageTypes != nil {
 | 
			
		||||
		for _, pt := range p.Metadata.PackageTypes.PackageType {
 | 
			
		||||
			if pt.Name == "SymbolsPackage" {
 | 
			
		||||
				packageType = SymbolsPackage
 | 
			
		||||
				break
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -161,24 +179,27 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
 | 
			
		|||
		ReleaseNotes:             p.Metadata.ReleaseNotes,
 | 
			
		||||
		Authors:                  p.Metadata.Authors,
 | 
			
		||||
		ProjectURL:               p.Metadata.ProjectURL,
 | 
			
		||||
		RepositoryURL:            p.Metadata.Repository.URL,
 | 
			
		||||
		RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
 | 
			
		||||
		Dependencies:             make(map[string][]Dependency),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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
 | 
			
		||||
	if p.Metadata.Repository != nil {
 | 
			
		||||
		m.RepositoryURL = p.Metadata.Repository.URL
 | 
			
		||||
	}
 | 
			
		||||
	if p.Metadata.Dependencies != nil {
 | 
			
		||||
		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
 | 
			
		||||
			}
 | 
			
		||||
			deps = append(deps, Dependency{
 | 
			
		||||
				ID:      dep.ID,
 | 
			
		||||
				Version: dep.Version,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
		if len(deps) > 0 {
 | 
			
		||||
			m.Dependencies[group.TargetFramework] = deps
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return &Package{
 | 
			
		||||
| 
						 | 
				
			
			@ -204,3 +225,51 @@ func toNormalizedVersion(v *version.Version) string {
 | 
			
		|||
	}
 | 
			
		||||
	return buf.String()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// returning any here because we use a private type and we don't need the type for xml marshalling
 | 
			
		||||
func GenerateNuspec(pd *Package) any {
 | 
			
		||||
	m := nuspeceMetadata{
 | 
			
		||||
		ID:                       pd.ID,
 | 
			
		||||
		Version:                  pd.Version,
 | 
			
		||||
		Authors:                  pd.Metadata.Authors,
 | 
			
		||||
		Description:              pd.Metadata.Description,
 | 
			
		||||
		ProjectURL:               pd.Metadata.ProjectURL,
 | 
			
		||||
		RequireLicenseAcceptance: pd.Metadata.RequireLicenseAcceptance,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if pd.Metadata.RepositoryURL != "" {
 | 
			
		||||
		m.Repository = &nuspecRepository{
 | 
			
		||||
			URL: pd.Metadata.RepositoryURL,
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	groups := len(pd.Metadata.Dependencies)
 | 
			
		||||
	if groups > 0 {
 | 
			
		||||
		m.Dependencies = &nuspecDependencies{
 | 
			
		||||
			Group: make([]nuspecGroup, 0, groups),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for tgf, deps := range pd.Metadata.Dependencies {
 | 
			
		||||
			if len(deps) == 0 {
 | 
			
		||||
				continue
 | 
			
		||||
			}
 | 
			
		||||
			gDeps := make([]nuspecDependency, 0, len(deps))
 | 
			
		||||
			for _, dep := range deps {
 | 
			
		||||
				gDeps = append(gDeps, nuspecDependency{
 | 
			
		||||
					ID:      dep.ID,
 | 
			
		||||
					Version: dep.Version,
 | 
			
		||||
				})
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			m.Dependencies.Group = append(m.Dependencies.Group, nuspecGroup{
 | 
			
		||||
				TargetFramework: tgf,
 | 
			
		||||
				Dependency:      gDeps,
 | 
			
		||||
			})
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &nuspecPackage{
 | 
			
		||||
		Xmlns:    "http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd",
 | 
			
		||||
		Metadata: m,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -387,34 +387,56 @@ func EnumeratePackageVersionsV3(ctx *context.Context) {
 | 
			
		|||
	ctx.JSON(http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
 | 
			
		||||
// https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-manifest-nuspec
 | 
			
		||||
// https://learn.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
 | 
			
		||||
func DownloadPackageFile(ctx *context.Context) {
 | 
			
		||||
	packageName := ctx.Params("id")
 | 
			
		||||
	packageVersion := ctx.Params("version")
 | 
			
		||||
	filename := ctx.Params("filename")
 | 
			
		||||
 | 
			
		||||
	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
 | 
			
		||||
		ctx,
 | 
			
		||||
		&packages_service.PackageInfo{
 | 
			
		||||
			Owner:       ctx.Package.Owner,
 | 
			
		||||
			PackageType: packages_model.TypeNuGet,
 | 
			
		||||
			Name:        packageName,
 | 
			
		||||
			Version:     packageVersion,
 | 
			
		||||
		},
 | 
			
		||||
		&packages_service.PackageFileInfo{
 | 
			
		||||
			Filename: filename,
 | 
			
		||||
		},
 | 
			
		||||
	)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
 | 
			
		||||
	if filename == fmt.Sprintf("%s.nuspec", packageName) {
 | 
			
		||||
		pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			apiError(ctx, http.StatusNotFound, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	helper.ServePackageFile(ctx, s, u, pf)
 | 
			
		||||
		pd, err := packages_model.GetPackageDescriptor(ctx, pv)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		pkg := &nuget_module.Package{
 | 
			
		||||
			ID:       pd.Package.Name,
 | 
			
		||||
			Version:  packageVersion,
 | 
			
		||||
			Metadata: pd.Metadata.(*nuget_module.Metadata),
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		xmlResponse(ctx, http.StatusOK, nuget_module.GenerateNuspec(pkg))
 | 
			
		||||
	} else {
 | 
			
		||||
		s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
 | 
			
		||||
			ctx,
 | 
			
		||||
			&packages_service.PackageInfo{
 | 
			
		||||
				Owner:       ctx.Package.Owner,
 | 
			
		||||
				PackageType: packages_model.TypeNuGet,
 | 
			
		||||
				Name:        packageName,
 | 
			
		||||
				Version:     packageVersion,
 | 
			
		||||
			},
 | 
			
		||||
			&packages_service.PackageFileInfo{
 | 
			
		||||
				Filename: filename,
 | 
			
		||||
			},
 | 
			
		||||
		)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
 | 
			
		||||
				apiError(ctx, http.StatusNotFound, err)
 | 
			
		||||
				return
 | 
			
		||||
			}
 | 
			
		||||
			apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		helper.ServePackageFile(ctx, s, u, pf)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -353,6 +353,21 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
 | 
			
		|||
 | 
			
		||||
		assert.Equal(t, content, resp.Body.Bytes())
 | 
			
		||||
 | 
			
		||||
		req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.nuspec", url, packageName, packageVersion, packageName)).
 | 
			
		||||
			AddBasicAuth(user.Name)
 | 
			
		||||
		resp = MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
		nuspec := `<?xml version="1.0" encoding="UTF-8"?>` + "\n" +
 | 
			
		||||
			`<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd"><metadata>` +
 | 
			
		||||
			`<id>` + packageName + `</id><version>` + packageVersion + `</version><authors>` + packageAuthors + `</authors><description>` + packageDescription + `</description>` +
 | 
			
		||||
			`<dependencies><group targetFramework=".NETStandard2.0">` +
 | 
			
		||||
			// https://github.com/golang/go/issues/21399 go can't generate self-closing tags
 | 
			
		||||
			`<dependency id="Microsoft.CSharp" version="4.5.0"></dependency>` +
 | 
			
		||||
			`</group></dependencies>` +
 | 
			
		||||
			`</metadata></package>`
 | 
			
		||||
 | 
			
		||||
		assert.Equal(t, nuspec, resp.Body.String())
 | 
			
		||||
 | 
			
		||||
		checkDownloadCount(1)
 | 
			
		||||
 | 
			
		||||
		req = NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/%s/%s.%s.snupkg", url, packageName, packageVersion, packageName, packageVersion)).
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue