gitea源码

backend.go 9.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. // Copyright 2024 The Gitea Authors. All rights reserved.
  2. // SPDX-License-Identifier: MIT
  3. package backend
  4. import (
  5. "context"
  6. "encoding/base64"
  7. "fmt"
  8. "io"
  9. "net/http"
  10. "net/url"
  11. "strconv"
  12. "code.gitea.io/gitea/modules/json"
  13. "code.gitea.io/gitea/modules/lfs"
  14. "code.gitea.io/gitea/modules/setting"
  15. "github.com/charmbracelet/git-lfs-transfer/transfer"
  16. )
  17. // Version is the git-lfs-transfer protocol version number.
  18. const Version = "1"
  19. // Capabilities is a list of Git LFS capabilities supported by this package.
  20. var Capabilities = []string{
  21. "version=" + Version,
  22. "locking",
  23. }
  24. var _ transfer.Backend = (*GiteaBackend)(nil)
  25. // GiteaBackend is an adapter between git-lfs-transfer library and Gitea's internal LFS API
  26. type GiteaBackend struct {
  27. ctx context.Context
  28. server *url.URL
  29. op string
  30. authToken string
  31. internalAuth string
  32. logger transfer.Logger
  33. }
  34. func New(ctx context.Context, repo, op, token string, logger transfer.Logger) (transfer.Backend, error) {
  35. // runServ guarantees repo will be in form [owner]/[name].git
  36. server, err := url.Parse(setting.LocalURL)
  37. if err != nil {
  38. return nil, err
  39. }
  40. server = server.JoinPath("api/internal/repo", repo, "info/lfs")
  41. return &GiteaBackend{ctx: ctx, server: server, op: op, authToken: token, internalAuth: "Bearer " + setting.InternalToken, logger: logger}, nil
  42. }
  43. // Batch implements transfer.Backend
  44. func (g *GiteaBackend) Batch(_ string, pointers []transfer.BatchItem, args transfer.Args) ([]transfer.BatchItem, error) {
  45. reqBody := lfs.BatchRequest{Operation: g.op}
  46. if transfer, ok := args[argTransfer]; ok {
  47. reqBody.Transfers = []string{transfer}
  48. }
  49. if ref, ok := args[argRefname]; ok {
  50. reqBody.Ref = &lfs.Reference{Name: ref}
  51. }
  52. reqBody.Objects = make([]lfs.Pointer, len(pointers))
  53. for i := range pointers {
  54. reqBody.Objects[i].Oid = pointers[i].Oid
  55. reqBody.Objects[i].Size = pointers[i].Size
  56. }
  57. bodyBytes, err := json.Marshal(reqBody)
  58. if err != nil {
  59. g.logger.Log("json marshal error", err)
  60. return nil, err
  61. }
  62. headers := map[string]string{
  63. headerAuthorization: g.authToken,
  64. headerGiteaInternalAuth: g.internalAuth,
  65. headerAccept: mimeGitLFS,
  66. headerContentType: mimeGitLFS,
  67. }
  68. req := newInternalRequestLFS(g.ctx, g.server.JoinPath("objects/batch").String(), http.MethodPost, headers, bodyBytes)
  69. resp, err := req.Response()
  70. if err != nil {
  71. g.logger.Log("http request error", err)
  72. return nil, err
  73. }
  74. defer resp.Body.Close()
  75. if resp.StatusCode != http.StatusOK {
  76. g.logger.Log("http statuscode error", resp.StatusCode, statusCodeToErr(resp.StatusCode))
  77. return nil, statusCodeToErr(resp.StatusCode)
  78. }
  79. respBytes, err := io.ReadAll(resp.Body)
  80. if err != nil {
  81. g.logger.Log("http read error", err)
  82. return nil, err
  83. }
  84. var respBody lfs.BatchResponse
  85. err = json.Unmarshal(respBytes, &respBody)
  86. if err != nil {
  87. g.logger.Log("json umarshal error", err)
  88. return nil, err
  89. }
  90. // rebuild slice, we can't rely on order in resp being the same as req
  91. pointers = pointers[:0]
  92. opNum := opMap[g.op]
  93. for _, obj := range respBody.Objects {
  94. pointer := transfer.Pointer{Oid: obj.Pointer.Oid, Size: obj.Pointer.Size}
  95. item := transfer.BatchItem{Pointer: pointer, Args: map[string]string{}}
  96. switch opNum {
  97. case opDownload:
  98. if action, ok := obj.Actions[actionDownload]; ok {
  99. item.Present = true
  100. idMap := obj.Actions
  101. idMapBytes, err := json.Marshal(idMap)
  102. if err != nil {
  103. g.logger.Log("json marshal error", err)
  104. return nil, err
  105. }
  106. idMapStr := base64.StdEncoding.EncodeToString(idMapBytes)
  107. item.Args[argID] = idMapStr
  108. if authHeader, ok := action.Header[headerAuthorization]; ok {
  109. authHeaderB64 := base64.StdEncoding.EncodeToString([]byte(authHeader))
  110. item.Args[argToken] = authHeaderB64
  111. }
  112. if action.ExpiresAt != nil {
  113. item.Args[argExpiresAt] = action.ExpiresAt.String()
  114. }
  115. } else {
  116. // must be an error, but the SSH protocol can't propagate individual errors
  117. g.logger.Log("object not found", obj.Pointer.Oid, obj.Pointer.Size)
  118. item.Present = false
  119. }
  120. case opUpload:
  121. if action, ok := obj.Actions[actionUpload]; ok {
  122. item.Present = false
  123. idMap := obj.Actions
  124. idMapBytes, err := json.Marshal(idMap)
  125. if err != nil {
  126. g.logger.Log("json marshal error", err)
  127. return nil, err
  128. }
  129. idMapStr := base64.StdEncoding.EncodeToString(idMapBytes)
  130. item.Args[argID] = idMapStr
  131. if authHeader, ok := action.Header[headerAuthorization]; ok {
  132. authHeaderB64 := base64.StdEncoding.EncodeToString([]byte(authHeader))
  133. item.Args[argToken] = authHeaderB64
  134. }
  135. if action.ExpiresAt != nil {
  136. item.Args[argExpiresAt] = action.ExpiresAt.String()
  137. }
  138. } else {
  139. item.Present = true
  140. }
  141. }
  142. pointers = append(pointers, item)
  143. }
  144. return pointers, nil
  145. }
  146. // Download implements transfer.Backend. The returned reader must be closed by the caller.
  147. func (g *GiteaBackend) Download(oid string, args transfer.Args) (io.ReadCloser, int64, error) {
  148. idMapStr, exists := args[argID]
  149. if !exists {
  150. return nil, 0, ErrMissingID
  151. }
  152. idMapBytes, err := base64.StdEncoding.DecodeString(idMapStr)
  153. if err != nil {
  154. g.logger.Log("base64 decode error", err)
  155. return nil, 0, transfer.ErrCorruptData
  156. }
  157. idMap := map[string]*lfs.Link{}
  158. err = json.Unmarshal(idMapBytes, &idMap)
  159. if err != nil {
  160. g.logger.Log("json unmarshal error", err)
  161. return nil, 0, transfer.ErrCorruptData
  162. }
  163. action, exists := idMap[actionDownload]
  164. if !exists {
  165. g.logger.Log("argument id incorrect")
  166. return nil, 0, transfer.ErrCorruptData
  167. }
  168. headers := map[string]string{
  169. headerAuthorization: g.authToken,
  170. headerGiteaInternalAuth: g.internalAuth,
  171. headerAccept: mimeOctetStream,
  172. }
  173. req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodGet, headers, nil)
  174. resp, err := req.Response()
  175. if err != nil {
  176. return nil, 0, fmt.Errorf("failed to get response: %w", err)
  177. }
  178. // no need to close the body here by "defer resp.Body.Close()", see below
  179. if resp.StatusCode != http.StatusOK {
  180. return nil, 0, statusCodeToErr(resp.StatusCode)
  181. }
  182. respSize, err := strconv.ParseInt(resp.Header.Get("X-Gitea-LFS-Content-Length"), 10, 64)
  183. if err != nil {
  184. return nil, 0, fmt.Errorf("failed to parse content length: %w", err)
  185. }
  186. // transfer.Backend will check io.Closer interface and close this Body reader
  187. return resp.Body, respSize, nil
  188. }
  189. // Upload implements transfer.Backend.
  190. func (g *GiteaBackend) Upload(oid string, size int64, r io.Reader, args transfer.Args) error {
  191. idMapStr, exists := args[argID]
  192. if !exists {
  193. return ErrMissingID
  194. }
  195. idMapBytes, err := base64.StdEncoding.DecodeString(idMapStr)
  196. if err != nil {
  197. g.logger.Log("base64 decode error", err)
  198. return transfer.ErrCorruptData
  199. }
  200. idMap := map[string]*lfs.Link{}
  201. err = json.Unmarshal(idMapBytes, &idMap)
  202. if err != nil {
  203. g.logger.Log("json unmarshal error", err)
  204. return transfer.ErrCorruptData
  205. }
  206. action, exists := idMap[actionUpload]
  207. if !exists {
  208. g.logger.Log("argument id incorrect")
  209. return transfer.ErrCorruptData
  210. }
  211. headers := map[string]string{
  212. headerAuthorization: g.authToken,
  213. headerGiteaInternalAuth: g.internalAuth,
  214. headerContentType: mimeOctetStream,
  215. headerContentLength: strconv.FormatInt(size, 10),
  216. }
  217. req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPut, headers, nil)
  218. req.Body(r)
  219. resp, err := req.Response()
  220. if err != nil {
  221. return err
  222. }
  223. defer resp.Body.Close()
  224. if resp.StatusCode != http.StatusOK {
  225. return statusCodeToErr(resp.StatusCode)
  226. }
  227. return nil
  228. }
  229. // Verify implements transfer.Backend.
  230. func (g *GiteaBackend) Verify(oid string, size int64, args transfer.Args) (transfer.Status, error) {
  231. reqBody := lfs.Pointer{Oid: oid, Size: size}
  232. bodyBytes, err := json.Marshal(reqBody)
  233. if err != nil {
  234. return transfer.NewStatus(transfer.StatusInternalServerError), err
  235. }
  236. idMapStr, exists := args[argID]
  237. if !exists {
  238. return transfer.NewStatus(transfer.StatusBadRequest, "missing argument: id"), ErrMissingID
  239. }
  240. idMapBytes, err := base64.StdEncoding.DecodeString(idMapStr)
  241. if err != nil {
  242. g.logger.Log("base64 decode error", err)
  243. return transfer.NewStatus(transfer.StatusBadRequest, "corrupt argument: id"), transfer.ErrCorruptData
  244. }
  245. idMap := map[string]*lfs.Link{}
  246. err = json.Unmarshal(idMapBytes, &idMap)
  247. if err != nil {
  248. g.logger.Log("json unmarshal error", err)
  249. return transfer.NewStatus(transfer.StatusBadRequest, "corrupt argument: id"), transfer.ErrCorruptData
  250. }
  251. action, exists := idMap[actionVerify]
  252. if !exists {
  253. // the server sent no verify action
  254. return transfer.SuccessStatus(), nil
  255. }
  256. headers := map[string]string{
  257. headerAuthorization: g.authToken,
  258. headerGiteaInternalAuth: g.internalAuth,
  259. headerAccept: mimeGitLFS,
  260. headerContentType: mimeGitLFS,
  261. }
  262. req := newInternalRequestLFS(g.ctx, toInternalLFSURL(action.Href), http.MethodPost, headers, bodyBytes)
  263. resp, err := req.Response()
  264. if err != nil {
  265. return transfer.NewStatus(transfer.StatusInternalServerError), err
  266. }
  267. defer resp.Body.Close()
  268. if resp.StatusCode != http.StatusOK {
  269. return transfer.NewStatus(uint32(resp.StatusCode), http.StatusText(resp.StatusCode)), statusCodeToErr(resp.StatusCode)
  270. }
  271. return transfer.SuccessStatus(), nil
  272. }
  273. // LockBackend implements transfer.Backend.
  274. func (g *GiteaBackend) LockBackend(_ transfer.Args) transfer.LockBackend {
  275. return newGiteaLockBackend(g)
  276. }