contents.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. // Copyright 2020 The Gogs Authors. All rights reserved.
  2. // Use of this source code is governed by a MIT-style
  3. // license that can be found in the LICENSE file.
  4. package repo
  5. import (
  6. "encoding/base64"
  7. "fmt"
  8. "net/http"
  9. "path"
  10. "github.com/gogs/git-module"
  11. "github.com/pkg/errors"
  12. "gogs.io/gogs/internal/context"
  13. "gogs.io/gogs/internal/database"
  14. "gogs.io/gogs/internal/gitutil"
  15. "gogs.io/gogs/internal/pathutil"
  16. "gogs.io/gogs/internal/repoutil"
  17. )
  18. type links struct {
  19. Git string `json:"git"`
  20. Self string `json:"self"`
  21. HTML string `json:"html"`
  22. }
  23. type repoContent struct {
  24. Type string `json:"type"`
  25. Target string `json:"target,omitempty"`
  26. SubmoduleGitURL string `json:"submodule_git_url,omitempty"`
  27. Encoding string `json:"encoding,omitempty"`
  28. Size int64 `json:"size"`
  29. Name string `json:"name"`
  30. Path string `json:"path"`
  31. Content string `json:"content,omitempty"`
  32. Sha string `json:"sha"`
  33. URL string `json:"url"`
  34. GitURL string `json:"git_url"`
  35. HTMLURL string `json:"html_url"`
  36. DownloadURL string `json:"download_url"`
  37. Links links `json:"_links"`
  38. }
  39. func toRepoContent(c *context.APIContext, ref, subpath string, commit *git.Commit, entry *git.TreeEntry) (*repoContent, error) {
  40. repoURL := fmt.Sprintf("%s/repos/%s/%s", c.BaseURL, c.Params(":username"), c.Params(":reponame"))
  41. selfURL := fmt.Sprintf("%s/contents/%s", repoURL, subpath)
  42. htmlURL := fmt.Sprintf("%s/src/%s/%s", repoutil.HTMLURL(c.Repo.Owner.Name, c.Repo.Repository.Name), ref, entry.Name())
  43. downloadURL := fmt.Sprintf("%s/raw/%s/%s", repoutil.HTMLURL(c.Repo.Owner.Name, c.Repo.Repository.Name), ref, entry.Name())
  44. content := &repoContent{
  45. Size: entry.Size(),
  46. Name: entry.Name(),
  47. Path: subpath,
  48. Sha: entry.ID().String(),
  49. URL: selfURL,
  50. HTMLURL: htmlURL,
  51. DownloadURL: downloadURL,
  52. Links: links{
  53. Self: selfURL,
  54. HTML: htmlURL,
  55. },
  56. }
  57. switch {
  58. case entry.IsBlob(), entry.IsExec():
  59. content.Type = "file"
  60. p, err := entry.Blob().Bytes()
  61. if err != nil {
  62. return nil, errors.Wrap(err, "get blob content")
  63. }
  64. content.Encoding = "base64"
  65. content.Content = base64.StdEncoding.EncodeToString(p)
  66. content.GitURL = fmt.Sprintf("%s/git/blobs/%s", repoURL, entry.ID().String())
  67. case entry.IsTree():
  68. content.Type = "dir"
  69. content.GitURL = fmt.Sprintf("%s/git/trees/%s", repoURL, entry.ID().String())
  70. case entry.IsSymlink():
  71. content.Type = "symlink"
  72. p, err := entry.Blob().Bytes()
  73. if err != nil {
  74. return nil, errors.Wrap(err, "get blob content")
  75. }
  76. content.Target = string(p)
  77. case entry.IsCommit():
  78. content.Type = "submodule"
  79. mod, err := commit.Submodule(subpath)
  80. if err != nil {
  81. return nil, errors.Wrap(err, "get submodule")
  82. }
  83. content.SubmoduleGitURL = mod.URL
  84. default:
  85. panic("unreachable")
  86. }
  87. content.Links.Git = content.GitURL
  88. return content, nil
  89. }
  90. func GetContents(c *context.APIContext) {
  91. repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
  92. gitRepo, err := git.Open(repoPath)
  93. if err != nil {
  94. c.Error(err, "open repository")
  95. return
  96. }
  97. ref := c.Query("ref")
  98. if ref == "" {
  99. ref = c.Repo.Repository.DefaultBranch
  100. }
  101. commit, err := gitRepo.CatFileCommit(ref)
  102. if err != nil {
  103. c.NotFoundOrError(gitutil.NewError(err), "get commit")
  104. return
  105. }
  106. // 🚨 SECURITY: Prevent path traversal.
  107. treePath := pathutil.Clean(c.Params("*"))
  108. entry, err := commit.TreeEntry(treePath)
  109. if err != nil {
  110. c.NotFoundOrError(gitutil.NewError(err), "get tree entry")
  111. return
  112. }
  113. if !entry.IsTree() {
  114. content, err := toRepoContent(c, ref, treePath, commit, entry)
  115. if err != nil {
  116. c.Errorf(err, "convert %q to repoContent", treePath)
  117. return
  118. }
  119. c.JSONSuccess(content)
  120. return
  121. }
  122. // The entry is a directory
  123. dir, err := gitRepo.LsTree(entry.ID().String())
  124. if err != nil {
  125. c.NotFoundOrError(gitutil.NewError(err), "get tree")
  126. return
  127. }
  128. entries, err := dir.Entries()
  129. if err != nil {
  130. c.NotFoundOrError(gitutil.NewError(err), "list entries")
  131. return
  132. }
  133. if len(entries) == 0 {
  134. c.JSONSuccess([]string{})
  135. return
  136. }
  137. contents := make([]*repoContent, 0, len(entries))
  138. for _, entry := range entries {
  139. subpath := path.Join(treePath, entry.Name())
  140. content, err := toRepoContent(c, ref, subpath, commit, entry)
  141. if err != nil {
  142. c.Errorf(err, "convert %q to repoContent", subpath)
  143. return
  144. }
  145. contents = append(contents, content)
  146. }
  147. c.JSONSuccess(contents)
  148. }
  149. // PutContentsRequest is the API message for creating or updating a file.
  150. type PutContentsRequest struct {
  151. Message string `json:"message" binding:"Required"`
  152. Content string `json:"content" binding:"Required"`
  153. Branch string `json:"branch"`
  154. }
  155. // PUT /repos/:username/:reponame/contents/*
  156. func PutContents(c *context.APIContext, r PutContentsRequest) {
  157. content, err := base64.StdEncoding.DecodeString(r.Content)
  158. if err != nil {
  159. c.Error(err, "decoding base64")
  160. return
  161. }
  162. if r.Branch == "" {
  163. r.Branch = c.Repo.Repository.DefaultBranch
  164. }
  165. // 🚨 SECURITY: Prevent path traversal.
  166. treePath := pathutil.Clean(c.Params("*"))
  167. err = c.Repo.Repository.UpdateRepoFile(
  168. c.User,
  169. database.UpdateRepoFileOptions{
  170. OldBranch: c.Repo.Repository.DefaultBranch,
  171. NewBranch: r.Branch,
  172. OldTreeName: treePath,
  173. NewTreeName: treePath,
  174. Message: r.Message,
  175. Content: string(content),
  176. },
  177. )
  178. if err != nil {
  179. c.Error(err, "updating repository file")
  180. return
  181. }
  182. repoPath := repoutil.RepositoryPath(c.Params(":username"), c.Params(":reponame"))
  183. gitRepo, err := git.Open(repoPath)
  184. if err != nil {
  185. c.Error(err, "open repository")
  186. return
  187. }
  188. commit, err := gitRepo.CatFileCommit(r.Branch)
  189. if err != nil {
  190. c.Error(err, "get file commit")
  191. return
  192. }
  193. entry, err := commit.TreeEntry(treePath)
  194. if err != nil {
  195. c.Error(err, "get tree entry")
  196. return
  197. }
  198. apiContent, err := toRepoContent(c, r.Branch, treePath, commit, entry)
  199. if err != nil {
  200. c.Error(err, "convert to *repoContent")
  201. return
  202. }
  203. apiCommit, err := gitCommitToAPICommit(commit, c)
  204. if err != nil {
  205. c.Error(err, "convert to *api.Commit")
  206. return
  207. }
  208. c.JSON(
  209. http.StatusCreated,
  210. map[string]any{
  211. "content": apiContent,
  212. "commit": apiCommit,
  213. },
  214. )
  215. }