code.gitea.io/gitea@v1.21.7/routers/api/packages/nuget/nuget.go (about)

     1  // Copyright 2021 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package nuget
     5  
     6  import (
     7  	"encoding/xml"
     8  	"errors"
     9  	"fmt"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  	"regexp"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"code.gitea.io/gitea/models/db"
    18  	packages_model "code.gitea.io/gitea/models/packages"
    19  	nuget_model "code.gitea.io/gitea/models/packages/nuget"
    20  	"code.gitea.io/gitea/modules/context"
    21  	"code.gitea.io/gitea/modules/log"
    22  	packages_module "code.gitea.io/gitea/modules/packages"
    23  	nuget_module "code.gitea.io/gitea/modules/packages/nuget"
    24  	"code.gitea.io/gitea/modules/setting"
    25  	"code.gitea.io/gitea/modules/util"
    26  	"code.gitea.io/gitea/routers/api/packages/helper"
    27  	packages_service "code.gitea.io/gitea/services/packages"
    28  )
    29  
    30  func apiError(ctx *context.Context, status int, obj any) {
    31  	helper.LogAndProcessError(ctx, status, obj, func(message string) {
    32  		ctx.JSON(status, map[string]string{
    33  			"Message": message,
    34  		})
    35  	})
    36  }
    37  
    38  func xmlResponse(ctx *context.Context, status int, obj any) {
    39  	ctx.Resp.Header().Set("Content-Type", "application/atom+xml; charset=utf-8")
    40  	ctx.Resp.WriteHeader(status)
    41  	if _, err := ctx.Resp.Write([]byte(xml.Header)); err != nil {
    42  		log.Error("Write failed: %v", err)
    43  	}
    44  	if err := xml.NewEncoder(ctx.Resp).Encode(obj); err != nil {
    45  		log.Error("XML encode failed: %v", err)
    46  	}
    47  }
    48  
    49  // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
    50  func ServiceIndexV2(ctx *context.Context) {
    51  	base := setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"
    52  
    53  	xmlResponse(ctx, http.StatusOK, &ServiceIndexResponseV2{
    54  		Base:      base,
    55  		Xmlns:     "http://www.w3.org/2007/app",
    56  		XmlnsAtom: "http://www.w3.org/2005/Atom",
    57  		Workspace: ServiceWorkspace{
    58  			Title: AtomTitle{
    59  				Type: "text",
    60  				Text: "Default",
    61  			},
    62  			Collection: ServiceCollection{
    63  				Href: "Packages",
    64  				Title: AtomTitle{
    65  					Type: "text",
    66  					Text: "Packages",
    67  				},
    68  			},
    69  		},
    70  	})
    71  }
    72  
    73  // https://docs.microsoft.com/en-us/nuget/api/service-index
    74  func ServiceIndexV3(ctx *context.Context) {
    75  	root := setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"
    76  
    77  	ctx.JSON(http.StatusOK, &ServiceIndexResponseV3{
    78  		Version: "3.0.0",
    79  		Resources: []ServiceResource{
    80  			{ID: root + "/query", Type: "SearchQueryService"},
    81  			{ID: root + "/query", Type: "SearchQueryService/3.0.0-beta"},
    82  			{ID: root + "/query", Type: "SearchQueryService/3.0.0-rc"},
    83  			{ID: root + "/registration", Type: "RegistrationsBaseUrl"},
    84  			{ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-beta"},
    85  			{ID: root + "/registration", Type: "RegistrationsBaseUrl/3.0.0-rc"},
    86  			{ID: root + "/package", Type: "PackageBaseAddress/3.0.0"},
    87  			{ID: root, Type: "PackagePublish/2.0.0"},
    88  			{ID: root + "/symbolpackage", Type: "SymbolPackagePublish/4.9.0"},
    89  		},
    90  	})
    91  }
    92  
    93  // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/LegacyFeedCapabilityResourceV2Feed.cs
    94  func FeedCapabilityResource(ctx *context.Context) {
    95  	xmlResponse(ctx, http.StatusOK, Metadata)
    96  }
    97  
    98  var searchTermExtract = regexp.MustCompile(`'([^']+)'`)
    99  
   100  func getSearchTerm(ctx *context.Context) string {
   101  	searchTerm := strings.Trim(ctx.FormTrim("searchTerm"), "'")
   102  	if searchTerm == "" {
   103  		// $filter contains a query like:
   104  		// (((Id ne null) and substringof('microsoft',tolower(Id)))
   105  		// We don't support these queries, just extract the search term.
   106  		match := searchTermExtract.FindStringSubmatch(ctx.FormTrim("$filter"))
   107  		if len(match) == 2 {
   108  			searchTerm = strings.TrimSpace(match[1])
   109  		}
   110  	}
   111  	return searchTerm
   112  }
   113  
   114  // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
   115  func SearchServiceV2(ctx *context.Context) {
   116  	skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
   117  	paginator := db.NewAbsoluteListOptions(skip, take)
   118  
   119  	pvs, total, err := packages_model.SearchLatestVersions(ctx, &packages_model.PackageSearchOptions{
   120  		OwnerID: ctx.Package.Owner.ID,
   121  		Type:    packages_model.TypeNuGet,
   122  		Name: packages_model.SearchValue{
   123  			Value: getSearchTerm(ctx),
   124  		},
   125  		IsInternal: util.OptionalBoolFalse,
   126  		Paginator:  paginator,
   127  	})
   128  	if err != nil {
   129  		apiError(ctx, http.StatusInternalServerError, err)
   130  		return
   131  	}
   132  
   133  	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
   134  	if err != nil {
   135  		apiError(ctx, http.StatusInternalServerError, err)
   136  		return
   137  	}
   138  
   139  	skip, take = paginator.GetSkipTake()
   140  
   141  	var next *nextOptions
   142  	if len(pvs) == take {
   143  		next = &nextOptions{
   144  			Path:  "Search()",
   145  			Query: url.Values{},
   146  		}
   147  		searchTerm := ctx.FormTrim("searchTerm")
   148  		if searchTerm != "" {
   149  			next.Query.Set("searchTerm", searchTerm)
   150  		}
   151  		filter := ctx.FormTrim("$filter")
   152  		if filter != "" {
   153  			next.Query.Set("$filter", filter)
   154  		}
   155  		next.Query.Set("$skip", strconv.Itoa(skip+take))
   156  		next.Query.Set("$top", strconv.Itoa(take))
   157  	}
   158  
   159  	resp := createFeedResponse(
   160  		&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next},
   161  		total,
   162  		pds,
   163  	)
   164  
   165  	xmlResponse(ctx, http.StatusOK, resp)
   166  }
   167  
   168  // 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
   169  func SearchServiceV2Count(ctx *context.Context) {
   170  	count, err := nuget_model.CountPackages(ctx, &packages_model.PackageSearchOptions{
   171  		OwnerID: ctx.Package.Owner.ID,
   172  		Name: packages_model.SearchValue{
   173  			Value: getSearchTerm(ctx),
   174  		},
   175  		IsInternal: util.OptionalBoolFalse,
   176  	})
   177  	if err != nil {
   178  		apiError(ctx, http.StatusInternalServerError, err)
   179  		return
   180  	}
   181  
   182  	ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
   183  }
   184  
   185  // https://docs.microsoft.com/en-us/nuget/api/search-query-service-resource#search-for-packages
   186  func SearchServiceV3(ctx *context.Context) {
   187  	pvs, count, err := nuget_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
   188  		OwnerID:    ctx.Package.Owner.ID,
   189  		Name:       packages_model.SearchValue{Value: ctx.FormTrim("q")},
   190  		IsInternal: util.OptionalBoolFalse,
   191  		Paginator: db.NewAbsoluteListOptions(
   192  			ctx.FormInt("skip"),
   193  			ctx.FormInt("take"),
   194  		),
   195  	})
   196  	if err != nil {
   197  		apiError(ctx, http.StatusInternalServerError, err)
   198  		return
   199  	}
   200  
   201  	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
   202  	if err != nil {
   203  		apiError(ctx, http.StatusInternalServerError, err)
   204  		return
   205  	}
   206  
   207  	resp := createSearchResultResponse(
   208  		&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
   209  		count,
   210  		pds,
   211  	)
   212  
   213  	ctx.JSON(http.StatusOK, resp)
   214  }
   215  
   216  // https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-index
   217  func RegistrationIndex(ctx *context.Context) {
   218  	packageName := ctx.Params("id")
   219  
   220  	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
   221  	if err != nil {
   222  		apiError(ctx, http.StatusInternalServerError, err)
   223  		return
   224  	}
   225  	if len(pvs) == 0 {
   226  		apiError(ctx, http.StatusNotFound, err)
   227  		return
   228  	}
   229  
   230  	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
   231  	if err != nil {
   232  		apiError(ctx, http.StatusInternalServerError, err)
   233  		return
   234  	}
   235  
   236  	resp := createRegistrationIndexResponse(
   237  		&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
   238  		pds,
   239  	)
   240  
   241  	ctx.JSON(http.StatusOK, resp)
   242  }
   243  
   244  // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
   245  func RegistrationLeafV2(ctx *context.Context) {
   246  	packageName := ctx.Params("id")
   247  	packageVersion := ctx.Params("version")
   248  
   249  	pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
   250  	if err != nil {
   251  		if err == packages_model.ErrPackageNotExist {
   252  			apiError(ctx, http.StatusNotFound, err)
   253  			return
   254  		}
   255  		apiError(ctx, http.StatusInternalServerError, err)
   256  		return
   257  	}
   258  
   259  	pd, err := packages_model.GetPackageDescriptor(ctx, pv)
   260  	if err != nil {
   261  		apiError(ctx, http.StatusInternalServerError, err)
   262  		return
   263  	}
   264  
   265  	resp := createEntryResponse(
   266  		&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
   267  		pd,
   268  	)
   269  
   270  	xmlResponse(ctx, http.StatusOK, resp)
   271  }
   272  
   273  // https://docs.microsoft.com/en-us/nuget/api/registration-base-url-resource#registration-leaf
   274  func RegistrationLeafV3(ctx *context.Context) {
   275  	packageName := ctx.Params("id")
   276  	packageVersion := strings.TrimSuffix(ctx.Params("version"), ".json")
   277  
   278  	pv, err := packages_model.GetVersionByNameAndVersion(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName, packageVersion)
   279  	if err != nil {
   280  		if err == packages_model.ErrPackageNotExist {
   281  			apiError(ctx, http.StatusNotFound, err)
   282  			return
   283  		}
   284  		apiError(ctx, http.StatusInternalServerError, err)
   285  		return
   286  	}
   287  
   288  	pd, err := packages_model.GetPackageDescriptor(ctx, pv)
   289  	if err != nil {
   290  		apiError(ctx, http.StatusInternalServerError, err)
   291  		return
   292  	}
   293  
   294  	resp := createRegistrationLeafResponse(
   295  		&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget"},
   296  		pd,
   297  	)
   298  
   299  	ctx.JSON(http.StatusOK, resp)
   300  }
   301  
   302  // https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Protocol/LegacyFeed/V2FeedQueryBuilder.cs
   303  func EnumeratePackageVersionsV2(ctx *context.Context) {
   304  	packageName := strings.Trim(ctx.FormTrim("id"), "'")
   305  
   306  	skip, take := ctx.FormInt("$skip"), ctx.FormInt("$top")
   307  	paginator := db.NewAbsoluteListOptions(skip, take)
   308  
   309  	pvs, total, err := packages_model.SearchVersions(ctx, &packages_model.PackageSearchOptions{
   310  		OwnerID: ctx.Package.Owner.ID,
   311  		Type:    packages_model.TypeNuGet,
   312  		Name: packages_model.SearchValue{
   313  			ExactMatch: true,
   314  			Value:      packageName,
   315  		},
   316  		IsInternal: util.OptionalBoolFalse,
   317  		Paginator:  paginator,
   318  	})
   319  	if err != nil {
   320  		apiError(ctx, http.StatusInternalServerError, err)
   321  		return
   322  	}
   323  
   324  	pds, err := packages_model.GetPackageDescriptors(ctx, pvs)
   325  	if err != nil {
   326  		apiError(ctx, http.StatusInternalServerError, err)
   327  		return
   328  	}
   329  
   330  	skip, take = paginator.GetSkipTake()
   331  
   332  	var next *nextOptions
   333  	if len(pvs) == take {
   334  		next = &nextOptions{
   335  			Path:  "FindPackagesById()",
   336  			Query: url.Values{},
   337  		}
   338  		next.Query.Set("id", packageName)
   339  		next.Query.Set("$skip", strconv.Itoa(skip+take))
   340  		next.Query.Set("$top", strconv.Itoa(take))
   341  	}
   342  
   343  	resp := createFeedResponse(
   344  		&linkBuilder{Base: setting.AppURL + "api/packages/" + ctx.Package.Owner.Name + "/nuget", Next: next},
   345  		total,
   346  		pds,
   347  	)
   348  
   349  	xmlResponse(ctx, http.StatusOK, resp)
   350  }
   351  
   352  // 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
   353  func EnumeratePackageVersionsV2Count(ctx *context.Context) {
   354  	count, err := packages_model.CountVersions(ctx, &packages_model.PackageSearchOptions{
   355  		OwnerID: ctx.Package.Owner.ID,
   356  		Type:    packages_model.TypeNuGet,
   357  		Name: packages_model.SearchValue{
   358  			ExactMatch: true,
   359  			Value:      strings.Trim(ctx.FormTrim("id"), "'"),
   360  		},
   361  		IsInternal: util.OptionalBoolFalse,
   362  	})
   363  	if err != nil {
   364  		apiError(ctx, http.StatusInternalServerError, err)
   365  		return
   366  	}
   367  
   368  	ctx.PlainText(http.StatusOK, strconv.FormatInt(count, 10))
   369  }
   370  
   371  // https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#enumerate-package-versions
   372  func EnumeratePackageVersionsV3(ctx *context.Context) {
   373  	packageName := ctx.Params("id")
   374  
   375  	pvs, err := packages_model.GetVersionsByPackageName(ctx, ctx.Package.Owner.ID, packages_model.TypeNuGet, packageName)
   376  	if err != nil {
   377  		apiError(ctx, http.StatusInternalServerError, err)
   378  		return
   379  	}
   380  	if len(pvs) == 0 {
   381  		apiError(ctx, http.StatusNotFound, err)
   382  		return
   383  	}
   384  
   385  	resp := createPackageVersionsResponse(pvs)
   386  
   387  	ctx.JSON(http.StatusOK, resp)
   388  }
   389  
   390  // https://docs.microsoft.com/en-us/nuget/api/package-base-address-resource#download-package-content-nupkg
   391  func DownloadPackageFile(ctx *context.Context) {
   392  	packageName := ctx.Params("id")
   393  	packageVersion := ctx.Params("version")
   394  	filename := ctx.Params("filename")
   395  
   396  	s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
   397  		ctx,
   398  		&packages_service.PackageInfo{
   399  			Owner:       ctx.Package.Owner,
   400  			PackageType: packages_model.TypeNuGet,
   401  			Name:        packageName,
   402  			Version:     packageVersion,
   403  		},
   404  		&packages_service.PackageFileInfo{
   405  			Filename: filename,
   406  		},
   407  	)
   408  	if err != nil {
   409  		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
   410  			apiError(ctx, http.StatusNotFound, err)
   411  			return
   412  		}
   413  		apiError(ctx, http.StatusInternalServerError, err)
   414  		return
   415  	}
   416  
   417  	helper.ServePackageFile(ctx, s, u, pf)
   418  }
   419  
   420  // UploadPackage creates a new package with the metadata contained in the uploaded nupgk file
   421  // https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#push-a-package
   422  func UploadPackage(ctx *context.Context) {
   423  	np, buf, closables := processUploadedFile(ctx, nuget_module.DependencyPackage)
   424  	defer func() {
   425  		for _, c := range closables {
   426  			c.Close()
   427  		}
   428  	}()
   429  	if np == nil {
   430  		return
   431  	}
   432  
   433  	_, _, err := packages_service.CreatePackageAndAddFile(
   434  		ctx,
   435  		&packages_service.PackageCreationInfo{
   436  			PackageInfo: packages_service.PackageInfo{
   437  				Owner:       ctx.Package.Owner,
   438  				PackageType: packages_model.TypeNuGet,
   439  				Name:        np.ID,
   440  				Version:     np.Version,
   441  			},
   442  			SemverCompatible: true,
   443  			Creator:          ctx.Doer,
   444  			Metadata:         np.Metadata,
   445  		},
   446  		&packages_service.PackageFileCreationInfo{
   447  			PackageFileInfo: packages_service.PackageFileInfo{
   448  				Filename: strings.ToLower(fmt.Sprintf("%s.%s.nupkg", np.ID, np.Version)),
   449  			},
   450  			Creator: ctx.Doer,
   451  			Data:    buf,
   452  			IsLead:  true,
   453  		},
   454  	)
   455  	if err != nil {
   456  		switch err {
   457  		case packages_model.ErrDuplicatePackageVersion:
   458  			apiError(ctx, http.StatusConflict, err)
   459  		case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   460  			apiError(ctx, http.StatusForbidden, err)
   461  		default:
   462  			apiError(ctx, http.StatusInternalServerError, err)
   463  		}
   464  		return
   465  	}
   466  
   467  	ctx.Status(http.StatusCreated)
   468  }
   469  
   470  // UploadSymbolPackage adds a symbol package to an existing package
   471  // https://docs.microsoft.com/en-us/nuget/api/symbol-package-publish-resource
   472  func UploadSymbolPackage(ctx *context.Context) {
   473  	np, buf, closables := processUploadedFile(ctx, nuget_module.SymbolsPackage)
   474  	defer func() {
   475  		for _, c := range closables {
   476  			c.Close()
   477  		}
   478  	}()
   479  	if np == nil {
   480  		return
   481  	}
   482  
   483  	pdbs, err := nuget_module.ExtractPortablePdb(buf, buf.Size())
   484  	if err != nil {
   485  		if errors.Is(err, util.ErrInvalidArgument) {
   486  			apiError(ctx, http.StatusBadRequest, err)
   487  		} else {
   488  			apiError(ctx, http.StatusInternalServerError, err)
   489  		}
   490  		return
   491  	}
   492  	defer pdbs.Close()
   493  
   494  	if _, err := buf.Seek(0, io.SeekStart); err != nil {
   495  		apiError(ctx, http.StatusInternalServerError, err)
   496  		return
   497  	}
   498  
   499  	pi := &packages_service.PackageInfo{
   500  		Owner:       ctx.Package.Owner,
   501  		PackageType: packages_model.TypeNuGet,
   502  		Name:        np.ID,
   503  		Version:     np.Version,
   504  	}
   505  
   506  	_, err = packages_service.AddFileToExistingPackage(
   507  		ctx,
   508  		pi,
   509  		&packages_service.PackageFileCreationInfo{
   510  			PackageFileInfo: packages_service.PackageFileInfo{
   511  				Filename: strings.ToLower(fmt.Sprintf("%s.%s.snupkg", np.ID, np.Version)),
   512  			},
   513  			Creator: ctx.Doer,
   514  			Data:    buf,
   515  			IsLead:  false,
   516  		},
   517  	)
   518  	if err != nil {
   519  		switch err {
   520  		case packages_model.ErrPackageNotExist:
   521  			apiError(ctx, http.StatusNotFound, err)
   522  		case packages_model.ErrDuplicatePackageFile:
   523  			apiError(ctx, http.StatusConflict, err)
   524  		case packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   525  			apiError(ctx, http.StatusForbidden, err)
   526  		default:
   527  			apiError(ctx, http.StatusInternalServerError, err)
   528  		}
   529  		return
   530  	}
   531  
   532  	for _, pdb := range pdbs {
   533  		_, err := packages_service.AddFileToExistingPackage(
   534  			ctx,
   535  			pi,
   536  			&packages_service.PackageFileCreationInfo{
   537  				PackageFileInfo: packages_service.PackageFileInfo{
   538  					Filename:     strings.ToLower(pdb.Name),
   539  					CompositeKey: strings.ToLower(pdb.ID),
   540  				},
   541  				Creator: ctx.Doer,
   542  				Data:    pdb.Content,
   543  				IsLead:  false,
   544  				Properties: map[string]string{
   545  					nuget_module.PropertySymbolID: strings.ToLower(pdb.ID),
   546  				},
   547  			},
   548  		)
   549  		if err != nil {
   550  			switch err {
   551  			case packages_model.ErrDuplicatePackageFile:
   552  				apiError(ctx, http.StatusConflict, err)
   553  			case packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
   554  				apiError(ctx, http.StatusForbidden, err)
   555  			default:
   556  				apiError(ctx, http.StatusInternalServerError, err)
   557  			}
   558  			return
   559  		}
   560  	}
   561  
   562  	ctx.Status(http.StatusCreated)
   563  }
   564  
   565  func processUploadedFile(ctx *context.Context, expectedType nuget_module.PackageType) (*nuget_module.Package, *packages_module.HashedBuffer, []io.Closer) {
   566  	closables := make([]io.Closer, 0, 2)
   567  
   568  	upload, close, err := ctx.UploadStream()
   569  	if err != nil {
   570  		apiError(ctx, http.StatusBadRequest, err)
   571  		return nil, nil, closables
   572  	}
   573  
   574  	if close {
   575  		closables = append(closables, upload)
   576  	}
   577  
   578  	buf, err := packages_module.CreateHashedBufferFromReader(upload)
   579  	if err != nil {
   580  		apiError(ctx, http.StatusInternalServerError, err)
   581  		return nil, nil, closables
   582  	}
   583  	closables = append(closables, buf)
   584  
   585  	np, err := nuget_module.ParsePackageMetaData(buf, buf.Size())
   586  	if err != nil {
   587  		if errors.Is(err, util.ErrInvalidArgument) {
   588  			apiError(ctx, http.StatusBadRequest, err)
   589  		} else {
   590  			apiError(ctx, http.StatusInternalServerError, err)
   591  		}
   592  		return nil, nil, closables
   593  	}
   594  	if np.PackageType != expectedType {
   595  		apiError(ctx, http.StatusBadRequest, errors.New("unexpected package type"))
   596  		return nil, nil, closables
   597  	}
   598  	if _, err := buf.Seek(0, io.SeekStart); err != nil {
   599  		apiError(ctx, http.StatusInternalServerError, err)
   600  		return nil, nil, closables
   601  	}
   602  	return np, buf, closables
   603  }
   604  
   605  // https://github.com/dotnet/symstore/blob/main/docs/specs/Simple_Symbol_Query_Protocol.md#request
   606  func DownloadSymbolFile(ctx *context.Context) {
   607  	filename := ctx.Params("filename")
   608  	guid := ctx.Params("guid")[:32]
   609  	filename2 := ctx.Params("filename2")
   610  
   611  	if filename != filename2 {
   612  		apiError(ctx, http.StatusBadRequest, nil)
   613  		return
   614  	}
   615  
   616  	pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
   617  		OwnerID:     ctx.Package.Owner.ID,
   618  		PackageType: packages_model.TypeNuGet,
   619  		Query:       filename,
   620  		Properties: map[string]string{
   621  			nuget_module.PropertySymbolID: strings.ToLower(guid),
   622  		},
   623  	})
   624  	if err != nil {
   625  		apiError(ctx, http.StatusInternalServerError, err)
   626  		return
   627  	}
   628  	if len(pfs) != 1 {
   629  		apiError(ctx, http.StatusNotFound, nil)
   630  		return
   631  	}
   632  
   633  	s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
   634  	if err != nil {
   635  		if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
   636  			apiError(ctx, http.StatusNotFound, err)
   637  			return
   638  		}
   639  		apiError(ctx, http.StatusInternalServerError, err)
   640  		return
   641  	}
   642  
   643  	helper.ServePackageFile(ctx, s, u, pf)
   644  }
   645  
   646  // DeletePackage hard deletes the package
   647  // https://docs.microsoft.com/en-us/nuget/api/package-publish-resource#delete-a-package
   648  func DeletePackage(ctx *context.Context) {
   649  	packageName := ctx.Params("id")
   650  	packageVersion := ctx.Params("version")
   651  
   652  	err := packages_service.RemovePackageVersionByNameAndVersion(
   653  		ctx,
   654  		ctx.Doer,
   655  		&packages_service.PackageInfo{
   656  			Owner:       ctx.Package.Owner,
   657  			PackageType: packages_model.TypeNuGet,
   658  			Name:        packageName,
   659  			Version:     packageVersion,
   660  		},
   661  	)
   662  	if err != nil {
   663  		if err == packages_model.ErrPackageNotExist {
   664  			apiError(ctx, http.StatusNotFound, err)
   665  			return
   666  		}
   667  		apiError(ctx, http.StatusInternalServerError, err)
   668  	}
   669  
   670  	ctx.Status(http.StatusNoContent)
   671  }