gitea源码

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