mirror of
				https://gitee.com/gitea/gitea
				synced 2025-11-04 08:30:25 +08:00 
			
		
		
		
	Add support for Chocolatey/NuGet v2 API (#21393)
Fixes #21294 This PR adds support for NuGet v2 API. Co-authored-by: Lauris BH <lauris@nix.lv> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com>
This commit is contained in:
		@@ -14,7 +14,7 @@ menu:
 | 
			
		||||
 | 
			
		||||
# NuGet Packages Repository
 | 
			
		||||
 | 
			
		||||
Publish [NuGet](https://www.nuget.org/) packages for your user or organization. The package registry supports [NuGet Symbol Packages](https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg) too.
 | 
			
		||||
Publish [NuGet](https://www.nuget.org/) packages for your user or organization. The package registry supports the V2 and V3 API protocol and you can work with [NuGet Symbol Packages](https://docs.microsoft.com/en-us/nuget/create-packages/symbol-packages-snupkg) too.
 | 
			
		||||
 | 
			
		||||
**Table of Contents**
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -55,12 +55,13 @@ type Package struct {
 | 
			
		||||
 | 
			
		||||
// Metadata represents the metadata of a Nuget package
 | 
			
		||||
type Metadata struct {
 | 
			
		||||
	Description   string                  `json:"description,omitempty"`
 | 
			
		||||
	ReleaseNotes  string                  `json:"release_notes,omitempty"`
 | 
			
		||||
	Authors       string                  `json:"authors,omitempty"`
 | 
			
		||||
	ProjectURL    string                  `json:"project_url,omitempty"`
 | 
			
		||||
	RepositoryURL string                  `json:"repository_url,omitempty"`
 | 
			
		||||
	Dependencies  map[string][]Dependency `json:"dependencies,omitempty"`
 | 
			
		||||
	Description              string                  `json:"description,omitempty"`
 | 
			
		||||
	ReleaseNotes             string                  `json:"release_notes,omitempty"`
 | 
			
		||||
	Authors                  string                  `json:"authors,omitempty"`
 | 
			
		||||
	ProjectURL               string                  `json:"project_url,omitempty"`
 | 
			
		||||
	RepositoryURL            string                  `json:"repository_url,omitempty"`
 | 
			
		||||
	RequireLicenseAcceptance bool                    `json:"require_license_acceptance"`
 | 
			
		||||
	Dependencies             map[string][]Dependency `json:"dependencies,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Dependency represents a dependency of a Nuget package
 | 
			
		||||
@@ -155,12 +156,13 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) {
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	m := &Metadata{
 | 
			
		||||
		Description:   p.Metadata.Description,
 | 
			
		||||
		ReleaseNotes:  p.Metadata.ReleaseNotes,
 | 
			
		||||
		Authors:       p.Metadata.Authors,
 | 
			
		||||
		ProjectURL:    p.Metadata.ProjectURL,
 | 
			
		||||
		RepositoryURL: p.Metadata.Repository.URL,
 | 
			
		||||
		Dependencies:  make(map[string][]Dependency),
 | 
			
		||||
		Description:              p.Metadata.Description,
 | 
			
		||||
		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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -180,15 +180,19 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
			r.Get("/*", maven.DownloadPackageFile)
 | 
			
		||||
		}, reqPackageAccess(perm.AccessModeRead))
 | 
			
		||||
		r.Group("/nuget", func() {
 | 
			
		||||
			r.Get("/index.json", nuget.ServiceIndex) // Needs to be unauthenticated for the NuGet client.
 | 
			
		||||
			r.Group("", func() { // Needs to be unauthenticated for the NuGet client.
 | 
			
		||||
				r.Get("/", nuget.ServiceIndexV2)
 | 
			
		||||
				r.Get("/index.json", nuget.ServiceIndexV3)
 | 
			
		||||
				r.Get("/$metadata", nuget.FeedCapabilityResource)
 | 
			
		||||
			})
 | 
			
		||||
			r.Group("", func() {
 | 
			
		||||
				r.Get("/query", nuget.SearchService)
 | 
			
		||||
				r.Get("/query", nuget.SearchServiceV3)
 | 
			
		||||
				r.Group("/registration/{id}", func() {
 | 
			
		||||
					r.Get("/index.json", nuget.RegistrationIndex)
 | 
			
		||||
					r.Get("/{version}", nuget.RegistrationLeaf)
 | 
			
		||||
					r.Get("/{version}", nuget.RegistrationLeafV3)
 | 
			
		||||
				})
 | 
			
		||||
				r.Group("/package/{id}", func() {
 | 
			
		||||
					r.Get("/index.json", nuget.EnumeratePackageVersions)
 | 
			
		||||
					r.Get("/index.json", nuget.EnumeratePackageVersionsV3)
 | 
			
		||||
					r.Get("/{version}/{filename}", nuget.DownloadPackageFile)
 | 
			
		||||
				})
 | 
			
		||||
				r.Group("", func() {
 | 
			
		||||
@@ -197,6 +201,10 @@ func Routes(ctx gocontext.Context) *web.Route {
 | 
			
		||||
					r.Delete("/{id}/{version}", nuget.DeletePackage)
 | 
			
		||||
				}, reqPackageAccess(perm.AccessModeWrite))
 | 
			
		||||
				r.Get("/symbols/{filename}/{guid:[0-9a-fA-F]{32}[fF]{8}}/{filename2}", nuget.DownloadSymbolFile)
 | 
			
		||||
				r.Get("/Packages(Id='{id:[^']+}',Version='{version:[^']+}')", nuget.RegistrationLeafV2)
 | 
			
		||||
				r.Get("/Packages()", nuget.SearchServiceV2)
 | 
			
		||||
				r.Get("/FindPackagesById()", nuget.EnumeratePackageVersionsV2)
 | 
			
		||||
				r.Get("/Search()", nuget.SearchServiceV2)
 | 
			
		||||
			}, reqPackageAccess(perm.AccessModeRead))
 | 
			
		||||
		})
 | 
			
		||||
		r.Group("/npm", func() {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										393
									
								
								routers/api/packages/nuget/api_v2.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										393
									
								
								routers/api/packages/nuget/api_v2.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,393 @@
 | 
			
		||||
// Copyright 2022 The Gitea Authors. All rights reserved.
 | 
			
		||||
// Use of this source code is governed by a MIT-style
 | 
			
		||||
// license that can be found in the LICENSE file.
 | 
			
		||||
 | 
			
		||||
package nuget
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	packages_model "code.gitea.io/gitea/models/packages"
 | 
			
		||||
	nuget_module "code.gitea.io/gitea/modules/packages/nuget"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type AtomTitle struct {
 | 
			
		||||
	Type string `xml:"type,attr"`
 | 
			
		||||
	Text string `xml:",chardata"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ServiceCollection struct {
 | 
			
		||||
	Href  string    `xml:"href,attr"`
 | 
			
		||||
	Title AtomTitle `xml:"atom:title"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ServiceWorkspace struct {
 | 
			
		||||
	Title      AtomTitle         `xml:"atom:title"`
 | 
			
		||||
	Collection ServiceCollection `xml:"collection"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type ServiceIndexResponseV2 struct {
 | 
			
		||||
	XMLName   xml.Name         `xml:"service"`
 | 
			
		||||
	Base      string           `xml:"base,attr"`
 | 
			
		||||
	Xmlns     string           `xml:"xmlns,attr"`
 | 
			
		||||
	XmlnsAtom string           `xml:"xmlns:atom,attr"`
 | 
			
		||||
	Workspace ServiceWorkspace `xml:"workspace"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxPropertyRef struct {
 | 
			
		||||
	Name string `xml:"Name,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxProperty struct {
 | 
			
		||||
	Name     string `xml:"Name,attr"`
 | 
			
		||||
	Type     string `xml:"Type,attr"`
 | 
			
		||||
	Nullable bool   `xml:"Nullable,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxEntityType struct {
 | 
			
		||||
	Name       string            `xml:"Name,attr"`
 | 
			
		||||
	HasStream  bool              `xml:"m:HasStream,attr"`
 | 
			
		||||
	Keys       []EdmxPropertyRef `xml:"Key>PropertyRef"`
 | 
			
		||||
	Properties []EdmxProperty    `xml:"Property"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxFunctionParameter struct {
 | 
			
		||||
	Name string `xml:"Name,attr"`
 | 
			
		||||
	Type string `xml:"Type,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxFunctionImport struct {
 | 
			
		||||
	Name       string                  `xml:"Name,attr"`
 | 
			
		||||
	ReturnType string                  `xml:"ReturnType,attr"`
 | 
			
		||||
	EntitySet  string                  `xml:"EntitySet,attr"`
 | 
			
		||||
	Parameter  []EdmxFunctionParameter `xml:"Parameter"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxEntitySet struct {
 | 
			
		||||
	Name       string `xml:"Name,attr"`
 | 
			
		||||
	EntityType string `xml:"EntityType,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxEntityContainer struct {
 | 
			
		||||
	Name                     string               `xml:"Name,attr"`
 | 
			
		||||
	IsDefaultEntityContainer bool                 `xml:"m:IsDefaultEntityContainer,attr"`
 | 
			
		||||
	EntitySet                EdmxEntitySet        `xml:"EntitySet"`
 | 
			
		||||
	FunctionImports          []EdmxFunctionImport `xml:"FunctionImport"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxSchema struct {
 | 
			
		||||
	Xmlns           string               `xml:"xmlns,attr"`
 | 
			
		||||
	Namespace       string               `xml:"Namespace,attr"`
 | 
			
		||||
	EntityType      *EdmxEntityType      `xml:"EntityType,omitempty"`
 | 
			
		||||
	EntityContainer *EdmxEntityContainer `xml:"EntityContainer,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxDataServices struct {
 | 
			
		||||
	XmlnsM                string       `xml:"xmlns:m,attr"`
 | 
			
		||||
	DataServiceVersion    string       `xml:"m:DataServiceVersion,attr"`
 | 
			
		||||
	MaxDataServiceVersion string       `xml:"m:MaxDataServiceVersion,attr"`
 | 
			
		||||
	Schema                []EdmxSchema `xml:"Schema"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type EdmxMetadata struct {
 | 
			
		||||
	XMLName      xml.Name         `xml:"edmx:Edmx"`
 | 
			
		||||
	XmlnsEdmx    string           `xml:"xmlns:edmx,attr"`
 | 
			
		||||
	Version      string           `xml:"Version,attr"`
 | 
			
		||||
	DataServices EdmxDataServices `xml:"edmx:DataServices"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var Metadata = &EdmxMetadata{
 | 
			
		||||
	XmlnsEdmx: "http://schemas.microsoft.com/ado/2007/06/edmx",
 | 
			
		||||
	Version:   "1.0",
 | 
			
		||||
	DataServices: EdmxDataServices{
 | 
			
		||||
		XmlnsM:                "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata",
 | 
			
		||||
		DataServiceVersion:    "2.0",
 | 
			
		||||
		MaxDataServiceVersion: "2.0",
 | 
			
		||||
		Schema: []EdmxSchema{
 | 
			
		||||
			{
 | 
			
		||||
				Xmlns:     "http://schemas.microsoft.com/ado/2006/04/edm",
 | 
			
		||||
				Namespace: "NuGetGallery.OData",
 | 
			
		||||
				EntityType: &EdmxEntityType{
 | 
			
		||||
					Name:      "V2FeedPackage",
 | 
			
		||||
					HasStream: true,
 | 
			
		||||
					Keys: []EdmxPropertyRef{
 | 
			
		||||
						{Name: "Id"},
 | 
			
		||||
						{Name: "Version"},
 | 
			
		||||
					},
 | 
			
		||||
					Properties: []EdmxProperty{
 | 
			
		||||
						{
 | 
			
		||||
							Name: "Id",
 | 
			
		||||
							Type: "Edm.String",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "Version",
 | 
			
		||||
							Type: "Edm.String",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "NormalizedVersion",
 | 
			
		||||
							Type:     "Edm.String",
 | 
			
		||||
							Nullable: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "Authors",
 | 
			
		||||
							Type:     "Edm.String",
 | 
			
		||||
							Nullable: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "Created",
 | 
			
		||||
							Type: "Edm.DateTime",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "Dependencies",
 | 
			
		||||
							Type: "Edm.String",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "Description",
 | 
			
		||||
							Type: "Edm.String",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "DownloadCount",
 | 
			
		||||
							Type: "Edm.Int64",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "LastUpdated",
 | 
			
		||||
							Type: "Edm.DateTime",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "Published",
 | 
			
		||||
							Type: "Edm.DateTime",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name: "PackageSize",
 | 
			
		||||
							Type: "Edm.Int64",
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "ProjectUrl",
 | 
			
		||||
							Type:     "Edm.String",
 | 
			
		||||
							Nullable: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "ReleaseNotes",
 | 
			
		||||
							Type:     "Edm.String",
 | 
			
		||||
							Nullable: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "RequireLicenseAcceptance",
 | 
			
		||||
							Type:     "Edm.Boolean",
 | 
			
		||||
							Nullable: false,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "Title",
 | 
			
		||||
							Type:     "Edm.String",
 | 
			
		||||
							Nullable: true,
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:     "VersionDownloadCount",
 | 
			
		||||
							Type:     "Edm.Int64",
 | 
			
		||||
							Nullable: false,
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
			{
 | 
			
		||||
				Xmlns:     "http://schemas.microsoft.com/ado/2006/04/edm",
 | 
			
		||||
				Namespace: "NuGetGallery",
 | 
			
		||||
				EntityContainer: &EdmxEntityContainer{
 | 
			
		||||
					Name:                     "V2FeedContext",
 | 
			
		||||
					IsDefaultEntityContainer: true,
 | 
			
		||||
					EntitySet: EdmxEntitySet{
 | 
			
		||||
						Name:       "Packages",
 | 
			
		||||
						EntityType: "NuGetGallery.OData.V2FeedPackage",
 | 
			
		||||
					},
 | 
			
		||||
					FunctionImports: []EdmxFunctionImport{
 | 
			
		||||
						{
 | 
			
		||||
							Name:       "Search",
 | 
			
		||||
							ReturnType: "Collection(NuGetGallery.OData.V2FeedPackage)",
 | 
			
		||||
							EntitySet:  "Packages",
 | 
			
		||||
							Parameter: []EdmxFunctionParameter{
 | 
			
		||||
								{
 | 
			
		||||
									Name: "searchTerm",
 | 
			
		||||
									Type: "Edm.String",
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
						{
 | 
			
		||||
							Name:       "FindPackagesById",
 | 
			
		||||
							ReturnType: "Collection(NuGetGallery.OData.V2FeedPackage)",
 | 
			
		||||
							EntitySet:  "Packages",
 | 
			
		||||
							Parameter: []EdmxFunctionParameter{
 | 
			
		||||
								{
 | 
			
		||||
									Name: "id",
 | 
			
		||||
									Type: "Edm.String",
 | 
			
		||||
								},
 | 
			
		||||
							},
 | 
			
		||||
						},
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FeedEntryCategory struct {
 | 
			
		||||
	Term   string `xml:"term,attr"`
 | 
			
		||||
	Scheme string `xml:"scheme,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FeedEntryLink struct {
 | 
			
		||||
	Rel  string `xml:"rel,attr"`
 | 
			
		||||
	Href string `xml:"href,attr"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type TypedValue[T any] struct {
 | 
			
		||||
	Type  string `xml:"type,attr,omitempty"`
 | 
			
		||||
	Value T      `xml:",chardata"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FeedEntryProperties struct {
 | 
			
		||||
	Version                  string                `xml:"d:Version"`
 | 
			
		||||
	NormalizedVersion        string                `xml:"d:NormalizedVersion"`
 | 
			
		||||
	Authors                  string                `xml:"d:Authors"`
 | 
			
		||||
	Dependencies             string                `xml:"d:Dependencies"`
 | 
			
		||||
	Description              string                `xml:"d:Description"`
 | 
			
		||||
	VersionDownloadCount     TypedValue[int64]     `xml:"d:VersionDownloadCount"`
 | 
			
		||||
	DownloadCount            TypedValue[int64]     `xml:"d:DownloadCount"`
 | 
			
		||||
	PackageSize              TypedValue[int64]     `xml:"d:PackageSize"`
 | 
			
		||||
	Created                  TypedValue[time.Time] `xml:"d:Created"`
 | 
			
		||||
	LastUpdated              TypedValue[time.Time] `xml:"d:LastUpdated"`
 | 
			
		||||
	Published                TypedValue[time.Time] `xml:"d:Published"`
 | 
			
		||||
	ProjectURL               string                `xml:"d:ProjectUrl,omitempty"`
 | 
			
		||||
	ReleaseNotes             string                `xml:"d:ReleaseNotes,omitempty"`
 | 
			
		||||
	RequireLicenseAcceptance TypedValue[bool]      `xml:"d:RequireLicenseAcceptance"`
 | 
			
		||||
	Title                    string                `xml:"d:Title"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FeedEntry struct {
 | 
			
		||||
	XMLName    xml.Name             `xml:"entry"`
 | 
			
		||||
	Xmlns      string               `xml:"xmlns,attr,omitempty"`
 | 
			
		||||
	XmlnsD     string               `xml:"xmlns:d,attr,omitempty"`
 | 
			
		||||
	XmlnsM     string               `xml:"xmlns:m,attr,omitempty"`
 | 
			
		||||
	Base       string               `xml:"xml:base,attr,omitempty"`
 | 
			
		||||
	ID         string               `xml:"id"`
 | 
			
		||||
	Category   FeedEntryCategory    `xml:"category"`
 | 
			
		||||
	Links      []FeedEntryLink      `xml:"link"`
 | 
			
		||||
	Title      TypedValue[string]   `xml:"title"`
 | 
			
		||||
	Updated    time.Time            `xml:"updated"`
 | 
			
		||||
	Author     string               `xml:"author>name"`
 | 
			
		||||
	Summary    string               `xml:"summary"`
 | 
			
		||||
	Properties *FeedEntryProperties `xml:"m:properties"`
 | 
			
		||||
	Content    string               `xml:",innerxml"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type FeedResponse struct {
 | 
			
		||||
	XMLName xml.Name           `xml:"feed"`
 | 
			
		||||
	Xmlns   string             `xml:"xmlns,attr,omitempty"`
 | 
			
		||||
	XmlnsD  string             `xml:"xmlns:d,attr,omitempty"`
 | 
			
		||||
	XmlnsM  string             `xml:"xmlns:m,attr,omitempty"`
 | 
			
		||||
	Base    string             `xml:"xml:base,attr,omitempty"`
 | 
			
		||||
	ID      string             `xml:"id"`
 | 
			
		||||
	Title   TypedValue[string] `xml:"title"`
 | 
			
		||||
	Updated time.Time          `xml:"updated"`
 | 
			
		||||
	Link    FeedEntryLink      `xml:"link"`
 | 
			
		||||
	Entries []*FeedEntry       `xml:"entry"`
 | 
			
		||||
	Count   int64              `xml:"m:count"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createFeedResponse(l *linkBuilder, totalEntries int64, pds []*packages_model.PackageDescriptor) *FeedResponse {
 | 
			
		||||
	entries := make([]*FeedEntry, 0, len(pds))
 | 
			
		||||
	for _, pd := range pds {
 | 
			
		||||
		entries = append(entries, createEntry(l, pd, false))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &FeedResponse{
 | 
			
		||||
		Xmlns:   "http://www.w3.org/2005/Atom",
 | 
			
		||||
		Base:    l.Base,
 | 
			
		||||
		XmlnsD:  "http://schemas.microsoft.com/ado/2007/08/dataservices",
 | 
			
		||||
		XmlnsM:  "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata",
 | 
			
		||||
		ID:      "http://schemas.datacontract.org/2004/07/",
 | 
			
		||||
		Updated: time.Now(),
 | 
			
		||||
		Link:    FeedEntryLink{Rel: "self", Href: l.Base},
 | 
			
		||||
		Count:   totalEntries,
 | 
			
		||||
		Entries: entries,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createEntryResponse(l *linkBuilder, pd *packages_model.PackageDescriptor) *FeedEntry {
 | 
			
		||||
	return createEntry(l, pd, true)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createEntry(l *linkBuilder, pd *packages_model.PackageDescriptor, withNamespace bool) *FeedEntry {
 | 
			
		||||
	metadata := pd.Metadata.(*nuget_module.Metadata)
 | 
			
		||||
 | 
			
		||||
	id := l.GetPackageMetadataURL(pd.Package.Name, pd.Version.Version)
 | 
			
		||||
 | 
			
		||||
	// Workaround to force a self-closing tag to satisfy XmlReader.IsEmptyElement used by the NuGet client.
 | 
			
		||||
	// https://learn.microsoft.com/en-us/dotnet/api/system.xml.xmlreader.isemptyelement
 | 
			
		||||
	content := `<content type="application/zip" src="` + l.GetPackageDownloadURL(pd.Package.Name, pd.Version.Version) + `"/>`
 | 
			
		||||
 | 
			
		||||
	createdValue := TypedValue[time.Time]{
 | 
			
		||||
		Type:  "Edm.DateTime",
 | 
			
		||||
		Value: pd.Version.CreatedUnix.AsLocalTime(),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	entry := &FeedEntry{
 | 
			
		||||
		ID:       id,
 | 
			
		||||
		Category: FeedEntryCategory{Term: "NuGetGallery.OData.V2FeedPackage", Scheme: "http://schemas.microsoft.com/ado/2007/08/dataservices/scheme"},
 | 
			
		||||
		Links: []FeedEntryLink{
 | 
			
		||||
			{Rel: "self", Href: id},
 | 
			
		||||
			{Rel: "edit", Href: id},
 | 
			
		||||
		},
 | 
			
		||||
		Title:   TypedValue[string]{Type: "text", Value: pd.Package.Name},
 | 
			
		||||
		Updated: pd.Version.CreatedUnix.AsLocalTime(),
 | 
			
		||||
		Author:  metadata.Authors,
 | 
			
		||||
		Content: content,
 | 
			
		||||
		Properties: &FeedEntryProperties{
 | 
			
		||||
			Version:                  pd.Version.Version,
 | 
			
		||||
			NormalizedVersion:        normalizeVersion(pd.SemVer),
 | 
			
		||||
			Authors:                  metadata.Authors,
 | 
			
		||||
			Dependencies:             buildDependencyString(metadata),
 | 
			
		||||
			Description:              metadata.Description,
 | 
			
		||||
			VersionDownloadCount:     TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
 | 
			
		||||
			DownloadCount:            TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
 | 
			
		||||
			PackageSize:              TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
 | 
			
		||||
			Created:                  createdValue,
 | 
			
		||||
			LastUpdated:              createdValue,
 | 
			
		||||
			Published:                createdValue,
 | 
			
		||||
			ProjectURL:               metadata.ProjectURL,
 | 
			
		||||
			ReleaseNotes:             metadata.ReleaseNotes,
 | 
			
		||||
			RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance},
 | 
			
		||||
			Title:                    pd.Package.Name,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if withNamespace {
 | 
			
		||||
		entry.Xmlns = "http://www.w3.org/2005/Atom"
 | 
			
		||||
		entry.Base = l.Base
 | 
			
		||||
		entry.XmlnsD = "http://schemas.microsoft.com/ado/2007/08/dataservices"
 | 
			
		||||
		entry.XmlnsM = "http://schemas.microsoft.com/ado/2007/08/dataservices/metadata"
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return entry
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func buildDependencyString(metadata *nuget_module.Metadata) string {
 | 
			
		||||
	var b strings.Builder
 | 
			
		||||
	first := true
 | 
			
		||||
	for group, deps := range metadata.Dependencies {
 | 
			
		||||
		for _, dep := range deps {
 | 
			
		||||
			if !first {
 | 
			
		||||
				b.WriteByte('|')
 | 
			
		||||
			}
 | 
			
		||||
			first = false
 | 
			
		||||
 | 
			
		||||
			b.WriteString(dep.ID)
 | 
			
		||||
			b.WriteByte(':')
 | 
			
		||||
			b.WriteString(dep.Version)
 | 
			
		||||
			b.WriteByte(':')
 | 
			
		||||
			b.WriteString(group)
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return b.String()
 | 
			
		||||
}
 | 
			
		||||
@@ -16,36 +16,19 @@ import (
 | 
			
		||||
	"github.com/hashicorp/go-version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// ServiceIndexResponse https://docs.microsoft.com/en-us/nuget/api/service-index#resources
 | 
			
		||||
type ServiceIndexResponse struct {
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
 | 
			
		||||
type ServiceIndexResponseV3 struct {
 | 
			
		||||
	Version   string            `json:"version"`
 | 
			
		||||
	Resources []ServiceResource `json:"resources"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServiceResource https://docs.microsoft.com/en-us/nuget/api/service-index#resource
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/service-index#resource
 | 
			
		||||
type ServiceResource struct {
 | 
			
		||||
	ID   string `json:"@id"`
 | 
			
		||||
	Type string `json:"@type"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func createServiceIndexResponse(root string) *ServiceIndexResponse {
 | 
			
		||||
	return &ServiceIndexResponse{
 | 
			
		||||
		Version: "3.0.0",
 | 
			
		||||
		Resources: []ServiceResource{
 | 
			
		||||
			{ID: root + "/query", Type: "SearchQueryService"},
 | 
			
		||||
			{ID: root + "/query", Type: "SearchQueryService/3.0.0-beta"},
 | 
			
		||||
			{ID: root + "/query", Type: "SearchQueryService/3.0.0-rc"},
 | 
			
		||||
			{ID: root + "/registration", Type: "RegistrationsBaseUrl"},
 | 
			
		||||
			{ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-beta"},
 | 
			
		||||
			{ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-rc"},
 | 
			
		||||
			{ID: root + "/package", Type: "PackageBaseAddress/3.0.0"},
 | 
			
		||||
			{ID: root, Type: "PackagePublish/2.0.0"},
 | 
			
		||||
			{ID: root + "/symbolpackage", Type: "SymbolPackagePublish/4.9.0"},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistrationIndexResponse https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#response
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#response
 | 
			
		||||
type RegistrationIndexResponse struct {
 | 
			
		||||
	RegistrationIndexURL string                   `json:"@id"`
 | 
			
		||||
	Type                 []string                 `json:"@type"`
 | 
			
		||||
@@ -53,7 +36,7 @@ type RegistrationIndexResponse struct {
 | 
			
		||||
	Pages                []*RegistrationIndexPage `json:"items"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistrationIndexPage https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-page-object
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-page-object
 | 
			
		||||
type RegistrationIndexPage struct {
 | 
			
		||||
	RegistrationPageURL string                       `json:"@id"`
 | 
			
		||||
	Lower               string                       `json:"lower"`
 | 
			
		||||
@@ -62,14 +45,14 @@ type RegistrationIndexPage struct {
 | 
			
		||||
	Items               []*RegistrationIndexPageItem `json:"items"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistrationIndexPageItem https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf-object-in-a-page
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf-object-in-a-page
 | 
			
		||||
type RegistrationIndexPageItem struct {
 | 
			
		||||
	RegistrationLeafURL string        `json:"@id"`
 | 
			
		||||
	PackageContentURL   string        `json:"packageContent"`
 | 
			
		||||
	CatalogEntry        *CatalogEntry `json:"catalogEntry"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// CatalogEntry https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#catalog-entry
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#catalog-entry
 | 
			
		||||
type CatalogEntry struct {
 | 
			
		||||
	CatalogLeafURL           string                    `json:"@id"`
 | 
			
		||||
	PackageContentURL        string                    `json:"packageContent"`
 | 
			
		||||
@@ -83,13 +66,13 @@ type CatalogEntry struct {
 | 
			
		||||
	DependencyGroups         []*PackageDependencyGroup `json:"dependencyGroups"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageDependencyGroup https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#package-dependency-group
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#package-dependency-group
 | 
			
		||||
type PackageDependencyGroup struct {
 | 
			
		||||
	TargetFramework string               `json:"targetFramework"`
 | 
			
		||||
	Dependencies    []*PackageDependency `json:"dependencies"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageDependency https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#package-dependency
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#package-dependency
 | 
			
		||||
type PackageDependency struct {
 | 
			
		||||
	ID    string `json:"id"`
 | 
			
		||||
	Range string `json:"range"`
 | 
			
		||||
@@ -162,7 +145,7 @@ func createDependencyGroups(pd *packages_model.PackageDescriptor) []*PackageDepe
 | 
			
		||||
	return dependencyGroups
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistrationLeafResponse https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf
 | 
			
		||||
type RegistrationLeafResponse struct {
 | 
			
		||||
	RegistrationLeafURL  string    `json:"@id"`
 | 
			
		||||
	Type                 []string  `json:"@type"`
 | 
			
		||||
@@ -183,7 +166,7 @@ func createRegistrationLeafResponse(l *linkBuilder, pd *packages_model.PackageDe
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PackageVersionsResponse https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#response
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#response
 | 
			
		||||
type PackageVersionsResponse struct {
 | 
			
		||||
	Versions []string `json:"versions"`
 | 
			
		||||
}
 | 
			
		||||
@@ -199,13 +182,13 @@ func createPackageVersionsResponse(pds []*packages_model.PackageDescriptor) *Pac
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchResultResponse https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#response
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#response
 | 
			
		||||
type SearchResultResponse struct {
 | 
			
		||||
	TotalHits int64           `json:"totalHits"`
 | 
			
		||||
	Data      []*SearchResult `json:"data"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchResult https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-result
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-result
 | 
			
		||||
type SearchResult struct {
 | 
			
		||||
	ID                   string                 `json:"id"`
 | 
			
		||||
	Version              string                 `json:"version"`
 | 
			
		||||
@@ -216,7 +199,7 @@ type SearchResult struct {
 | 
			
		||||
	RegistrationIndexURL string                 `json:"registration"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchResultVersion https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-result
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-result
 | 
			
		||||
type SearchResultVersion struct {
 | 
			
		||||
	RegistrationLeafURL string `json:"@id"`
 | 
			
		||||
	Version             string `json:"version"`
 | 
			
		||||
@@ -26,3 +26,8 @@ func (l *linkBuilder) GetRegistrationLeafURL(id, version string) string {
 | 
			
		||||
func (l *linkBuilder) GetPackageDownloadURL(id, version string) string {
 | 
			
		||||
	return fmt.Sprintf("%s/package/%s/%s/%s.%s.nupkg", l.Base, id, version, id, version)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetPackageMetadataURL builds the package metadata url
 | 
			
		||||
func (l *linkBuilder) GetPackageMetadataURL(id, version string) string {
 | 
			
		||||
	return fmt.Sprintf("%s/Packages(Id='%s',Version='%s')", l.Base, id, version)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -5,15 +5,18 @@
 | 
			
		||||
package nuget
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"errors"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"regexp"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	packages_model "code.gitea.io/gitea/models/packages"
 | 
			
		||||
	"code.gitea.io/gitea/modules/context"
 | 
			
		||||
	"code.gitea.io/gitea/modules/log"
 | 
			
		||||
	packages_module "code.gitea.io/gitea/modules/packages"
 | 
			
		||||
	nuget_module "code.gitea.io/gitea/modules/packages/nuget"
 | 
			
		||||
	"code.gitea.io/gitea/modules/setting"
 | 
			
		||||
@@ -30,15 +33,121 @@ func apiError(ctx *context.Context, status int, obj interface{}) {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// ServiceIndex https://docs.microsoft.com/en-us/nuget/api/service-index
 | 
			
		||||
func ServiceIndex(ctx *context.Context) {
 | 
			
		||||
	resp := createServiceIndexResponse(setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget")
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, resp)
 | 
			
		||||
func xmlResponse(ctx *context.Context, status int, obj interface{}) {
 | 
			
		||||
	ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
 | 
			
		||||
	ctx.Resp.WriteHeader(status)
 | 
			
		||||
	if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
 | 
			
		||||
		log.Error("Write failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	if err := xml.NewEncoder(ctx.Resp).Encode(obj); err != nil {
 | 
			
		||||
		log.Error("XML encode failed: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// SearchService https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
 | 
			
		||||
func SearchService(ctx *context.Context) {
 | 
			
		||||
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
 | 
			
		||||
func ServiceIndexV2(ctx *context.Context) {
 | 
			
		||||
	base := setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"
 | 
			
		||||
 | 
			
		||||
	xmlResponse(ctx, http.StatusOK, &ServiceIndexResponseV2{
 | 
			
		||||
		Base:      base,
 | 
			
		||||
		Xmlns:     "http://www.w3.org/2007/app",
 | 
			
		||||
		XmlnsAtom: "http://www.w3.org/2005/Atom",
 | 
			
		||||
		Workspace: ServiceWorkspace{
 | 
			
		||||
			Title: AtomTitle{
 | 
			
		||||
				Type: "text",
 | 
			
		||||
				Text: "Default",
 | 
			
		||||
			},
 | 
			
		||||
			Collection: ServiceCollection{
 | 
			
		||||
				Href: "Packages",
 | 
			
		||||
				Title: AtomTitle{
 | 
			
		||||
					Type: "text",
 | 
			
		||||
					Text: "Packages",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/service-index
 | 
			
		||||
func ServiceIndexV3(ctx *context.Context) {
 | 
			
		||||
	root := setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"
 | 
			
		||||
 | 
			
		||||
	ctx.JSON(http.StatusOK, &ServiceIndexResponseV3{
 | 
			
		||||
		Version: "3.0.0",
 | 
			
		||||
		Resources: []ServiceResource{
 | 
			
		||||
			{ID: root + "/query", Type: "SearchQueryService"},
 | 
			
		||||
			{ID: root + "/query", Type: "SearchQueryService/3.0.0-beta"},
 | 
			
		||||
			{ID: root + "/query", Type: "SearchQueryService/3.0.0-rc"},
 | 
			
		||||
			{ID: root + "/registration", Type: "RegistrationsBaseUrl"},
 | 
			
		||||
			{ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-beta"},
 | 
			
		||||
			{ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-rc"},
 | 
			
		||||
			{ID: root + "/package", Type: "PackageBaseAddress/3.0.0"},
 | 
			
		||||
			{ID: root, Type: "PackagePublish/2.0.0"},
 | 
			
		||||
			{ID: root + "/symbolpackage", Type: "SymbolPackagePublish/4.9.0"},
 | 
			
		||||
		},
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/LegacyFeedCapabilityResourceV2Feed.cs
 | 
			
		||||
func FeedCapabilityResource(ctx *context.Context) {
 | 
			
		||||
	xmlResponse(ctx, http.StatusOK, Metadata)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var searchTermExtract = regexp.MustCompile(`'([^']+)'`)
 | 
			
		||||
 | 
			
		||||
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
 | 
			
		||||
func SearchServiceV2(ctx *context.Context) {
 | 
			
		||||
	searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
 | 
			
		||||
	if searchTerm == "" {
 | 
			
		||||
		// $filter contains a query like:
 | 
			
		||||
		// (((Id ne null) and substringof('microsoft',tolower(Id)))
 | 
			
		||||
		// We don't support these queries, just extract the search term.
 | 
			
		||||
		match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter"))
 | 
			
		||||
		if len(match) == 2 {
 | 
			
		||||
			searchTerm = strings.TrimSpace(match[1])
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	skip, take := ctx.FormInt("skip"), ctx.FormInt("take")
 | 
			
		||||
	if skip == 0 {
 | 
			
		||||
		skip = ctx.FormInt("$skip")
 | 
			
		||||
	}
 | 
			
		||||
	if take == 0 {
 | 
			
		||||
		take = ctx.FormInt("$top")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
 | 
			
		||||
		OwnerID:    ctx.Package.Owner.ID,
 | 
			
		||||
		Type:       packages_model.TypeNuGet,
 | 
			
		||||
		Name:       packages_model.SearchValue{Value: searchTerm},
 | 
			
		||||
		IsInternal: util.OptionalBoolFalse,
 | 
			
		||||
		Paginator: db.NewAbsoluteListOptions(
 | 
			
		||||
			skip,
 | 
			
		||||
			take,
 | 
			
		||||
		),
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp := createFeedResponse(
 | 
			
		||||
		&linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
 | 
			
		||||
		total,
 | 
			
		||||
		pds,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	xmlResponse(ctx, http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
 | 
			
		||||
func SearchServiceV3(ctx *context.Context) {
 | 
			
		||||
	pvs, count, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
 | 
			
		||||
		OwnerID:    ctx.Package.Owner.ID,
 | 
			
		||||
		Type:       packages_model.TypeNuGet,
 | 
			
		||||
@@ -69,7 +178,7 @@ func SearchService(ctx *context.Context) {
 | 
			
		||||
	ctx.JSON(http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistrationIndex https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-index
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-index
 | 
			
		||||
func RegistrationIndex(ctx *context.Context) {
 | 
			
		||||
	packageName := ctx.Params("id")
 | 
			
		||||
 | 
			
		||||
@@ -97,8 +206,37 @@ func RegistrationIndex(ctx *context.Context) {
 | 
			
		||||
	ctx.JSON(http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// RegistrationLeaf https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf
 | 
			
		||||
func RegistrationLeaf(ctx *context.Context) {
 | 
			
		||||
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
 | 
			
		||||
func RegistrationLeafV2(ctx *context.Context) {
 | 
			
		||||
	packageName := ctx.Params("id")
 | 
			
		||||
	packageVersion := ctx.Params("version")
 | 
			
		||||
 | 
			
		||||
	pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		if err == packages_model.ErrPackageNotExist {
 | 
			
		||||
			apiError(ctx, http.StatusNotFound, err)
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pd, err := packages_model.GetPackageDescriptor(ctx, pv)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp := createEntryResponse(
 | 
			
		||||
		&linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
 | 
			
		||||
		pd,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	xmlResponse(ctx, http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf
 | 
			
		||||
func RegistrationLeafV3(ctx *context.Context) {
 | 
			
		||||
	packageName := ctx.Params("id")
 | 
			
		||||
	packageVersion := strings.TrimSuffix(ctx.Params("version"), ".json")
 | 
			
		||||
 | 
			
		||||
@@ -126,8 +264,33 @@ func RegistrationLeaf(ctx *context.Context) {
 | 
			
		||||
	ctx.JSON(http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// EnumeratePackageVersions https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions
 | 
			
		||||
func EnumeratePackageVersions(ctx *context.Context) {
 | 
			
		||||
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
 | 
			
		||||
func EnumeratePackageVersionsV2(ctx *context.Context) {
 | 
			
		||||
	packageName := strings.Trim(ctx.FormTrim("id"), "'")
 | 
			
		||||
 | 
			
		||||
	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		apiError(ctx, http.StatusInternalServerError, err)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp := createFeedResponse(
 | 
			
		||||
		&linkBuilder{setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
 | 
			
		||||
		int64(len(pds)),
 | 
			
		||||
		pds,
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	xmlResponse(ctx, http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions
 | 
			
		||||
func EnumeratePackageVersionsV3(ctx *context.Context) {
 | 
			
		||||
	packageName := ctx.Params("id")
 | 
			
		||||
 | 
			
		||||
	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
 | 
			
		||||
@@ -151,7 +314,7 @@ func EnumeratePackageVersions(ctx *context.Context) {
 | 
			
		||||
	ctx.JSON(http.StatusOK, resp)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DownloadPackageFile https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
 | 
			
		||||
// https://docs.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")
 | 
			
		||||
@@ -350,7 +513,7 @@ func processUploadedFile(ctx *context.Context, expectedType nuget_module.Package
 | 
			
		||||
	return np, buf, closables
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// DownloadSymbolFile https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
 | 
			
		||||
// https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
 | 
			
		||||
func DownloadSymbolFile(ctx *context.Context) {
 | 
			
		||||
	filename := ctx.Params("filename")
 | 
			
		||||
	guid := ctx.Params("guid")[:32]
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,13 @@ import (
 | 
			
		||||
	"archive/zip"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/xml"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/http/httptest"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/gitea/models/db"
 | 
			
		||||
	"code.gitea.io/gitea/models/packages"
 | 
			
		||||
@@ -31,9 +34,45 @@ func addNuGetAPIKeyHeader(request *http.Request, token string) *http.Request {
 | 
			
		||||
	return request
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func decodeXML(t testing.TB, resp *httptest.ResponseRecorder, v interface{}) {
 | 
			
		||||
	t.Helper()
 | 
			
		||||
 | 
			
		||||
	assert.NoError(t, xml.NewDecoder(resp.Body).Decode(v))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func TestPackageNuGet(t *testing.T) {
 | 
			
		||||
	defer tests.PrepareTestEnv(t)()
 | 
			
		||||
 | 
			
		||||
	type FeedEntryProperties struct {
 | 
			
		||||
		Version                  string                      `xml:"Version"`
 | 
			
		||||
		NormalizedVersion        string                      `xml:"NormalizedVersion"`
 | 
			
		||||
		Authors                  string                      `xml:"Authors"`
 | 
			
		||||
		Dependencies             string                      `xml:"Dependencies"`
 | 
			
		||||
		Description              string                      `xml:"Description"`
 | 
			
		||||
		VersionDownloadCount     nuget.TypedValue[int64]     `xml:"VersionDownloadCount"`
 | 
			
		||||
		DownloadCount            nuget.TypedValue[int64]     `xml:"DownloadCount"`
 | 
			
		||||
		PackageSize              nuget.TypedValue[int64]     `xml:"PackageSize"`
 | 
			
		||||
		Created                  nuget.TypedValue[time.Time] `xml:"Created"`
 | 
			
		||||
		LastUpdated              nuget.TypedValue[time.Time] `xml:"LastUpdated"`
 | 
			
		||||
		Published                nuget.TypedValue[time.Time] `xml:"Published"`
 | 
			
		||||
		ProjectURL               string                      `xml:"ProjectUrl,omitempty"`
 | 
			
		||||
		ReleaseNotes             string                      `xml:"ReleaseNotes,omitempty"`
 | 
			
		||||
		RequireLicenseAcceptance nuget.TypedValue[bool]      `xml:"RequireLicenseAcceptance"`
 | 
			
		||||
		Title                    string                      `xml:"Title"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type FeedEntry struct {
 | 
			
		||||
		XMLName    xml.Name             `xml:"entry"`
 | 
			
		||||
		Properties *FeedEntryProperties `xml:"properties"`
 | 
			
		||||
		Content    string               `xml:",innerxml"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	type FeedResponse struct {
 | 
			
		||||
		XMLName xml.Name     `xml:"feed"`
 | 
			
		||||
		Entries []*FeedEntry `xml:"entry"`
 | 
			
		||||
		Count   int64        `xml:"count"`
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	user := unittest.AssertExistsAndLoadBean(t, &user_model.User{ID: 2})
 | 
			
		||||
	token := getUserToken(t, user.Name)
 | 
			
		||||
 | 
			
		||||
@@ -54,9 +93,11 @@ func TestPackageNuGet(t *testing.T) {
 | 
			
		||||
		<version>` + packageVersion + `</version>
 | 
			
		||||
		<authors>` + packageAuthors + `</authors>
 | 
			
		||||
		<description>` + packageDescription + `</description>
 | 
			
		||||
		<group targetFramework=".NETStandard2.0">
 | 
			
		||||
			<dependency id="Microsoft.CSharp" version="4.5.0" />
 | 
			
		||||
		</group>
 | 
			
		||||
		<dependencies>
 | 
			
		||||
			<group targetFramework=".NETStandard2.0">
 | 
			
		||||
				<dependency id="Microsoft.CSharp" version="4.5.0" />
 | 
			
		||||
			</group>
 | 
			
		||||
		</dependencies>
 | 
			
		||||
	  </metadata>
 | 
			
		||||
	</package>`))
 | 
			
		||||
	archive.Close()
 | 
			
		||||
@@ -67,60 +108,101 @@ func TestPackageNuGet(t *testing.T) {
 | 
			
		||||
	t.Run("ServiceIndex", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
 | 
			
		||||
		t.Run("v2", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		cases := []struct {
 | 
			
		||||
			Owner        string
 | 
			
		||||
			UseBasicAuth bool
 | 
			
		||||
			UseTokenAuth bool
 | 
			
		||||
		}{
 | 
			
		||||
			{privateUser.Name, false, false},
 | 
			
		||||
			{privateUser.Name, true, false},
 | 
			
		||||
			{privateUser.Name, false, true},
 | 
			
		||||
			{user.Name, false, false},
 | 
			
		||||
			{user.Name, true, false},
 | 
			
		||||
			{user.Name, false, true},
 | 
			
		||||
		}
 | 
			
		||||
			privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
 | 
			
		||||
 | 
			
		||||
		for _, c := range cases {
 | 
			
		||||
			url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
 | 
			
		||||
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
 | 
			
		||||
			if c.UseBasicAuth {
 | 
			
		||||
				req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
			} else if c.UseTokenAuth {
 | 
			
		||||
				req = addNuGetAPIKeyHeader(req, token)
 | 
			
		||||
			cases := []struct {
 | 
			
		||||
				Owner        string
 | 
			
		||||
				UseBasicAuth bool
 | 
			
		||||
				UseTokenAuth bool
 | 
			
		||||
			}{
 | 
			
		||||
				{privateUser.Name, false, false},
 | 
			
		||||
				{privateUser.Name, true, false},
 | 
			
		||||
				{privateUser.Name, false, true},
 | 
			
		||||
				{user.Name, false, false},
 | 
			
		||||
				{user.Name, true, false},
 | 
			
		||||
				{user.Name, false, true},
 | 
			
		||||
			}
 | 
			
		||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
			var result nuget.ServiceIndexResponse
 | 
			
		||||
			DecodeJSON(t, resp, &result)
 | 
			
		||||
			for _, c := range cases {
 | 
			
		||||
				url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
 | 
			
		||||
 | 
			
		||||
			assert.Equal(t, "3.0.0", result.Version)
 | 
			
		||||
			assert.NotEmpty(t, result.Resources)
 | 
			
		||||
				req := NewRequest(t, "GET", url)
 | 
			
		||||
				if c.UseBasicAuth {
 | 
			
		||||
					req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
				} else if c.UseTokenAuth {
 | 
			
		||||
					req = addNuGetAPIKeyHeader(req, token)
 | 
			
		||||
				}
 | 
			
		||||
				resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
			root := setting.AppURL + url[1:]
 | 
			
		||||
			for _, r := range result.Resources {
 | 
			
		||||
				switch r.Type {
 | 
			
		||||
				case "SearchQueryService":
 | 
			
		||||
					fallthrough
 | 
			
		||||
				case "SearchQueryService/3.0.0-beta":
 | 
			
		||||
					fallthrough
 | 
			
		||||
				case "SearchQueryService/3.0.0-rc":
 | 
			
		||||
					assert.Equal(t, root+"/query", r.ID)
 | 
			
		||||
				case "RegistrationsBaseUrl":
 | 
			
		||||
					fallthrough
 | 
			
		||||
				case "RegistrationsBaseUrl/3.0.0-beta":
 | 
			
		||||
					fallthrough
 | 
			
		||||
				case "RegistrationsBaseUrl/3.0.0-rc":
 | 
			
		||||
					assert.Equal(t, root+"/registration", r.ID)
 | 
			
		||||
				case "PackageBaseAddress/3.0.0":
 | 
			
		||||
					assert.Equal(t, root+"/package", r.ID)
 | 
			
		||||
				case "PackagePublish/2.0.0":
 | 
			
		||||
					assert.Equal(t, root, r.ID)
 | 
			
		||||
				var result nuget.ServiceIndexResponseV2
 | 
			
		||||
				decodeXML(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
				assert.Equal(t, setting.AppURL+url[1:], result.Base)
 | 
			
		||||
				assert.Equal(t, "Packages", result.Workspace.Collection.Href)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("v3", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			privateUser := unittest.AssertExistsAndLoadBean(t, &user_model.User{Visibility: structs.VisibleTypePrivate})
 | 
			
		||||
 | 
			
		||||
			cases := []struct {
 | 
			
		||||
				Owner        string
 | 
			
		||||
				UseBasicAuth bool
 | 
			
		||||
				UseTokenAuth bool
 | 
			
		||||
			}{
 | 
			
		||||
				{privateUser.Name, false, false},
 | 
			
		||||
				{privateUser.Name, true, false},
 | 
			
		||||
				{privateUser.Name, false, true},
 | 
			
		||||
				{user.Name, false, false},
 | 
			
		||||
				{user.Name, true, false},
 | 
			
		||||
				{user.Name, false, true},
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			for _, c := range cases {
 | 
			
		||||
				url := fmt.Sprintf("/api/packages/%s/nuget", c.Owner)
 | 
			
		||||
 | 
			
		||||
				req := NewRequest(t, "GET", fmt.Sprintf("%s/index.json", url))
 | 
			
		||||
				if c.UseBasicAuth {
 | 
			
		||||
					req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
				} else if c.UseTokenAuth {
 | 
			
		||||
					req = addNuGetAPIKeyHeader(req, token)
 | 
			
		||||
				}
 | 
			
		||||
				resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
				var result nuget.ServiceIndexResponseV3
 | 
			
		||||
				DecodeJSON(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
				assert.Equal(t, "3.0.0", result.Version)
 | 
			
		||||
				assert.NotEmpty(t, result.Resources)
 | 
			
		||||
 | 
			
		||||
				root := setting.AppURL + url[1:]
 | 
			
		||||
				for _, r := range result.Resources {
 | 
			
		||||
					switch r.Type {
 | 
			
		||||
					case "SearchQueryService":
 | 
			
		||||
						fallthrough
 | 
			
		||||
					case "SearchQueryService/3.0.0-beta":
 | 
			
		||||
						fallthrough
 | 
			
		||||
					case "SearchQueryService/3.0.0-rc":
 | 
			
		||||
						assert.Equal(t, root+"/query", r.ID)
 | 
			
		||||
					case "RegistrationsBaseUrl":
 | 
			
		||||
						fallthrough
 | 
			
		||||
					case "RegistrationsBaseUrl/3.0.0-beta":
 | 
			
		||||
						fallthrough
 | 
			
		||||
					case "RegistrationsBaseUrl/3.0.0-rc":
 | 
			
		||||
						assert.Equal(t, root+"/registration", r.ID)
 | 
			
		||||
					case "PackageBaseAddress/3.0.0":
 | 
			
		||||
						assert.Equal(t, root+"/package", r.ID)
 | 
			
		||||
					case "PackagePublish/2.0.0":
 | 
			
		||||
						assert.Equal(t, root, r.ID)
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Upload", func(t *testing.T) {
 | 
			
		||||
@@ -305,17 +387,57 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
 | 
			
		||||
			{"test", 1, 10, 1, 0},
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		for i, c := range cases {
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
 | 
			
		||||
			req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		t.Run("v2", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			var result nuget.SearchResultResponse
 | 
			
		||||
			DecodeJSON(t, resp, &result)
 | 
			
		||||
			t.Run("Search()", func(t *testing.T) {
 | 
			
		||||
				defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i)
 | 
			
		||||
			assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i)
 | 
			
		||||
		}
 | 
			
		||||
				for i, c := range cases {
 | 
			
		||||
					req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?searchTerm='%s'&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
 | 
			
		||||
					req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
					resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
					var result FeedResponse
 | 
			
		||||
					decodeXML(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
					assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
 | 
			
		||||
					assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			t.Run("Packages()", func(t *testing.T) {
 | 
			
		||||
				defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
				for i, c := range cases {
 | 
			
		||||
					req := NewRequest(t, "GET", fmt.Sprintf("%s/Search()?$filter=substringof('%s',tolower(Id))&$skip=%d&$top=%d", url, c.Query, c.Skip, c.Take))
 | 
			
		||||
					req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
					resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
					var result FeedResponse
 | 
			
		||||
					decodeXML(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
					assert.Equal(t, c.ExpectedTotal, result.Count, "case %d: unexpected total hits", i)
 | 
			
		||||
					assert.Len(t, result.Entries, c.ExpectedResults, "case %d: unexpected result count", i)
 | 
			
		||||
				}
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("v3", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			for i, c := range cases {
 | 
			
		||||
				req := NewRequest(t, "GET", fmt.Sprintf("%s/query?q=%s&skip=%d&take=%d", url, c.Query, c.Skip, c.Take))
 | 
			
		||||
				req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
				resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
				var result nuget.SearchResultResponse
 | 
			
		||||
				DecodeJSON(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
				assert.Equal(t, c.ExpectedTotal, result.TotalHits, "case %d: unexpected total hits", i)
 | 
			
		||||
				assert.Len(t, result.Data, c.ExpectedResults, "case %d: unexpected result count", i)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("RegistrationService", func(t *testing.T) {
 | 
			
		||||
@@ -352,31 +474,70 @@ AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`)
 | 
			
		||||
		t.Run("RegistrationLeaf", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/%s.json", url, packageName, packageVersion))
 | 
			
		||||
			req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
			t.Run("v2", func(t *testing.T) {
 | 
			
		||||
				defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			var result nuget.RegistrationLeafResponse
 | 
			
		||||
			DecodeJSON(t, resp, &result)
 | 
			
		||||
				req := NewRequest(t, "GET", fmt.Sprintf("%s/Packages(Id='%s',Version='%s')", url, packageName, packageVersion))
 | 
			
		||||
				req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
				resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
			assert.Equal(t, leafURL, result.RegistrationLeafURL)
 | 
			
		||||
			assert.Equal(t, contentURL, result.PackageContentURL)
 | 
			
		||||
			assert.Equal(t, indexURL, result.RegistrationIndexURL)
 | 
			
		||||
				var result FeedEntry
 | 
			
		||||
				decodeXML(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
				assert.Equal(t, packageName, result.Properties.Title)
 | 
			
		||||
				assert.Equal(t, packageVersion, result.Properties.Version)
 | 
			
		||||
				assert.Equal(t, packageAuthors, result.Properties.Authors)
 | 
			
		||||
				assert.Equal(t, packageDescription, result.Properties.Description)
 | 
			
		||||
				assert.Equal(t, "Microsoft.CSharp:4.5.0:.NETStandard2.0", result.Properties.Dependencies)
 | 
			
		||||
			})
 | 
			
		||||
 | 
			
		||||
			t.Run("v3", func(t *testing.T) {
 | 
			
		||||
				defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
				req := NewRequest(t, "GET", fmt.Sprintf("%s/registration/%s/%s.json", url, packageName, packageVersion))
 | 
			
		||||
				req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
				resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
				var result nuget.RegistrationLeafResponse
 | 
			
		||||
				DecodeJSON(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
				assert.Equal(t, leafURL, result.RegistrationLeafURL)
 | 
			
		||||
				assert.Equal(t, contentURL, result.PackageContentURL)
 | 
			
		||||
				assert.Equal(t, indexURL, result.RegistrationIndexURL)
 | 
			
		||||
			})
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("PackageService", func(t *testing.T) {
 | 
			
		||||
		defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/index.json", url, packageName))
 | 
			
		||||
		req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
		resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
		t.Run("v2", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
		var result nuget.PackageVersionsResponse
 | 
			
		||||
		DecodeJSON(t, resp, &result)
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("%s/FindPackagesById()?id='%s'", url, packageName))
 | 
			
		||||
			req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
		assert.Len(t, result.Versions, 1)
 | 
			
		||||
		assert.Equal(t, packageVersion, result.Versions[0])
 | 
			
		||||
			var result FeedResponse
 | 
			
		||||
			decodeXML(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
			assert.Len(t, result.Entries, 1)
 | 
			
		||||
			assert.Equal(t, packageVersion, result.Entries[0].Properties.Version)
 | 
			
		||||
		})
 | 
			
		||||
 | 
			
		||||
		t.Run("v3", func(t *testing.T) {
 | 
			
		||||
			defer tests.PrintCurrentTest(t)()
 | 
			
		||||
 | 
			
		||||
			req := NewRequest(t, "GET", fmt.Sprintf("%s/package/%s/index.json", url, packageName))
 | 
			
		||||
			req = AddBasicAuthHeader(req, user.Name)
 | 
			
		||||
			resp := MakeRequest(t, req, http.StatusOK)
 | 
			
		||||
 | 
			
		||||
			var result nuget.PackageVersionsResponse
 | 
			
		||||
			DecodeJSON(t, resp, &result)
 | 
			
		||||
 | 
			
		||||
			assert.Len(t, result.Versions, 1)
 | 
			
		||||
			assert.Equal(t, packageVersion, result.Versions[0])
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	t.Run("Delete", func(t *testing.T) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user