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