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.

debian.go 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309
  1. // Copyright 2023 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package debian
  4. import (
  5. stdctx "context"
  6. "errors"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "strings"
  11. "code.gitea.io/gitea/models/db"
  12. packages_model "code.gitea.io/gitea/models/packages"
  13. packages_module "code.gitea.io/gitea/modules/packages"
  14. debian_module "code.gitea.io/gitea/modules/packages/debian"
  15. "code.gitea.io/gitea/modules/util"
  16. "code.gitea.io/gitea/routers/api/packages/helper"
  17. "code.gitea.io/gitea/services/context"
  18. notify_service "code.gitea.io/gitea/services/notify"
  19. packages_service "code.gitea.io/gitea/services/packages"
  20. debian_service "code.gitea.io/gitea/services/packages/debian"
  21. )
  22. func apiError(ctx *context.Context, status int, obj any) {
  23. helper.LogAndProcessError(ctx, status, obj, func(message string) {
  24. ctx.PlainText(status, message)
  25. })
  26. }
  27. func GetRepositoryKey(ctx *context.Context) {
  28. _, pub, err := debian_service.GetOrCreateKeyPair(ctx, ctx.Package.Owner.ID)
  29. if err != nil {
  30. apiError(ctx, http.StatusInternalServerError, err)
  31. return
  32. }
  33. ctx.ServeContent(strings.NewReader(pub), &context.ServeHeaderOptions{
  34. ContentType: "application/pgp-keys",
  35. Filename: "repository.key",
  36. })
  37. }
  38. // https://wiki.debian.org/DebianRepository/Format#A.22Release.22_files
  39. // https://wiki.debian.org/DebianRepository/Format#A.22Packages.22_Indices
  40. func GetRepositoryFile(ctx *context.Context) {
  41. pv, err := debian_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
  42. if err != nil {
  43. apiError(ctx, http.StatusInternalServerError, err)
  44. return
  45. }
  46. key := ctx.Params("distribution")
  47. component := ctx.Params("component")
  48. architecture := strings.TrimPrefix(ctx.Params("architecture"), "binary-")
  49. if component != "" && architecture != "" {
  50. key += "|" + component + "|" + architecture
  51. }
  52. s, u, pf, err := packages_service.GetFileStreamByPackageVersion(
  53. ctx,
  54. pv,
  55. &packages_service.PackageFileInfo{
  56. Filename: ctx.Params("filename"),
  57. CompositeKey: key,
  58. },
  59. )
  60. if err != nil {
  61. if err == packages_model.ErrPackageNotExist || err == packages_model.ErrPackageFileNotExist {
  62. apiError(ctx, http.StatusNotFound, err)
  63. } else {
  64. apiError(ctx, http.StatusInternalServerError, err)
  65. }
  66. return
  67. }
  68. helper.ServePackageFile(ctx, s, u, pf)
  69. }
  70. // https://wiki.debian.org/DebianRepository/Format#indices_acquisition_via_hashsums_.28by-hash.29
  71. func GetRepositoryFileByHash(ctx *context.Context) {
  72. pv, err := debian_service.GetOrCreateRepositoryVersion(ctx, ctx.Package.Owner.ID)
  73. if err != nil {
  74. apiError(ctx, http.StatusInternalServerError, err)
  75. return
  76. }
  77. algorithm := strings.ToLower(ctx.Params("algorithm"))
  78. if algorithm == "md5sum" {
  79. algorithm = "md5"
  80. }
  81. pfs, _, err := packages_model.SearchFiles(ctx, &packages_model.PackageFileSearchOptions{
  82. VersionID: pv.ID,
  83. Hash: strings.ToLower(ctx.Params("hash")),
  84. HashAlgorithm: algorithm,
  85. })
  86. if err != nil {
  87. apiError(ctx, http.StatusInternalServerError, err)
  88. return
  89. }
  90. if len(pfs) != 1 {
  91. apiError(ctx, http.StatusNotFound, nil)
  92. return
  93. }
  94. s, u, pf, err := packages_service.GetPackageFileStream(ctx, pfs[0])
  95. if err != nil {
  96. if errors.Is(err, util.ErrNotExist) {
  97. apiError(ctx, http.StatusNotFound, err)
  98. } else {
  99. apiError(ctx, http.StatusInternalServerError, err)
  100. }
  101. return
  102. }
  103. helper.ServePackageFile(ctx, s, u, pf)
  104. }
  105. func UploadPackageFile(ctx *context.Context) {
  106. distribution := strings.TrimSpace(ctx.Params("distribution"))
  107. component := strings.TrimSpace(ctx.Params("component"))
  108. if distribution == "" || component == "" {
  109. apiError(ctx, http.StatusBadRequest, "invalid distribution or component")
  110. return
  111. }
  112. upload, needToClose, err := ctx.UploadStream()
  113. if err != nil {
  114. apiError(ctx, http.StatusInternalServerError, err)
  115. return
  116. }
  117. if needToClose {
  118. defer upload.Close()
  119. }
  120. buf, err := packages_module.CreateHashedBufferFromReader(upload)
  121. if err != nil {
  122. apiError(ctx, http.StatusInternalServerError, err)
  123. return
  124. }
  125. defer buf.Close()
  126. pck, err := debian_module.ParsePackage(buf)
  127. if err != nil {
  128. if errors.Is(err, util.ErrInvalidArgument) {
  129. apiError(ctx, http.StatusBadRequest, err)
  130. } else {
  131. apiError(ctx, http.StatusInternalServerError, err)
  132. }
  133. return
  134. }
  135. if _, err := buf.Seek(0, io.SeekStart); err != nil {
  136. apiError(ctx, http.StatusInternalServerError, err)
  137. return
  138. }
  139. _, _, err = packages_service.CreatePackageOrAddFileToExisting(
  140. ctx,
  141. &packages_service.PackageCreationInfo{
  142. PackageInfo: packages_service.PackageInfo{
  143. Owner: ctx.Package.Owner,
  144. PackageType: packages_model.TypeDebian,
  145. Name: pck.Name,
  146. Version: pck.Version,
  147. },
  148. Creator: ctx.Doer,
  149. Metadata: pck.Metadata,
  150. },
  151. &packages_service.PackageFileCreationInfo{
  152. PackageFileInfo: packages_service.PackageFileInfo{
  153. Filename: fmt.Sprintf("%s_%s_%s.deb", pck.Name, pck.Version, pck.Architecture),
  154. CompositeKey: fmt.Sprintf("%s|%s", distribution, component),
  155. },
  156. Creator: ctx.Doer,
  157. Data: buf,
  158. IsLead: true,
  159. Properties: map[string]string{
  160. debian_module.PropertyDistribution: distribution,
  161. debian_module.PropertyComponent: component,
  162. debian_module.PropertyArchitecture: pck.Architecture,
  163. debian_module.PropertyControl: pck.Control,
  164. },
  165. },
  166. )
  167. if err != nil {
  168. switch err {
  169. case packages_model.ErrDuplicatePackageVersion, packages_model.ErrDuplicatePackageFile:
  170. apiError(ctx, http.StatusConflict, err)
  171. case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
  172. apiError(ctx, http.StatusForbidden, err)
  173. default:
  174. apiError(ctx, http.StatusInternalServerError, err)
  175. }
  176. return
  177. }
  178. if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, pck.Architecture); err != nil {
  179. apiError(ctx, http.StatusInternalServerError, err)
  180. return
  181. }
  182. ctx.Status(http.StatusCreated)
  183. }
  184. func DownloadPackageFile(ctx *context.Context) {
  185. name := ctx.Params("name")
  186. version := ctx.Params("version")
  187. s, u, pf, err := packages_service.GetFileStreamByPackageNameAndVersion(
  188. ctx,
  189. &packages_service.PackageInfo{
  190. Owner: ctx.Package.Owner,
  191. PackageType: packages_model.TypeDebian,
  192. Name: name,
  193. Version: version,
  194. },
  195. &packages_service.PackageFileInfo{
  196. Filename: fmt.Sprintf("%s_%s_%s.deb", name, version, ctx.Params("architecture")),
  197. CompositeKey: fmt.Sprintf("%s|%s", ctx.Params("distribution"), ctx.Params("component")),
  198. },
  199. )
  200. if err != nil {
  201. if errors.Is(err, util.ErrNotExist) {
  202. apiError(ctx, http.StatusNotFound, err)
  203. } else {
  204. apiError(ctx, http.StatusInternalServerError, err)
  205. }
  206. return
  207. }
  208. helper.ServePackageFile(ctx, s, u, pf, &context.ServeHeaderOptions{
  209. ContentType: "application/vnd.debian.binary-package",
  210. Filename: pf.Name,
  211. LastModified: pf.CreatedUnix.AsLocalTime(),
  212. })
  213. }
  214. func DeletePackageFile(ctx *context.Context) {
  215. distribution := ctx.Params("distribution")
  216. component := ctx.Params("component")
  217. name := ctx.Params("name")
  218. version := ctx.Params("version")
  219. architecture := ctx.Params("architecture")
  220. owner := ctx.Package.Owner
  221. var pd *packages_model.PackageDescriptor
  222. err := db.WithTx(ctx, func(ctx stdctx.Context) error {
  223. pv, err := packages_model.GetVersionByNameAndVersion(ctx, owner.ID, packages_model.TypeDebian, name, version)
  224. if err != nil {
  225. return err
  226. }
  227. pf, err := packages_model.GetFileForVersionByName(
  228. ctx,
  229. pv.ID,
  230. fmt.Sprintf("%s_%s_%s.deb", name, version, architecture),
  231. fmt.Sprintf("%s|%s", distribution, component),
  232. )
  233. if err != nil {
  234. return err
  235. }
  236. if err := packages_service.DeletePackageFile(ctx, pf); err != nil {
  237. return err
  238. }
  239. has, err := packages_model.HasVersionFileReferences(ctx, pv.ID)
  240. if err != nil {
  241. return err
  242. }
  243. if !has {
  244. pd, err = packages_model.GetPackageDescriptor(ctx, pv)
  245. if err != nil {
  246. return err
  247. }
  248. if err := packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil {
  249. return err
  250. }
  251. }
  252. return nil
  253. })
  254. if err != nil {
  255. if errors.Is(err, util.ErrNotExist) {
  256. apiError(ctx, http.StatusNotFound, err)
  257. } else {
  258. apiError(ctx, http.StatusInternalServerError, err)
  259. }
  260. return
  261. }
  262. if pd != nil {
  263. notify_service.PackageDelete(ctx, ctx.Doer, pd)
  264. }
  265. if err := debian_service.BuildSpecificRepositoryFiles(ctx, ctx.Package.Owner.ID, distribution, component, architecture); err != nil {
  266. apiError(ctx, http.StatusInternalServerError, err)
  267. return
  268. }
  269. ctx.Status(http.StatusNoContent)
  270. }