mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-10-31 21:28:11 +09: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:
		
							
								
								
									
										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() | ||||
| } | ||||
		Reference in New Issue
	
	Block a user