1
0

contents.go 6.0 KB

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