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.

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