You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

nuget.go 19KB

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