first-commit

This commit is contained in:
2025-08-25 15:46:12 +08:00
commit f4d95dfff4
5665 changed files with 705359 additions and 0 deletions

View File

@@ -0,0 +1,420 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
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 {
Authors string `xml:"d:Authors"`
Copyright string `xml:"d:Copyright,omitempty"`
Created TypedValue[time.Time] `xml:"d:Created"`
Dependencies string `xml:"d:Dependencies"`
Description string `xml:"d:Description"`
DevelopmentDependency TypedValue[bool] `xml:"d:DevelopmentDependency"`
DownloadCount TypedValue[int64] `xml:"d:DownloadCount"`
ID string `xml:"d:Id"`
IconURL string `xml:"d:IconUrl,omitempty"`
Language string `xml:"d:Language,omitempty"`
LastUpdated TypedValue[time.Time] `xml:"d:LastUpdated"`
LicenseURL string `xml:"d:LicenseUrl,omitempty"`
MinClientVersion string `xml:"d:MinClientVersion,omitempty"`
NormalizedVersion string `xml:"d:NormalizedVersion"`
Owners string `xml:"d:Owners,omitempty"`
PackageSize TypedValue[int64] `xml:"d:PackageSize"`
ProjectURL string `xml:"d:ProjectUrl,omitempty"`
Published TypedValue[time.Time] `xml:"d:Published"`
ReleaseNotes string `xml:"d:ReleaseNotes,omitempty"`
RequireLicenseAcceptance TypedValue[bool] `xml:"d:RequireLicenseAcceptance"`
Tags string `xml:"d:Tags,omitempty"`
Title string `xml:"d:Title,omitempty"`
Version string `xml:"d:Version"`
VersionDownloadCount TypedValue[int64] `xml:"d:VersionDownloadCount"`
}
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"`
Links []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))
}
links := []FeedEntryLink{
{Rel: "self", Href: l.Base},
}
if l.Next != nil {
links = append(links, FeedEntryLink{
Rel: "next",
Href: l.GetNextURL(),
})
}
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(),
Links: links,
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{
Authors: metadata.Authors,
Copyright: metadata.Copyright,
Created: createdValue,
Dependencies: buildDependencyString(metadata),
Description: metadata.Description,
DevelopmentDependency: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.DevelopmentDependency},
DownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
ID: pd.Package.Name,
IconURL: metadata.IconURL,
Language: metadata.Language,
LastUpdated: createdValue,
LicenseURL: metadata.LicenseURL,
MinClientVersion: metadata.MinClientVersion,
NormalizedVersion: pd.Version.Version,
Owners: metadata.Owners,
PackageSize: TypedValue[int64]{Type: "Edm.Int64", Value: pd.CalculateBlobSize()},
ProjectURL: metadata.ProjectURL,
Published: createdValue,
ReleaseNotes: metadata.ReleaseNotes,
RequireLicenseAcceptance: TypedValue[bool]{Type: "Edm.Boolean", Value: metadata.RequireLicenseAcceptance},
Tags: metadata.Tags,
Title: metadata.Title,
Version: pd.Version.Version,
VersionDownloadCount: TypedValue[int64]{Type: "Edm.Int64", Value: pd.Version.DownloadCount},
},
}
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()
}

View File

@@ -0,0 +1,315 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"sort"
"time"
packages_model "code.gitea.io/gitea/models/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"golang.org/x/text/collate"
"golang.org/x/text/language"
)
// https://docs.microsoft.com/en-us/nuget/api/service-index#resources
type ServiceIndexResponseV3 struct {
Version string `json:"version"`
Resources []ServiceResource `json:"resources"`
}
// https://docs.microsoft.com/en-us/nuget/api/service-index#resource
type ServiceResource struct {
ID string `json:"@id"`
Type string `json:"@type"`
}
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#response
type RegistrationIndexResponse struct {
RegistrationIndexURL string `json:"@id"`
Type []string `json:"@type"`
Count int `json:"count"`
Pages []*RegistrationIndexPage `json:"items"`
}
// 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"`
Upper string `json:"upper"`
Count int `json:"count"`
Items []*RegistrationIndexPageItem `json:"items"`
}
// 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"`
}
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#catalog-entry
type CatalogEntry struct {
CatalogLeafURL string `json:"@id"`
Authors string `json:"authors"`
Copyright string `json:"copyright"`
DependencyGroups []*PackageDependencyGroup `json:"dependencyGroups"`
Description string `json:"description"`
IconURL string `json:"iconUrl"`
ID string `json:"id"`
IsPrerelease bool `json:"isPrerelease"`
Language string `json:"language"`
LicenseURL string `json:"licenseUrl"`
PackageContentURL string `json:"packageContent"`
ProjectURL string `json:"projectUrl"`
RequireLicenseAcceptance bool `json:"requireLicenseAcceptance"`
Summary string `json:"summary"`
Tags string `json:"tags"`
Version string `json:"version"`
ReleaseNotes string `json:"releaseNotes"`
Published time.Time `json:"published"`
}
// 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"`
}
// 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"`
}
func createRegistrationIndexResponse(l *linkBuilder, pds []*packages_model.PackageDescriptor) *RegistrationIndexResponse {
sort.Slice(pds, func(i, j int) bool {
return pds[i].SemVer.LessThan(pds[j].SemVer)
})
items := make([]*RegistrationIndexPageItem, 0, len(pds))
for _, p := range pds {
items = append(items, createRegistrationIndexPageItem(l, p))
}
return &RegistrationIndexResponse{
RegistrationIndexURL: l.GetRegistrationIndexURL(pds[0].Package.Name),
Type: []string{"catalog:CatalogRoot", "PackageRegistration", "catalog:Permalink"},
Count: 1,
Pages: []*RegistrationIndexPage{
{
RegistrationPageURL: l.GetRegistrationIndexURL(pds[0].Package.Name),
Count: len(pds),
Lower: pds[0].Version.Version,
Upper: pds[len(pds)-1].Version.Version,
Items: items,
},
},
}
}
func createRegistrationIndexPageItem(l *linkBuilder, pd *packages_model.PackageDescriptor) *RegistrationIndexPageItem {
metadata := pd.Metadata.(*nuget_module.Metadata)
return &RegistrationIndexPageItem{
RegistrationLeafURL: l.GetRegistrationLeafURL(pd.Package.Name, pd.Version.Version),
PackageContentURL: l.GetPackageDownloadURL(pd.Package.Name, pd.Version.Version),
CatalogEntry: &CatalogEntry{
CatalogLeafURL: l.GetRegistrationLeafURL(pd.Package.Name, pd.Version.Version),
Authors: metadata.Authors,
Copyright: metadata.Copyright,
DependencyGroups: createDependencyGroups(pd),
Description: metadata.Description,
IconURL: metadata.IconURL,
ID: pd.Package.Name,
IsPrerelease: pd.Version.IsPrerelease(),
Language: metadata.Language,
LicenseURL: metadata.LicenseURL,
PackageContentURL: l.GetPackageDownloadURL(pd.Package.Name, pd.Version.Version),
ProjectURL: metadata.ProjectURL,
RequireLicenseAcceptance: metadata.RequireLicenseAcceptance,
Summary: metadata.Summary,
Tags: metadata.Tags,
Version: pd.Version.Version,
ReleaseNotes: metadata.ReleaseNotes,
Published: pd.Version.CreatedUnix.AsLocalTime(),
},
}
}
func createDependencyGroups(pd *packages_model.PackageDescriptor) []*PackageDependencyGroup {
metadata := pd.Metadata.(*nuget_module.Metadata)
dependencyGroups := make([]*PackageDependencyGroup, 0, len(metadata.Dependencies))
for k, v := range metadata.Dependencies {
dependencies := make([]*PackageDependency, 0, len(v))
for _, dep := range v {
dependencies = append(dependencies, &PackageDependency{
ID: dep.ID,
Range: dep.Version,
})
}
dependencyGroups = append(dependencyGroups, &PackageDependencyGroup{
TargetFramework: k,
Dependencies: dependencies,
})
}
return dependencyGroups
}
// 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"`
PackageContentURL string `json:"packageContent"`
RegistrationIndexURL string `json:"registration"`
CatalogEntry CatalogEntry `json:"catalogEntry"`
}
func createRegistrationLeafResponse(l *linkBuilder, pd *packages_model.PackageDescriptor) *RegistrationLeafResponse {
registrationLeafURL := l.GetRegistrationLeafURL(pd.Package.Name, pd.Version.Version)
packageDownloadURL := l.GetPackageDownloadURL(pd.Package.Name, pd.Version.Version)
metadata := pd.Metadata.(*nuget_module.Metadata)
return &RegistrationLeafResponse{
RegistrationLeafURL: registrationLeafURL,
RegistrationIndexURL: l.GetRegistrationIndexURL(pd.Package.Name),
PackageContentURL: packageDownloadURL,
Type: []string{"Package", "http://schema.nuget.org/catalog#Permalink"},
CatalogEntry: CatalogEntry{
CatalogLeafURL: registrationLeafURL,
Authors: metadata.Authors,
Copyright: metadata.Copyright,
DependencyGroups: createDependencyGroups(pd),
Description: metadata.Description,
IconURL: metadata.IconURL,
ID: pd.Package.Name,
IsPrerelease: pd.Version.IsPrerelease(),
Language: metadata.Language,
LicenseURL: metadata.LicenseURL,
PackageContentURL: packageDownloadURL,
ProjectURL: metadata.ProjectURL,
RequireLicenseAcceptance: metadata.RequireLicenseAcceptance,
Summary: metadata.Summary,
Tags: metadata.Tags,
Version: pd.Version.Version,
ReleaseNotes: metadata.ReleaseNotes,
Published: pd.Version.CreatedUnix.AsLocalTime(),
},
}
}
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#response
type PackageVersionsResponse struct {
Versions []string `json:"versions"`
}
func createPackageVersionsResponse(pvs []*packages_model.PackageVersion) *PackageVersionsResponse {
versions := make([]string, 0, len(pvs))
for _, pv := range pvs {
versions = append(versions, pv.Version)
}
return &PackageVersionsResponse{
Versions: versions,
}
}
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#response
type SearchResultResponse struct {
TotalHits int64 `json:"totalHits"`
Data []*SearchResult `json:"data"`
}
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-result
type SearchResult struct {
Authors string `json:"authors"`
Copyright string `json:"copyright"`
DependencyGroups []*PackageDependencyGroup `json:"dependencyGroups"`
Description string `json:"description"`
IconURL string `json:"iconUrl"`
ID string `json:"id"`
IsPrerelease bool `json:"isPrerelease"`
Language string `json:"language"`
LicenseURL string `json:"licenseUrl"`
ProjectURL string `json:"projectUrl"`
RequireLicenseAcceptance bool `json:"requireLicenseAcceptance"`
Summary string `json:"summary"`
Tags string `json:"tags"`
Title string `json:"title"`
TotalDownloads int64 `json:"totalDownloads"`
Version string `json:"version"`
Versions []*SearchResultVersion `json:"versions"`
RegistrationIndexURL string `json:"registration"`
}
// 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"`
Downloads int64 `json:"downloads"`
}
func createSearchResultResponse(l *linkBuilder, totalHits int64, pds []*packages_model.PackageDescriptor) *SearchResultResponse {
grouped := make(map[string][]*packages_model.PackageDescriptor)
for _, pd := range pds {
grouped[pd.Package.Name] = append(grouped[pd.Package.Name], pd)
}
keys := make([]string, 0, len(grouped))
for key := range grouped {
keys = append(keys, key)
}
collate.New(language.English, collate.IgnoreCase).SortStrings(keys)
data := make([]*SearchResult, 0, len(pds))
for _, key := range keys {
data = append(data, createSearchResult(l, grouped[key]))
}
return &SearchResultResponse{
TotalHits: totalHits,
Data: data,
}
}
func createSearchResult(l *linkBuilder, pds []*packages_model.PackageDescriptor) *SearchResult {
latest := pds[0]
versions := make([]*SearchResultVersion, 0, len(pds))
totalDownloads := int64(0)
for _, pd := range pds {
if latest.SemVer.LessThan(pd.SemVer) {
latest = pd
}
totalDownloads += pd.Version.DownloadCount
versions = append(versions, &SearchResultVersion{
RegistrationLeafURL: l.GetRegistrationLeafURL(pd.Package.Name, pd.Version.Version),
Version: pd.Version.Version,
})
}
metadata := latest.Metadata.(*nuget_module.Metadata)
return &SearchResult{
Authors: metadata.Authors,
Copyright: metadata.Copyright,
Description: metadata.Description,
DependencyGroups: createDependencyGroups(latest),
IconURL: metadata.IconURL,
ID: latest.Package.Name,
IsPrerelease: latest.Version.IsPrerelease(),
Language: metadata.Language,
LicenseURL: metadata.LicenseURL,
ProjectURL: metadata.ProjectURL,
RequireLicenseAcceptance: metadata.RequireLicenseAcceptance,
Summary: metadata.Summary,
Tags: metadata.Tags,
Title: metadata.Title,
TotalDownloads: totalDownloads,
Version: latest.Version.Version,
Versions: versions,
RegistrationIndexURL: l.GetRegistrationIndexURL(latest.Package.Name),
}
}

View File

@@ -0,0 +1,50 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"net/http"
auth_model "code.gitea.io/gitea/models/auth"
user_model "code.gitea.io/gitea/models/user"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/services/auth"
)
var _ auth.Method = &Auth{}
type Auth struct{}
func (a *Auth) Name() string {
return "nuget"
}
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#request-parameters
func (a *Auth) Verify(req *http.Request, w http.ResponseWriter, store auth.DataStore, sess auth.SessionStore) (*user_model.User, error) {
token, err := auth_model.GetAccessTokenBySHA(req.Context(), req.Header.Get("X-NuGet-ApiKey"))
if err != nil {
if !(auth_model.IsErrAccessTokenNotExist(err) || auth_model.IsErrAccessTokenEmpty(err)) {
log.Error("GetAccessTokenBySHA: %v", err)
return nil, err
}
return nil, nil
}
u, err := user_model.GetUserByID(req.Context(), token.UID)
if err != nil {
log.Error("GetUserByID: %v", err)
return nil, err
}
token.UpdatedUnix = timeutil.TimeStampNow()
if err := auth_model.UpdateAccessToken(req.Context(), token); err != nil {
log.Error("UpdateAccessToken: %v", err)
}
store.GetData()["IsApiToken"] = true
store.GetData()["ApiToken"] = token
return u, nil
}

View File

@@ -0,0 +1,52 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"fmt"
"net/url"
)
type nextOptions struct {
Path string
Query url.Values
}
type linkBuilder struct {
Base string
Next *nextOptions
}
// GetRegistrationIndexURL builds the registration index url
func (l *linkBuilder) GetRegistrationIndexURL(id string) string {
return fmt.Sprintf("%s/registration/%s/index.json", l.Base, id)
}
// GetRegistrationLeafURL builds the registration leaf url
func (l *linkBuilder) GetRegistrationLeafURL(id, version string) string {
return fmt.Sprintf("%s/registration/%s/%s.json", l.Base, id, version)
}
// GetPackageDownloadURL builds the download url
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)
}
func (l *linkBuilder) GetNextURL() string {
u, _ := url.Parse(l.Base)
u = u.JoinPath(l.Next.Path)
q := u.Query()
for k, vs := range l.Next.Query {
for _, v := range vs {
q.Add(k, v)
}
}
u.RawQuery = q.Encode()
return u.String()
}

View File

@@ -0,0 +1,710 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"encoding/xml"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"regexp"
"strconv"
"strings"
"code.gitea.io/gitea/models/db"
packages_model "code.gitea.io/gitea/models/packages"
nuget_model "code.gitea.io/gitea/models/packages/nuget"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
packages_module "code.gitea.io/gitea/modules/packages"
nuget_module "code.gitea.io/gitea/modules/packages/nuget"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
"code.gitea.io/gitea/services/context"
packages_service "code.gitea.io/gitea/services/packages"
)
func apiError(ctx *context.Context, status int, obj any) {
helper.LogAndProcessError(ctx, status, obj, func(message string) {
ctx.JSON(status, map[string]string{
"Message": message,
})
})
}
func xmlResponse(ctx *context.Context, status int, obj any) { //nolint:unparam // status is always StatusOK
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)
}
}
// 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(`'([^']+)'`)
searchTermExact = regexp.MustCompile(`\s+eq\s+'`)
)
func getSearchTerm(ctx *context.Context) packages_model.SearchValue {
searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
if searchTerm != "" {
return packages_model.SearchValue{
Value: searchTerm,
ExactMatch: false,
}
}
// $filter contains a query like:
// (((Id ne null) and substringof('microsoft',tolower(Id)))
// https://www.odata.org/documentation/odata-version-2-0/uri-conventions/ section 4.5
// We don't support these queries, just extract the search term.
filter := ctx.FormTrim("$filter")
match := searchTermExtract.FindStringSubmatch(filter)
if len(match) == 2 {
return packages_model.SearchValue{
Value: strings.TrimSpace(match[1]),
ExactMatch: searchTermExact.MatchString(filter),
}
}
return packages_model.SearchValue{}
}
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
func SearchServiceV2(ctx *context.Context) {
skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
paginator := db.NewAbsoluteListOptions(skip, take)
pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: getSearchTerm(ctx),
IsInternal: optional.Some(false),
Paginator: paginator,
})
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
}
skip, take = paginator.GetSkipTake()
var next *nextOptions
if len(pvs) == take {
next = &nextOptions{
Path: "Search()",
Query: url.Values{},
}
searchTerm := ctx.FormTrim("searchTerm")
if searchTerm != "" {
next.Query.Set("searchTerm", searchTerm)
}
filter := ctx.FormTrim("$filter")
if filter != "" {
next.Query.Set("$filter", filter)
}
next.Query.Set("$skip", strconv.Itoa(skip+take))
next.Query.Set("$top", strconv.Itoa(take))
}
resp := createFeedResponse(
&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next},
total,
pds,
)
xmlResponse(ctx, http.StatusOK, resp)
}
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
func SearchServiceV2Count(ctx *context.Context) {
count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Name: getSearchTerm(ctx),
IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
}
// https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
func SearchServiceV3(ctx *context.Context) {
pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Name: packages_model.SearchValue{Value: ctx.FormTrim("q")},
IsInternal: optional.Some(false),
Paginator: db.NewAbsoluteListOptions(
ctx.FormInt("skip"),
ctx.FormInt("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 := createSearchResultResponse(
&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
count,
pds,
)
ctx.JSON(http.StatusOK, resp)
}
// https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-index
func RegistrationIndex(ctx *context.Context) {
packageName := ctx.PathParam("id")
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, err)
return
}
pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
resp := createRegistrationIndexResponse(
&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
pds,
)
ctx.JSON(http.StatusOK, resp)
}
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
func RegistrationLeafV2(ctx *context.Context) {
packageName := ctx.PathParam("id")
packageVersion := ctx.PathParam("version")
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
if err != nil {
if errors.Is(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{Base: 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.PathParam("id")
packageVersion := strings.TrimSuffix(ctx.PathParam("version"), ".json")
pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
if err != nil {
if errors.Is(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 := createRegistrationLeafResponse(
&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
pd,
)
ctx.JSON(http.StatusOK, resp)
}
// 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"), "'")
skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
paginator := db.NewAbsoluteListOptions(skip, take)
pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
ExactMatch: true,
Value: packageName,
},
IsInternal: optional.Some(false),
Paginator: paginator,
})
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
}
skip, take = paginator.GetSkipTake()
var next *nextOptions
if len(pvs) == take {
next = &nextOptions{
Path: "FindPackagesById()",
Query: url.Values{},
}
next.Query.Set("id", packageName)
next.Query.Set("$skip", strconv.Itoa(skip+take))
next.Query.Set("$top", strconv.Itoa(take))
}
resp := createFeedResponse(
&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next},
total,
pds,
)
xmlResponse(ctx, http.StatusOK, resp)
}
// http://docs.oasis-open.org/odata/odata/v4.0/errata03/os/complete/part2-url-conventions/odata-v4.0-errata03-os-part2-url-conventions-complete.html#_Toc453752351
func EnumeratePackageVersionsV2Count(ctx *context.Context) {
count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Type: packages_model.TypeNuGet,
Name: packages_model.SearchValue{
ExactMatch: true,
Value: strings.Trim(ctx.FormTrim("id"), "'"),
},
IsInternal: optional.Some(false),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
}
// https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions
func EnumeratePackageVersionsV3(ctx *context.Context) {
packageName := ctx.PathParam("id")
pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pvs) == 0 {
apiError(ctx, http.StatusNotFound, err)
return
}
resp := createPackageVersionsResponse(pvs)
ctx.JSON(http.StatusOK, resp)
}
// 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.PathParam("id")
packageVersion := ctx.PathParam("version")
filename := ctx.PathParam("filename")
s, u, pf, err := packages_service.OpenFileForDownloadByPackageNameAndVersion(
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 errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(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
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#push-a-package
func UploadPackage(ctx *context.Context) {
np, buf, closables := processUploadedFile(ctx, nuget_module.DependencyPackage)
defer func() {
for _, c := range closables {
c.Close()
}
}()
if np == nil {
return
}
pv, _, err := packages_service.CreatePackageAndAddFile(
ctx,
&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeNuGet,
Name: np.ID,
Version: np.Version,
},
SemverCompatible: true,
Creator: ctx.Doer,
Metadata: np.Metadata,
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: strings.ToLower(fmt.Sprintf("%s.%s.nupkg", np.ID, np.Version)),
},
Creator: ctx.Doer,
Data: buf,
IsLead: true,
},
)
if err != nil {
switch err {
case packages_model.ErrDuplicatePackageVersion:
apiError(ctx, http.StatusConflict, err)
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
nuspecBuf, err := packages_module.CreateHashedBufferFromReaderWithSize(np.NuspecContent, np.NuspecContent.Len())
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer nuspecBuf.Close()
_, err = packages_service.AddFileToPackageVersionInternal(
ctx,
pv,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: strings.ToLower(np.ID + ".nuspec"),
},
Data: nuspecBuf,
},
)
if err != nil {
switch err {
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
ctx.Status(http.StatusCreated)
}
// UploadSymbolPackage adds a symbol package to an existing package
// https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource
func UploadSymbolPackage(ctx *context.Context) {
np, buf, closables := processUploadedFile(ctx, nuget_module.SymbolsPackage)
defer func() {
for _, c := range closables {
c.Close()
}
}()
if np == nil {
return
}
pdbs, err := nuget_module.ExtractPortablePdb(buf, buf.Size())
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
defer pdbs.Close()
if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pi := &packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeNuGet,
Name: np.ID,
Version: np.Version,
}
_, err = packages_service.AddFileToExistingPackage(
ctx,
pi,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: strings.ToLower(fmt.Sprintf("%s.%s.snupkg", np.ID, np.Version)),
},
Creator: ctx.Doer,
Data: buf,
IsLead: false,
},
)
if err != nil {
switch err {
case packages_model.ErrPackageNotExist:
apiError(ctx, http.StatusNotFound, err)
case packages_model.ErrDuplicatePackageFile:
apiError(ctx, http.StatusConflict, err)
case packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
for _, pdb := range pdbs {
_, err := packages_service.AddFileToExistingPackage(
ctx,
pi,
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: strings.ToLower(pdb.Name),
CompositeKey: strings.ToLower(pdb.ID),
},
Creator: ctx.Doer,
Data: pdb.Content,
IsLead: false,
Properties: map[string]string{
nuget_module.PropertySymbolID: strings.ToLower(pdb.ID),
},
},
)
if err != nil {
switch err {
case packages_model.ErrDuplicatePackageFile:
apiError(ctx, http.StatusConflict, err)
case packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
}
ctx.Status(http.StatusCreated)
}
func processUploadedFile(ctx *context.Context, expectedType nuget_module.PackageType) (*nuget_module.Package, *packages_module.HashedBuffer, []io.Closer) {
closables := make([]io.Closer, 0, 2)
upload, needToClose, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusBadRequest, err)
return nil, nil, closables
}
if needToClose {
closables = append(closables, upload)
}
buf, err := packages_module.CreateHashedBufferFromReader(upload)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return nil, nil, closables
}
closables = append(closables, buf)
np, err := nuget_module.ParsePackageMetaData(buf, buf.Size())
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return nil, nil, closables
}
if np.PackageType != expectedType {
apiError(ctx, http.StatusBadRequest, errors.New("unexpected package type"))
return nil, nil, closables
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return nil, nil, closables
}
return np, buf, closables
}
// https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
func DownloadSymbolFile(ctx *context.Context) {
filename := ctx.PathParam("filename")
guid := ctx.PathParam("guid")[:32]
filename2 := ctx.PathParam("filename2")
if filename != filename2 {
apiError(ctx, http.StatusBadRequest, nil)
return
}
pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
OwnerID: ctx.Package.Owner.ID,
PackageType: packages_model.TypeNuGet,
Query: filename,
Properties: map[string]string{
nuget_module.PropertySymbolID: strings.ToLower(guid),
},
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pfs) != 1 {
apiError(ctx, http.StatusNotFound, nil)
return
}
s, u, pf, err := packages_service.OpenFileForDownload(ctx, pfs[0])
if err != nil {
if errors.Is(err, packages_model.ErrPackageNotExist) || errors.Is(err, packages_model.ErrPackageFileNotExist) {
apiError(ctx, http.StatusNotFound, err)
return
}
apiError(ctx, http.StatusInternalServerError, err)
return
}
helper.ServePackageFile(ctx, s, u, pf)
}
// DeletePackage hard deletes the package
// https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#delete-a-package
func DeletePackage(ctx *context.Context) {
packageName := ctx.PathParam("id")
packageVersion := ctx.PathParam("version")
err := packages_service.RemovePackageVersionByNameAndVersion(
ctx,
ctx.Doer,
&packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeNuGet,
Name: packageName,
Version: packageVersion,
},
)
if err != nil {
if errors.Is(err, packages_model.ErrPackageNotExist) {
apiError(ctx, http.StatusNotFound, err)
return
}
apiError(ctx, http.StatusInternalServerError, err)
}
ctx.Status(http.StatusNoContent)
}