From 0b64f733d7382ff5dacd64f0e48e7759adffbdbd Mon Sep 17 00:00:00 2001 From: zhzhang Date: Tue, 27 Feb 2024 15:16:44 +0800 Subject: [PATCH] Add MAX_NON_LFS_FILE_SIZE to limit the file size of non-lfs file --- modules/git/diff.go | 90 +++++++++++++++++++++++++++++ modules/setting/repository.go | 2 + routers/private/hook_pre_receive.go | 29 ++++++++++ 3 files changed, 121 insertions(+) diff --git a/modules/git/diff.go b/modules/git/diff.go index 10ef3d83fb..0a01451686 100644 --- a/modules/git/diff.go +++ b/modules/git/diff.go @@ -315,3 +315,93 @@ func GetAffectedFiles(repo *Repository, oldCommitID, newCommitID string, env []s return affectedFiles, err } + +func GetChangesFiles(repo *Repository, oldCommitID, newCommitID string, env []string) ([]string, error) { + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + log.Error("Unable to create os.Pipe for %s", repo.Path) + return nil, err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + + affectedFiles := make([]string, 0, 32) + + // Run `git diff --name-status` to get the status of the changed files + err = NewCommand(repo.Ctx, "diff", "--name-status").AddDynamicArguments(oldCommitID, newCommitID). + Run(&RunOpts{ + Env: env, + Dir: repo.Path, + Stdout: stdoutWriter, + PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing + _ = stdoutWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = stdoutReader.Close() + }() + // Now scan the output from the command + scanner := bufio.NewScanner(stdoutReader) + for scanner.Scan() { + path := strings.TrimSpace(scanner.Text()) + if len(path) == 0 { + continue + } + affectedFiles = append(affectedFiles, path) + } + return scanner.Err() + }, + }) + if err != nil { + log.Error("Unable to get changes files for commits from %s to %s in %s: %v", oldCommitID, newCommitID, repo.Path, err) + } + + return affectedFiles, err +} + +func GetFileSize(repo *Repository, newCommitID, fileName string, env []string) (int64, error) { + stdoutReader, stdoutWriter, err := os.Pipe() + if err != nil { + log.Error("Unable to create os.Pipe for %s", repo.Path) + return 0, err + } + defer func() { + _ = stdoutReader.Close() + _ = stdoutWriter.Close() + }() + + var fileSize int64 + + // Run `git cat-file -s commitId:fileName` to get the file size of the commit + err = NewCommand(repo.Ctx, "cat-file", "-s").AddDynamicArguments(fmt.Sprintf("%s:%s", newCommitID, fileName)). + Run(&RunOpts{ + Env: env, + Dir: repo.Path, + Stdout: stdoutWriter, + PipelineFunc: func(ctx context.Context, cancel context.CancelFunc) error { + // Close the writer end of the pipe to begin processing + _ = stdoutWriter.Close() + defer func() { + // Close the reader on return to terminate the git command if necessary + _ = stdoutReader.Close() + }() + // Now scan the output from the command + scanner := bufio.NewScanner(stdoutReader) + for scanner.Scan() { + size := strings.TrimSpace(scanner.Text()) + if len(size) == 0 { + continue + } + fileSize, err = strconv.ParseInt(size, 10, 64) + } + return scanner.Err() + }, + }) + if err != nil { + log.Error("Unable to get file size of %s for commit %s in %s: %v", fileName, newCommitID, repo.Path, err) + } + + return fileSize, err +} diff --git a/modules/setting/repository.go b/modules/setting/repository.go index 9697a851d3..a49507cbb4 100644 --- a/modules/setting/repository.go +++ b/modules/setting/repository.go @@ -51,6 +51,7 @@ var ( AllowDeleteOfUnadoptedRepositories bool DisableDownloadSourceArchives bool AllowForkWithoutMaximumLimit bool + MaxNonLfsFileSize int64 // Repository editor settings Editor struct { @@ -276,6 +277,7 @@ func loadRepositoryFrom(rootCfg ConfigProvider) { Repository.GoGetCloneURLProtocol = sec.Key("GO_GET_CLONE_URL_PROTOCOL").MustString("https") Repository.MaxCreationLimit = sec.Key("MAX_CREATION_LIMIT").MustInt(-1) Repository.DefaultBranch = sec.Key("DEFAULT_BRANCH").MustString(Repository.DefaultBranch) + Repository.MaxNonLfsFileSize = sec.Key("MAX_NON_LFS_FILE_SIZE").MustInt64(-1) RepoRootPath = sec.Key("ROOT").MustString(path.Join(AppDataPath, "gitea-repositories")) if !filepath.IsAbs(RepoRootPath) { RepoRootPath = filepath.Join(AppWorkPath, RepoRootPath) diff --git a/routers/private/hook_pre_receive.go b/routers/private/hook_pre_receive.go index 90d8287f06..1bdb989edb 100644 --- a/routers/private/hook_pre_receive.go +++ b/routers/private/hook_pre_receive.go @@ -7,6 +7,7 @@ import ( "fmt" "net/http" "os" + "strings" "code.gitea.io/gitea/models" asymkey_model "code.gitea.io/gitea/models/asymkey" @@ -20,6 +21,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/private" + "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/web" pull_service "code.gitea.io/gitea/services/pull" ) @@ -117,6 +119,8 @@ func HookPreReceive(ctx *gitea_context.PrivateContext) { newCommitID := opts.NewCommitIDs[i] refFullName := opts.RefFullNames[i] + checkFileSize(ourCtx, oldCommitID, newCommitID, generateGitEnv(opts)) + switch { case refFullName.IsBranch(): preReceiveBranch(ourCtx, oldCommitID, newCommitID, refFullName) @@ -522,3 +526,28 @@ func (ctx *preReceiveContext) loadPusherAndPermission() bool { ctx.loadedPusher = true return true } + +func checkFileSize(ctx *preReceiveContext, oldCommitID, newCommitID string, env []string) { + if setting.Repository.MaxNonLfsFileSize == -1 { + return + } + affectedFiles, err := git.GetChangesFiles(ctx.Repo.GitRepo, oldCommitID, newCommitID, env) + if err != nil { + return + } + for _, change := range affectedFiles { + fileName := change[2:] + if strings.HasPrefix(change, "A\t") || strings.HasPrefix(change, "M\t") { + fileSize, err := git.GetFileSize(ctx.Repo.GitRepo, newCommitID, fileName, env) + if err != nil { + return + } + if fileSize > setting.Repository.MaxNonLfsFileSize { + ctx.JSON(http.StatusRequestEntityTooLarge, private.Response{ + UserMsg: fmt.Sprintf("The size of the file %s exceeds %d bytes.", fileName, setting.Repository.MaxNonLfsFileSize), + }) + return + } + } + } +}