repo_editor.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649
  1. package database
  2. import (
  3. "fmt"
  4. "io"
  5. "mime/multipart"
  6. "os"
  7. "os/exec"
  8. "path"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. "github.com/cockroachdb/errors"
  13. gouuid "github.com/satori/go.uuid"
  14. "github.com/unknwon/com"
  15. "github.com/gogs/git-module"
  16. "gogs.io/gogs/internal/conf"
  17. "gogs.io/gogs/internal/cryptoutil"
  18. "gogs.io/gogs/internal/gitutil"
  19. "gogs.io/gogs/internal/osutil"
  20. "gogs.io/gogs/internal/pathutil"
  21. "gogs.io/gogs/internal/process"
  22. "gogs.io/gogs/internal/tool"
  23. )
  24. // BranchAlreadyExists represents an error when branch already exists.
  25. type BranchAlreadyExists struct {
  26. Name string
  27. }
  28. // IsBranchAlreadyExists returns true if the error is BranchAlreadyExists.
  29. func IsBranchAlreadyExists(err error) bool {
  30. _, ok := err.(BranchAlreadyExists)
  31. return ok
  32. }
  33. func (err BranchAlreadyExists) Error() string {
  34. return fmt.Sprintf("branch already exists [name: %s]", err.Name)
  35. }
  36. const (
  37. EnvAuthUserID = "GOGS_AUTH_USER_ID"
  38. EnvAuthUserName = "GOGS_AUTH_USER_NAME"
  39. EnvAuthUserEmail = "GOGS_AUTH_USER_EMAIL"
  40. EnvRepoOwnerName = "GOGS_REPO_OWNER_NAME"
  41. EnvRepoOwnerSaltMd5 = "GOGS_REPO_OWNER_SALT_MD5"
  42. EnvRepoID = "GOGS_REPO_ID"
  43. EnvRepoName = "GOGS_REPO_NAME"
  44. EnvRepoCustomHooksPath = "GOGS_REPO_CUSTOM_HOOKS_PATH"
  45. )
  46. type ComposeHookEnvsOptions struct {
  47. AuthUser *User
  48. OwnerName string
  49. OwnerSalt string
  50. RepoID int64
  51. RepoName string
  52. RepoPath string
  53. }
  54. func ComposeHookEnvs(opts ComposeHookEnvsOptions) []string {
  55. envs := []string{
  56. "SSH_ORIGINAL_COMMAND=1",
  57. EnvAuthUserID + "=" + com.ToStr(opts.AuthUser.ID),
  58. EnvAuthUserName + "=" + opts.AuthUser.Name,
  59. EnvAuthUserEmail + "=" + opts.AuthUser.Email,
  60. EnvRepoOwnerName + "=" + opts.OwnerName,
  61. EnvRepoOwnerSaltMd5 + "=" + cryptoutil.MD5(opts.OwnerSalt),
  62. EnvRepoID + "=" + com.ToStr(opts.RepoID),
  63. EnvRepoName + "=" + opts.RepoName,
  64. EnvRepoCustomHooksPath + "=" + filepath.Join(opts.RepoPath, "custom_hooks"),
  65. }
  66. return envs
  67. }
  68. // ___________ .___.__ __ ___________.__.__
  69. // \_ _____/ __| _/|__|/ |_ \_ _____/|__| | ____
  70. // | __)_ / __ | | \ __\ | __) | | | _/ __ \
  71. // | \/ /_/ | | || | | \ | | |_\ ___/
  72. // /_______ /\____ | |__||__| \___ / |__|____/\___ >
  73. // \/ \/ \/ \/
  74. // discardLocalRepoBranchChanges discards local commits/changes of
  75. // given branch to make sure it is even to remote branch.
  76. func discardLocalRepoBranchChanges(localPath, branch string) error {
  77. if !com.IsExist(localPath) {
  78. return nil
  79. }
  80. // No need to check if nothing in the repository.
  81. if !git.RepoHasBranch(localPath, branch) {
  82. return nil
  83. }
  84. rev := "origin/" + branch
  85. if err := git.Reset(localPath, rev, git.ResetOptions{Hard: true}); err != nil {
  86. return errors.Newf("reset [revision: %s]: %v", rev, err)
  87. }
  88. return nil
  89. }
  90. func (r *Repository) DiscardLocalRepoBranchChanges(branch string) error {
  91. return discardLocalRepoBranchChanges(r.LocalCopyPath(), branch)
  92. }
  93. // CheckoutNewBranch checks out to a new branch from the a branch name.
  94. func (r *Repository) CheckoutNewBranch(oldBranch, newBranch string) error {
  95. if err := git.Checkout(r.LocalCopyPath(), newBranch, git.CheckoutOptions{
  96. BaseBranch: oldBranch,
  97. Timeout: time.Duration(conf.Git.Timeout.Pull) * time.Second,
  98. }); err != nil {
  99. return errors.Newf("checkout [base: %s, new: %s]: %v", oldBranch, newBranch, err)
  100. }
  101. return nil
  102. }
  103. // hasSymlinkInPath returns true if there is any symlink in path hierarchy using
  104. // the given base and relative path.
  105. func hasSymlinkInPath(base, relPath string) bool {
  106. parts := strings.Split(filepath.ToSlash(relPath), "/")
  107. for i := range parts {
  108. filePath := path.Join(append([]string{base}, parts[:i+1]...)...)
  109. if osutil.IsSymlink(filePath) {
  110. return true
  111. }
  112. }
  113. return false
  114. }
  115. type UpdateRepoFileOptions struct {
  116. OldBranch string
  117. NewBranch string
  118. OldTreeName string
  119. NewTreeName string
  120. Message string
  121. Content string
  122. IsNewFile bool
  123. }
  124. // UpdateRepoFile adds or updates a file in repository.
  125. func (r *Repository) UpdateRepoFile(doer *User, opts UpdateRepoFileOptions) error {
  126. // 🚨 SECURITY: Prevent uploading files into the ".git" directory.
  127. if isRepositoryGitPath(opts.NewTreeName) {
  128. return errors.Errorf("bad tree path %q", opts.NewTreeName)
  129. }
  130. repoWorkingPool.CheckIn(com.ToStr(r.ID))
  131. defer repoWorkingPool.CheckOut(com.ToStr(r.ID))
  132. if err := r.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
  133. return errors.Newf("discard local repo branch[%s] changes: %v", opts.OldBranch, err)
  134. } else if err = r.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
  135. return errors.Newf("update local copy branch[%s]: %v", opts.OldBranch, err)
  136. }
  137. localPath := r.LocalCopyPath()
  138. // 🚨 SECURITY: Prevent touching files in surprising places, reject operations
  139. // that involve symlinks.
  140. if hasSymlinkInPath(localPath, opts.OldTreeName) || hasSymlinkInPath(localPath, opts.NewTreeName) {
  141. return errors.New("cannot update file with symbolic link in path")
  142. }
  143. repoPath := r.RepoPath()
  144. if opts.OldBranch != opts.NewBranch {
  145. // Directly return error if new branch already exists in the server
  146. if git.RepoHasBranch(repoPath, opts.NewBranch) {
  147. return BranchAlreadyExists{Name: opts.NewBranch}
  148. }
  149. // Otherwise, delete branch from local copy in case out of sync
  150. if git.RepoHasBranch(localPath, opts.NewBranch) {
  151. if err := git.DeleteBranch(localPath, opts.NewBranch, git.DeleteBranchOptions{
  152. Force: true,
  153. }); err != nil {
  154. return errors.Newf("delete branch %q: %v", opts.NewBranch, err)
  155. }
  156. }
  157. if err := r.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
  158. return errors.Newf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err)
  159. }
  160. }
  161. oldFilePath := path.Join(localPath, opts.OldTreeName)
  162. newFilePath := path.Join(localPath, opts.NewTreeName)
  163. // Prompt the user if the meant-to-be new file already exists.
  164. if osutil.Exist(newFilePath) && opts.IsNewFile {
  165. return ErrRepoFileAlreadyExist{newFilePath}
  166. }
  167. if err := os.MkdirAll(path.Dir(newFilePath), os.ModePerm); err != nil {
  168. return errors.Wrapf(err, "create parent directories of %q", newFilePath)
  169. }
  170. if osutil.IsFile(oldFilePath) && opts.OldTreeName != opts.NewTreeName {
  171. if err := git.Move(localPath, opts.OldTreeName, opts.NewTreeName); err != nil {
  172. return errors.Wrapf(err, "git mv %q %q", opts.OldTreeName, opts.NewTreeName)
  173. }
  174. }
  175. if err := os.WriteFile(newFilePath, []byte(opts.Content), 0o600); err != nil {
  176. return errors.Newf("write file: %v", err)
  177. }
  178. if err := git.Add(localPath, git.AddOptions{All: true}); err != nil {
  179. return errors.Newf("git add --all: %v", err)
  180. }
  181. err := git.CreateCommit(
  182. localPath,
  183. &git.Signature{
  184. Name: doer.DisplayName(),
  185. Email: doer.Email,
  186. When: time.Now(),
  187. },
  188. opts.Message,
  189. )
  190. if err != nil {
  191. return errors.Newf("commit changes on %q: %v", localPath, err)
  192. }
  193. err = git.Push(localPath, "origin", opts.NewBranch,
  194. git.PushOptions{
  195. CommandOptions: git.CommandOptions{
  196. Envs: ComposeHookEnvs(ComposeHookEnvsOptions{
  197. AuthUser: doer,
  198. OwnerName: r.MustOwner().Name,
  199. OwnerSalt: r.MustOwner().Salt,
  200. RepoID: r.ID,
  201. RepoName: r.Name,
  202. RepoPath: r.RepoPath(),
  203. }),
  204. },
  205. },
  206. )
  207. if err != nil {
  208. return errors.Newf("git push origin %s: %v", opts.NewBranch, err)
  209. }
  210. return nil
  211. }
  212. // GetDiffPreview produces and returns diff result of a file which is not yet committed.
  213. func (r *Repository) GetDiffPreview(branch, treePath, content string) (*gitutil.Diff, error) {
  214. // 🚨 SECURITY: Prevent uploading files into the ".git" directory.
  215. if isRepositoryGitPath(treePath) {
  216. return nil, errors.Errorf("bad tree path %q", treePath)
  217. }
  218. repoWorkingPool.CheckIn(com.ToStr(r.ID))
  219. defer repoWorkingPool.CheckOut(com.ToStr(r.ID))
  220. if err := r.DiscardLocalRepoBranchChanges(branch); err != nil {
  221. return nil, errors.Newf("discard local repo branch[%s] changes: %v", branch, err)
  222. } else if err = r.UpdateLocalCopyBranch(branch); err != nil {
  223. return nil, errors.Newf("update local copy branch[%s]: %v", branch, err)
  224. }
  225. localPath := r.LocalCopyPath()
  226. filePath := path.Join(localPath, treePath)
  227. // 🚨 SECURITY: Prevent touching files in surprising places, reject operations
  228. // that involve symlinks.
  229. if hasSymlinkInPath(localPath, treePath) {
  230. return nil, errors.New("cannot update file with symbolic link in path")
  231. }
  232. if err := os.MkdirAll(path.Dir(filePath), os.ModePerm); err != nil {
  233. return nil, errors.Wrap(err, "create parent directories")
  234. } else if err = os.WriteFile(filePath, []byte(content), 0o600); err != nil {
  235. return nil, errors.Newf("write file: %v", err)
  236. }
  237. // 🚨 SECURITY: Prevent including unintended options in the path to the Git command.
  238. cmd := exec.Command("git", "diff", "--end-of-options", treePath)
  239. cmd.Dir = localPath
  240. cmd.Stderr = os.Stderr
  241. stdout, err := cmd.StdoutPipe()
  242. if err != nil {
  243. return nil, errors.Newf("get stdout pipe: %v", err)
  244. }
  245. if err = cmd.Start(); err != nil {
  246. return nil, errors.Newf("start: %v", err)
  247. }
  248. pid := process.Add(fmt.Sprintf("GetDiffPreview [repo_path: %s]", r.RepoPath()), cmd)
  249. defer process.Remove(pid)
  250. diff, err := gitutil.ParseDiff(stdout, conf.Git.MaxDiffFiles, conf.Git.MaxDiffLines, conf.Git.MaxDiffLineChars)
  251. if err != nil {
  252. return nil, errors.Newf("parse diff: %v", err)
  253. }
  254. if err = cmd.Wait(); err != nil {
  255. return nil, errors.Newf("wait: %v", err)
  256. }
  257. return diff, nil
  258. }
  259. // ________ .__ __ ___________.__.__
  260. // \______ \ ____ | | _____/ |_ ____ \_ _____/|__| | ____
  261. // | | \_/ __ \| | _/ __ \ __\/ __ \ | __) | | | _/ __ \
  262. // | ` \ ___/| |_\ ___/| | \ ___/ | \ | | |_\ ___/
  263. // /_______ /\___ >____/\___ >__| \___ > \___ / |__|____/\___ >
  264. // \/ \/ \/ \/ \/ \/
  265. //
  266. type DeleteRepoFileOptions struct {
  267. LastCommitID string
  268. OldBranch string
  269. NewBranch string
  270. TreePath string
  271. Message string
  272. }
  273. func (r *Repository) DeleteRepoFile(doer *User, opts DeleteRepoFileOptions) (err error) {
  274. // 🚨 SECURITY: Prevent uploading files into the ".git" directory.
  275. if isRepositoryGitPath(opts.TreePath) {
  276. return errors.Errorf("bad tree path %q", opts.TreePath)
  277. }
  278. repoWorkingPool.CheckIn(com.ToStr(r.ID))
  279. defer repoWorkingPool.CheckOut(com.ToStr(r.ID))
  280. if err = r.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
  281. return errors.Newf("discard local r branch[%s] changes: %v", opts.OldBranch, err)
  282. } else if err = r.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
  283. return errors.Newf("update local copy branch[%s]: %v", opts.OldBranch, err)
  284. }
  285. localPath := r.LocalCopyPath()
  286. // 🚨 SECURITY: Prevent touching files in surprising places, reject operations
  287. // that involve symlinks.
  288. if hasSymlinkInPath(localPath, opts.TreePath) {
  289. return errors.New("cannot update file with symbolic link in path")
  290. }
  291. if opts.OldBranch != opts.NewBranch {
  292. if err := r.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
  293. return errors.Newf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err)
  294. }
  295. }
  296. filePath := path.Join(localPath, opts.TreePath)
  297. if err = os.Remove(filePath); err != nil {
  298. return errors.Newf("remove file %q: %v", opts.TreePath, err)
  299. }
  300. if err = git.Add(localPath, git.AddOptions{All: true}); err != nil {
  301. return errors.Newf("git add --all: %v", err)
  302. }
  303. err = git.CreateCommit(
  304. localPath,
  305. &git.Signature{
  306. Name: doer.DisplayName(),
  307. Email: doer.Email,
  308. When: time.Now(),
  309. },
  310. opts.Message,
  311. )
  312. if err != nil {
  313. return errors.Newf("commit changes to %q: %v", localPath, err)
  314. }
  315. err = git.Push(localPath, "origin", opts.NewBranch,
  316. git.PushOptions{
  317. CommandOptions: git.CommandOptions{
  318. Envs: ComposeHookEnvs(ComposeHookEnvsOptions{
  319. AuthUser: doer,
  320. OwnerName: r.MustOwner().Name,
  321. OwnerSalt: r.MustOwner().Salt,
  322. RepoID: r.ID,
  323. RepoName: r.Name,
  324. RepoPath: r.RepoPath(),
  325. }),
  326. },
  327. },
  328. )
  329. if err != nil {
  330. return errors.Newf("git push origin %s: %v", opts.NewBranch, err)
  331. }
  332. return nil
  333. }
  334. // ____ ___ .__ .___ ___________.___.__
  335. // | | \______ | | _________ __| _/ \_ _____/| | | ____ ______
  336. // | | /\____ \| | / _ \__ \ / __ | | __) | | | _/ __ \ / ___/
  337. // | | / | |_> > |_( <_> ) __ \_/ /_/ | | \ | | |_\ ___/ \___ \
  338. // |______/ | __/|____/\____(____ /\____ | \___ / |___|____/\___ >____ >
  339. // |__| \/ \/ \/ \/ \/
  340. //
  341. // Upload represent a uploaded file to a repo to be deleted when moved
  342. type Upload struct {
  343. ID int64
  344. UUID string `xorm:"uuid UNIQUE"`
  345. Name string
  346. }
  347. // UploadLocalPath returns where uploads is stored in local file system based on given UUID.
  348. func UploadLocalPath(uuid string) string {
  349. return path.Join(conf.Repository.Upload.TempPath, uuid[0:1], uuid[1:2], uuid)
  350. }
  351. // LocalPath returns where uploads are temporarily stored in local file system.
  352. func (upload *Upload) LocalPath() string {
  353. return UploadLocalPath(upload.UUID)
  354. }
  355. // NewUpload creates a new upload object.
  356. func NewUpload(name string, buf []byte, file multipart.File) (_ *Upload, err error) {
  357. if tool.IsMaliciousPath(name) {
  358. return nil, errors.Newf("malicious path detected: %s", name)
  359. }
  360. upload := &Upload{
  361. UUID: gouuid.NewV4().String(),
  362. Name: name,
  363. }
  364. localPath := upload.LocalPath()
  365. if err = os.MkdirAll(path.Dir(localPath), os.ModePerm); err != nil {
  366. return nil, errors.Newf("mkdir all: %v", err)
  367. }
  368. fw, err := os.Create(localPath)
  369. if err != nil {
  370. return nil, errors.Newf("create: %v", err)
  371. }
  372. defer func() { _ = fw.Close() }()
  373. if _, err = fw.Write(buf); err != nil {
  374. return nil, errors.Newf("write: %v", err)
  375. } else if _, err = io.Copy(fw, file); err != nil {
  376. return nil, errors.Newf("copy: %v", err)
  377. }
  378. if _, err := x.Insert(upload); err != nil {
  379. return nil, err
  380. }
  381. return upload, nil
  382. }
  383. func GetUploadByUUID(uuid string) (*Upload, error) {
  384. upload := &Upload{UUID: uuid}
  385. has, err := x.Get(upload)
  386. if err != nil {
  387. return nil, err
  388. } else if !has {
  389. return nil, ErrUploadNotExist{0, uuid}
  390. }
  391. return upload, nil
  392. }
  393. func GetUploadsByUUIDs(uuids []string) ([]*Upload, error) {
  394. if len(uuids) == 0 {
  395. return []*Upload{}, nil
  396. }
  397. // Silently drop invalid uuids.
  398. uploads := make([]*Upload, 0, len(uuids))
  399. return uploads, x.In("uuid", uuids).Find(&uploads)
  400. }
  401. func DeleteUploads(uploads ...*Upload) (err error) {
  402. if len(uploads) == 0 {
  403. return nil
  404. }
  405. sess := x.NewSession()
  406. defer sess.Close()
  407. if err = sess.Begin(); err != nil {
  408. return err
  409. }
  410. ids := make([]int64, len(uploads))
  411. for i := 0; i < len(uploads); i++ {
  412. ids[i] = uploads[i].ID
  413. }
  414. if _, err = sess.In("id", ids).Delete(new(Upload)); err != nil {
  415. return errors.Newf("delete uploads: %v", err)
  416. }
  417. for _, upload := range uploads {
  418. localPath := upload.LocalPath()
  419. if !osutil.IsFile(localPath) {
  420. continue
  421. }
  422. if err := os.Remove(localPath); err != nil {
  423. return errors.Newf("remove upload: %v", err)
  424. }
  425. }
  426. return sess.Commit()
  427. }
  428. func DeleteUpload(u *Upload) error {
  429. return DeleteUploads(u)
  430. }
  431. func DeleteUploadByUUID(uuid string) error {
  432. upload, err := GetUploadByUUID(uuid)
  433. if err != nil {
  434. if IsErrUploadNotExist(err) {
  435. return nil
  436. }
  437. return errors.Newf("get upload by UUID[%s]: %v", uuid, err)
  438. }
  439. if err := DeleteUpload(upload); err != nil {
  440. return errors.Newf("delete upload: %v", err)
  441. }
  442. return nil
  443. }
  444. type UploadRepoFileOptions struct {
  445. LastCommitID string
  446. OldBranch string
  447. NewBranch string
  448. TreePath string
  449. Message string
  450. Files []string // In UUID format
  451. }
  452. // isRepositoryGitPath returns true if given path is or resides inside ".git"
  453. // path of the repository.
  454. //
  455. // TODO(unknwon): Move to repoutil during refactoring for this file.
  456. func isRepositoryGitPath(path string) bool {
  457. path = strings.ToLower(path)
  458. return strings.HasSuffix(path, ".git") ||
  459. strings.Contains(path, ".git/") ||
  460. strings.Contains(path, `.git\`) ||
  461. // Windows treats ".git." the same as ".git"
  462. strings.HasSuffix(path, ".git.") ||
  463. strings.Contains(path, ".git./") ||
  464. strings.Contains(path, `.git.\`)
  465. }
  466. func (r *Repository) UploadRepoFiles(doer *User, opts UploadRepoFileOptions) error {
  467. if len(opts.Files) == 0 {
  468. return nil
  469. }
  470. // 🚨 SECURITY: Prevent uploading files into the ".git" directory.
  471. if isRepositoryGitPath(opts.TreePath) {
  472. return errors.Errorf("bad tree path %q", opts.TreePath)
  473. }
  474. uploads, err := GetUploadsByUUIDs(opts.Files)
  475. if err != nil {
  476. return errors.Newf("get uploads by UUIDs[%v]: %v", opts.Files, err)
  477. }
  478. repoWorkingPool.CheckIn(com.ToStr(r.ID))
  479. defer repoWorkingPool.CheckOut(com.ToStr(r.ID))
  480. if err = r.DiscardLocalRepoBranchChanges(opts.OldBranch); err != nil {
  481. return errors.Newf("discard local r branch[%s] changes: %v", opts.OldBranch, err)
  482. } else if err = r.UpdateLocalCopyBranch(opts.OldBranch); err != nil {
  483. return errors.Newf("update local copy branch[%s]: %v", opts.OldBranch, err)
  484. }
  485. if opts.OldBranch != opts.NewBranch {
  486. if err = r.CheckoutNewBranch(opts.OldBranch, opts.NewBranch); err != nil {
  487. return errors.Newf("checkout new branch[%s] from old branch[%s]: %v", opts.NewBranch, opts.OldBranch, err)
  488. }
  489. }
  490. localPath := r.LocalCopyPath()
  491. dirPath := path.Join(localPath, opts.TreePath)
  492. if err = os.MkdirAll(dirPath, os.ModePerm); err != nil {
  493. return err
  494. }
  495. // Copy uploaded files into repository
  496. for _, upload := range uploads {
  497. tmpPath := upload.LocalPath()
  498. if !osutil.IsFile(tmpPath) {
  499. continue
  500. }
  501. // 🚨 SECURITY: Prevent path traversal.
  502. upload.Name = pathutil.Clean(upload.Name)
  503. // 🚨 SECURITY: Prevent uploading files into the ".git" directory.
  504. if isRepositoryGitPath(upload.Name) {
  505. continue
  506. }
  507. targetPath := path.Join(dirPath, upload.Name)
  508. // 🚨 SECURITY: Prevent updating files in surprising place, check if the target
  509. // is a symlink.
  510. if osutil.IsSymlink(targetPath) {
  511. return errors.Newf("cannot overwrite symbolic link: %s", upload.Name)
  512. }
  513. if err = com.Copy(tmpPath, targetPath); err != nil {
  514. return errors.Newf("copy: %v", err)
  515. }
  516. }
  517. if err = git.Add(localPath, git.AddOptions{All: true}); err != nil {
  518. return errors.Newf("git add --all: %v", err)
  519. }
  520. err = git.CreateCommit(
  521. localPath,
  522. &git.Signature{
  523. Name: doer.DisplayName(),
  524. Email: doer.Email,
  525. When: time.Now(),
  526. },
  527. opts.Message,
  528. )
  529. if err != nil {
  530. return errors.Newf("commit changes on %q: %v", localPath, err)
  531. }
  532. err = git.Push(localPath, "origin", opts.NewBranch,
  533. git.PushOptions{
  534. CommandOptions: git.CommandOptions{
  535. Envs: ComposeHookEnvs(ComposeHookEnvsOptions{
  536. AuthUser: doer,
  537. OwnerName: r.MustOwner().Name,
  538. OwnerSalt: r.MustOwner().Salt,
  539. RepoID: r.ID,
  540. RepoName: r.Name,
  541. RepoPath: r.RepoPath(),
  542. }),
  543. },
  544. },
  545. )
  546. if err != nil {
  547. return errors.Newf("git push origin %s: %v", opts.NewBranch, err)
  548. }
  549. return DeleteUploads(uploads...)
  550. }