diff --git a/go.mod b/go.mod index 7d787cf11b..11ab491946 100644 --- a/go.mod +++ b/go.mod @@ -117,9 +117,9 @@ require ( github.com/yuin/goldmark-highlighting/v2 v2.0.0-20230729083705-37449abec8cc github.com/yuin/goldmark-meta v1.1.0 gitlab.com/gitlab-org/api/client-go v0.142.4 - golang.org/x/crypto v0.42.0 + golang.org/x/crypto v0.43.0 golang.org/x/image v0.30.0 - golang.org/x/net v0.44.0 + golang.org/x/net v0.45.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sync v0.17.0 golang.org/x/sys v0.37.0 diff --git a/go.sum b/go.sum index 02a710e7f0..29f5ba745f 100644 --- a/go.sum +++ b/go.sum @@ -840,8 +840,8 @@ golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDf golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= -golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= -golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= +golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= +golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -908,8 +908,8 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= -golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= -golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= +golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM= +golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -987,8 +987,8 @@ golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.28.0/go.mod h1:Sw/lC2IAUZ92udQNf3WodGtn4k/XoLyZoh8v/8uiwek= -golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= -golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= +golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= +golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/modules/actions/workflows.go b/modules/actions/workflows.go index 69f71bf651..26a6ebc370 100644 --- a/modules/actions/workflows.go +++ b/modules/actions/workflows.go @@ -5,7 +5,6 @@ package actions import ( "bytes" - "io" "slices" "strings" @@ -13,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/glob" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" "github.com/nektos/act/pkg/jobparser" @@ -77,7 +77,7 @@ func GetContentFromEntry(entry *git.TreeEntry) ([]byte, error) { if err != nil { return nil, err } - content, err := io.ReadAll(f) + content, err := util.ReadWithLimit(f, 1024*1024) _ = f.Close() if err != nil { return nil, err diff --git a/modules/issue/template/unmarshal.go b/modules/issue/template/unmarshal.go index 1d8e9dd02d..ceab6babf4 100644 --- a/modules/issue/template/unmarshal.go +++ b/modules/issue/template/unmarshal.go @@ -5,7 +5,6 @@ package template import ( "fmt" - "io" "path" "strconv" @@ -76,7 +75,7 @@ func unmarshalFromEntry(entry *git.TreeEntry, filename string) (*api.IssueTempla } defer r.Close() - content, err := io.ReadAll(r) + content, err := util.ReadWithLimit(r, 1024*1024) if err != nil { return nil, fmt.Errorf("read all: %w", err) } diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 513b4dd2b9..5124627395 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -216,7 +216,7 @@ func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { if p.Metadata.Readme != "" { f, err := archive.Open(p.Metadata.Readme) if err == nil { - buf, _ := io.ReadAll(f) + buf, _ := util.ReadWithLimit(f, 1024*1024) m.Readme = string(buf) _ = f.Close() } diff --git a/modules/packages/pub/metadata.go b/modules/packages/pub/metadata.go index 9b00472eb2..a2cf6b728a 100644 --- a/modules/packages/pub/metadata.go +++ b/modules/packages/pub/metadata.go @@ -89,7 +89,7 @@ func ParsePackage(r io.Reader) (*Package, error) { return nil, err } } else if strings.EqualFold(hd.Name, "readme.md") { - data, err := io.ReadAll(tr) + data, err := util.ReadWithLimit(tr, 1024*1024) if err != nil { return nil, err } diff --git a/modules/repository/env.go b/modules/repository/env.go index 78e06f86fb..55a81f006e 100644 --- a/modules/repository/env.go +++ b/modules/repository/env.go @@ -25,6 +25,7 @@ const ( EnvKeyID = "GITEA_KEY_ID" // public key ID EnvDeployKeyID = "GITEA_DEPLOY_KEY_ID" EnvPRID = "GITEA_PR_ID" + EnvPRIndex = "GITEA_PR_INDEX" // not used by Gitea at the moment, it is for custom git hooks EnvPushTrigger = "GITEA_PUSH_TRIGGER" EnvIsInternal = "GITEA_INTERNAL_PUSH" EnvAppURL = "GITEA_ROOT_URL" @@ -50,11 +51,11 @@ func InternalPushingEnvironment(doer *user_model.User, repo *repo_model.Reposito // PushingEnvironment returns an os environment to allow hooks to work on push func PushingEnvironment(doer *user_model.User, repo *repo_model.Repository) []string { - return FullPushingEnvironment(doer, doer, repo, repo.Name, 0) + return FullPushingEnvironment(doer, doer, repo, repo.Name, 0, 0) } // FullPushingEnvironment returns an os environment to allow hooks to work on push -func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model.Repository, repoName string, prID int64) []string { +func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model.Repository, repoName string, prID, prIndex int64) []string { isWiki := "false" if strings.HasSuffix(repoName, ".wiki") { isWiki = "true" @@ -75,6 +76,7 @@ func FullPushingEnvironment(author, committer *user_model.User, repo *repo_model EnvPusherID+"="+strconv.FormatInt(committer.ID, 10), EnvRepoID+"="+strconv.FormatInt(repo.ID, 10), EnvPRID+"="+strconv.FormatInt(prID, 10), + EnvPRIndex+"="+strconv.FormatInt(prIndex, 10), EnvAppURL+"="+setting.AppURL, "SSH_ORIGINAL_COMMAND=gitea-internal", ) diff --git a/modules/util/io.go b/modules/util/io.go index b3dde9d1f6..f5a3d320e5 100644 --- a/modules/util/io.go +++ b/modules/util/io.go @@ -29,7 +29,7 @@ func ReadAtMost(r io.Reader, buf []byte) (n int, err error) { // ReadWithLimit reads at most "limit" bytes from r into buf. // If EOF or ErrUnexpectedEOF occurs while reading, err will be nil. func ReadWithLimit(r io.Reader, n int) (buf []byte, err error) { - return readWithLimit(r, 1024, n) + return readWithLimit(r, 4*1024, n) } func readWithLimit(r io.Reader, batch, limit int) ([]byte, error) { diff --git a/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index ae877827c4..a78ffb3b78 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1482,6 +1482,7 @@ projects.column.new_submit = "Create Column" projects.column.new = "New Column" projects.column.set_default = "Set Default" projects.column.set_default_desc = "Set this column as default for uncategorized issues and pulls" +projects.column.default_column_hint = "New issues added to this project will be added to this column" projects.column.delete = "Delete Column" projects.column.deletion_desc = "Deleting a project column moves all related issues to the default column. Continue?" projects.column.color = "Color" diff --git a/routers/api/packages/container/manifest.go b/routers/api/packages/container/manifest.go index de40215aa7..e408f6ee3b 100644 --- a/routers/api/packages/container/manifest.go +++ b/routers/api/packages/container/manifest.go @@ -10,7 +10,6 @@ import ( "io" "os" "strings" - "time" "code.gitea.io/gitea/models/db" packages_model "code.gitea.io/gitea/models/packages" @@ -260,6 +259,13 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met return nil, err } + // "docker buildx imagetools create" multi-arch operations: + // {"type":"oci","is_tagged":false,"platform":"unknown/unknown"} + // {"type":"oci","is_tagged":false,"platform":"linux/amd64","layer_creation":["ADD file:9233f6f2237d79659a9521f7e390df217cec49f1a8aa3a12147bbca1956acdb9 in /","CMD [\"/bin/sh\"]"]} + // {"type":"oci","is_tagged":false,"platform":"unknown/unknown"} + // {"type":"oci","is_tagged":false,"platform":"linux/arm64","layer_creation":["ADD file:df53811312284306901fdaaff0a357a4bf40d631e662fe9ce6d342442e494b6c in /","CMD [\"/bin/sh\"]"]} + // {"type":"oci","is_tagged":true,"manifests":[{"platform":"linux/amd64","digest":"sha256:72bb73e706c0dec424d00a1febb21deaf1175a70ead009ad8b159729cfcf5769","size":2819478},{"platform":"linux/arm64","digest":"sha256:9e1426dd084a3221663b85ca1ee99d140c50b153917a5c5604c1f9b78229fd24","size":2716499},{"platform":"unknown/unknown","digest":"sha256:b93f03d0ae11b988243e1b2cd8d29accf5b9670547b7bd8c7d96abecc7283e6e","size":1798},{"platform":"unknown/unknown","digest":"sha256:f034b182ba66366c63a5d195c6dfcd3333c027409c0ac98e55ade36aaa3b2963","size":1798}]} + _pv := &packages_model.PackageVersion{ PackageID: p.ID, CreatorID: mci.Creator.ID, @@ -273,25 +279,16 @@ func createPackageAndVersion(ctx context.Context, mci *manifestCreationInfo, met log.Error("Error inserting package: %v", err) return nil, err } - - if container_module.IsMediaTypeImageIndex(mci.MediaType) { - if pv.CreatedUnix.AsTime().Before(time.Now().Add(-24 * time.Hour)) { - if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { - return nil, err - } - // keep download count on overwriting - _pv.DownloadCount = pv.DownloadCount - if pv, err = packages_model.GetOrInsertVersion(ctx, _pv); err != nil { - if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) { - log.Error("Error inserting package: %v", err) - return nil, err - } - } - } else { - err = packages_model.UpdateVersion(ctx, &packages_model.PackageVersion{ID: pv.ID, MetadataJSON: _pv.MetadataJSON}) - if err != nil { - return nil, err - } + if err = packages_service.DeletePackageVersionAndReferences(ctx, pv); err != nil { + return nil, err + } + // keep download count on overwriting + _pv.DownloadCount = pv.DownloadCount + pv, err = packages_model.GetOrInsertVersion(ctx, _pv) + if err != nil { + if !errors.Is(err, packages_model.ErrDuplicatePackageVersion) { + log.Error("Error inserting package: %v", err) + return nil, err } } } diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index f3375e4898..7750278a8d 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -9,7 +9,6 @@ import ( "encoding/csv" "errors" "fmt" - "html" "io" "net/http" "net/url" @@ -957,30 +956,26 @@ func ExcerptBlob(ctx *context.Context) { ctx.HTTPError(http.StatusInternalServerError, "getExcerptLines") return } - if idxRight > lastRight { - lineText := " " - if rightHunkSize > 0 || leftHunkSize > 0 { - lineText = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize) - } - lineText = html.EscapeString(lineText) - lineSection := &gitdiff.DiffLine{ - Type: gitdiff.DiffLineSection, - Content: lineText, - SectionInfo: &gitdiff.DiffLineSectionInfo{ - Path: filePath, - LastLeftIdx: lastLeft, - LastRightIdx: lastRight, - LeftIdx: idxLeft, - RightIdx: idxRight, - LeftHunkSize: leftHunkSize, - RightHunkSize: rightHunkSize, - }, - } + + newLineSection := &gitdiff.DiffLine{ + Type: gitdiff.DiffLineSection, + SectionInfo: &gitdiff.DiffLineSectionInfo{ + Path: filePath, + LastLeftIdx: lastLeft, + LastRightIdx: lastRight, + LeftIdx: idxLeft, + RightIdx: idxRight, + LeftHunkSize: leftHunkSize, + RightHunkSize: rightHunkSize, + }, + } + if newLineSection.GetExpandDirection() != "" { + newLineSection.Content = fmt.Sprintf("@@ -%d,%d +%d,%d @@\n", idxLeft, leftHunkSize, idxRight, rightHunkSize) switch direction { case "up": - section.Lines = append([]*gitdiff.DiffLine{lineSection}, section.Lines...) + section.Lines = append([]*gitdiff.DiffLine{newLineSection}, section.Lines...) case "down": - section.Lines = append(section.Lines, lineSection) + section.Lines = append(section.Lines, newLineSection) } } diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 9d04b90d79..d06605a08a 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -1243,7 +1243,11 @@ func MergePullRequest(ctx *context.Context) { func deleteBranchAfterMergeAndFlashMessage(ctx *context.Context, prID int64) { var fullBranchName string err := repo_service.DeleteBranchAfterMerge(ctx, ctx.Doer, prID, &fullBranchName) - if errTr := util.ErrorAsTranslatable(err); errTr != nil { + if errors.Is(err, util.ErrPermissionDenied) || errors.Is(err, util.ErrNotExist) { + // no need to show error to end users if no permission or branch not exist + log.Debug("DeleteBranchAfterMerge (ignore unnecessary error): %v", err) + return + } else if errTr := util.ErrorAsTranslatable(err); errTr != nil { ctx.Flash.Error(errTr.Translate(ctx.Locale)) return } else if err == nil { diff --git a/routers/web/repo/view_file.go b/routers/web/repo/view_file.go index ea3920439d..167cd5f927 100644 --- a/routers/web/repo/view_file.go +++ b/routers/web/repo/view_file.go @@ -92,8 +92,6 @@ func handleFileViewRenderMarkup(ctx *context.Context, filename string, sniffedTy ctx.ServerError("Render", err) return true } - // to prevent iframe from loading third-party url - ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'") return true } @@ -241,14 +239,17 @@ func prepareFileView(ctx *context.Context, entry *git.TreeEntry) { // * IsRenderableXxx: some files are rendered by backend "markup" engine, some are rendered by frontend (pdf, 3d) // * DefaultViewMode: when there is no "display" query parameter, which view mode should be used by default, source or rendered - utf8Reader := charset.ToUTF8WithFallbackReader(io.MultiReader(bytes.NewReader(buf), dataRc), charset.ConvertOpts{}) + contentReader := io.MultiReader(bytes.NewReader(buf), dataRc) + if fInfo.st.IsRepresentableAsText() { + contentReader = charset.ToUTF8WithFallbackReader(contentReader, charset.ConvertOpts{}) + } switch { case fInfo.blobOrLfsSize >= setting.UI.MaxDisplayFileSize: ctx.Data["IsFileTooLarge"] = true - case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, utf8Reader): + case handleFileViewRenderMarkup(ctx, entry.Name(), fInfo.st, buf, contentReader): // it also sets ctx.Data["FileContent"] and more ctx.Data["IsMarkup"] = true - case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, utf8Reader): + case handleFileViewRenderSource(ctx, entry.Name(), attrs, fInfo, contentReader): // it also sets ctx.Data["FileContent"] and more ctx.Data["IsDisplayingSource"] = true case handleFileViewRenderImage(ctx, fInfo, buf): diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index e7c34ba1d6..542ac9c731 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -133,7 +133,7 @@ func wikiContentsByEntry(ctx *context.Context, entry *git.TreeEntry) []byte { return nil } defer reader.Close() - content, err := io.ReadAll(reader) + content, err := util.ReadWithLimit(reader, 5*1024*1024) // 5MB should be enough for a wiki page if err != nil { ctx.ServerError("ReadAll", err) return nil diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 830bb1131b..4ad06bc04f 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -82,14 +82,34 @@ type DiffLine struct { // DiffLineSectionInfo represents diff line section meta data type DiffLineSectionInfo struct { - Path string - LastLeftIdx int - LastRightIdx int - LeftIdx int - RightIdx int + Path string + + // These line "idx" are 1-based line numbers + // Left/Right refer to the left/right side of the diff: + // + // LastLeftIdx | LastRightIdx + // [up/down expander] @@ hunk info @@ + // LeftIdx | RightIdx + + LastLeftIdx int + LastRightIdx int + LeftIdx int + RightIdx int + + // Hunk sizes of the hidden lines LeftHunkSize int RightHunkSize int + // For example: + // 17 | 31 + // [up/down] @@ -40,23 +54,9 @@ .... + // 40 | 54 + // + // In this case: + // LastLeftIdx = 17, LastRightIdx = 31 + // LeftHunkSize = 23, RightHunkSize = 9 + // LeftIdx = 40, RightIdx = 54 + HiddenCommentIDs []int64 // IDs of hidden comments in this section } @@ -158,13 +178,13 @@ func (d *DiffLine) getBlobExcerptQuery() string { return query } -func (d *DiffLine) getExpandDirection() string { +func (d *DiffLine) GetExpandDirection() string { if d.Type != DiffLineSection || d.SectionInfo == nil || d.SectionInfo.LeftIdx-d.SectionInfo.LastLeftIdx <= 1 || d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx <= 1 { return "" } if d.SectionInfo.LastLeftIdx <= 0 && d.SectionInfo.LastRightIdx <= 0 { return "up" - } else if d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx > BlobExcerptChunkSize && d.SectionInfo.RightHunkSize > 0 { + } else if d.SectionInfo.RightIdx-d.SectionInfo.LastRightIdx-1 > BlobExcerptChunkSize && d.SectionInfo.RightHunkSize > 0 { return "updown" } else if d.SectionInfo.LeftHunkSize <= 0 && d.SectionInfo.RightHunkSize <= 0 { return "down" @@ -202,13 +222,13 @@ func (d *DiffLine) RenderBlobExcerptButtons(fileNameHash string, data *DiffBlobE content += htmlutil.HTMLFormat(`%d`, tooltip, len(d.SectionInfo.HiddenCommentIDs)) } - expandDirection := d.getExpandDirection() - if expandDirection == "up" || expandDirection == "updown" { - content += makeButton("up", "octicon-fold-up") - } + expandDirection := d.GetExpandDirection() if expandDirection == "updown" || expandDirection == "down" { content += makeButton("down", "octicon-fold-down") } + if expandDirection == "up" || expandDirection == "updown" { + content += makeButton("up", "octicon-fold-up") + } if expandDirection == "single" { content += makeButton("single", "octicon-fold") } diff --git a/services/gitdiff/gitdiff_test.go b/services/gitdiff/gitdiff_test.go index 51fb9b58d6..721ae0dfc7 100644 --- a/services/gitdiff/gitdiff_test.go +++ b/services/gitdiff/gitdiff_test.go @@ -983,3 +983,126 @@ func TestDiffLine_RenderBlobExcerptButtons(t *testing.T) { }) } } + +func TestDiffLine_GetExpandDirection(t *testing.T) { + cases := []struct { + name string + diffLine *DiffLine + direction string + }{ + { + name: "NotSectionLine", + diffLine: &DiffLine{Type: DiffLineAdd, SectionInfo: &DiffLineSectionInfo{}}, + direction: "", + }, + { + name: "NilSectionInfo", + diffLine: &DiffLine{Type: DiffLineSection, SectionInfo: nil}, + direction: "", + }, + { + name: "NoHiddenLines", + // last block stops at line 100, next block starts at line 101, so no hidden lines, no expansion. + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 101, + LeftIdx: 101, + }, + }, + direction: "", + }, + { + name: "FileHead", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 0, // LastXxxIdx = 0 means this is the first section in the file. + LastLeftIdx: 0, + RightIdx: 1, + LeftIdx: 1, + }, + }, + direction: "", + }, + { + name: "FileHeadHiddenLines", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 0, + LastLeftIdx: 0, + RightIdx: 101, + LeftIdx: 101, + }, + }, + direction: "up", + }, + { + name: "HiddenSingleHunk", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 102, + LeftIdx: 102, + RightHunkSize: 1234, // non-zero dummy value + LeftHunkSize: 5678, // non-zero dummy value + }, + }, + direction: "single", + }, + { + name: "HiddenSingleFullHunk", + // the hidden lines can exactly fit into one hunk + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 100 + BlobExcerptChunkSize + 1, + LeftIdx: 100 + BlobExcerptChunkSize + 1, + RightHunkSize: 1234, // non-zero dummy value + LeftHunkSize: 5678, // non-zero dummy value + }, + }, + direction: "single", + }, + { + name: "HiddenUpDownHunks", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 100 + BlobExcerptChunkSize + 2, + LeftIdx: 100 + BlobExcerptChunkSize + 2, + RightHunkSize: 1234, // non-zero dummy value + LeftHunkSize: 5678, // non-zero dummy value + }, + }, + direction: "updown", + }, + { + name: "FileTail", + diffLine: &DiffLine{ + Type: DiffLineSection, + SectionInfo: &DiffLineSectionInfo{ + LastRightIdx: 100, + LastLeftIdx: 100, + RightIdx: 102, + LeftIdx: 102, + RightHunkSize: 0, + LeftHunkSize: 0, + }, + }, + direction: "down", + }, + } + for _, c := range cases { + assert.Equal(t, c.direction, c.diffLine.GetExpandDirection(), "case %s expected direction: %s", c.name, c.direction) + } +} diff --git a/services/issue/template.go b/services/issue/template.go index 4b0f1aa987..99977c67cf 100644 --- a/services/issue/template.go +++ b/services/issue/template.go @@ -5,7 +5,6 @@ package issue import ( "fmt" - "io" "net/url" "path" "strings" @@ -15,6 +14,7 @@ import ( "code.gitea.io/gitea/modules/issue/template" "code.gitea.io/gitea/modules/log" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "gopkg.in/yaml.v3" ) @@ -65,7 +65,7 @@ func GetTemplateConfig(gitRepo *git.Repository, path string, commit *git.Commit) defer reader.Close() - configContent, err := io.ReadAll(reader) + configContent, err := util.ReadWithLimit(reader, 1024*1024) if err != nil { return GetDefaultTemplateConfig(), err } diff --git a/services/pull/merge.go b/services/pull/merge.go index 9c7e09a227..f5430546a3 100644 --- a/services/pull/merge.go +++ b/services/pull/merge.go @@ -403,6 +403,7 @@ func doMergeAndPush(ctx context.Context, pr *issues_model.PullRequest, doer *use pr.BaseRepo, pr.BaseRepo.Name, pr.ID, + pr.Index, ) mergeCtx.env = append(mergeCtx.env, repo_module.EnvPushTrigger+"="+string(pushTrigger)) diff --git a/services/pull/update_rebase.go b/services/pull/update_rebase.go index e6845f6b14..6a70c03467 100644 --- a/services/pull/update_rebase.go +++ b/services/pull/update_rebase.go @@ -80,6 +80,7 @@ func updateHeadByRebaseOnToBase(ctx context.Context, pr *issues_model.PullReques pr.HeadRepo, pr.HeadRepo.Name, pr.ID, + pr.Index, )). WithDir(mergeCtx.tmpBasePath). WithStdout(mergeCtx.outbuf). diff --git a/services/repository/generate.go b/services/repository/generate.go index 062c6f4fb1..caf15265a0 100644 --- a/services/repository/generate.go +++ b/services/repository/generate.go @@ -7,7 +7,9 @@ import ( "bufio" "bytes" "context" + "errors" "fmt" + "io/fs" "os" "path/filepath" "regexp" @@ -138,31 +140,37 @@ func (gt *giteaTemplateFileMatcher) Match(s string) bool { return false } -func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) { - localPath := filepath.Join(tmpDir, ".gitea", "template") - if _, err := os.Stat(localPath); os.IsNotExist(err) { - return nil, nil - } else if err != nil { +func readLocalTmpRepoFileContent(localPath string, limit int) ([]byte, error) { + ok, err := util.IsRegularFile(localPath) + if err != nil { return nil, err + } else if !ok { + return nil, fs.ErrNotExist } - content, err := os.ReadFile(localPath) + f, err := os.Open(localPath) if err != nil { return nil, err } + defer f.Close() + return util.ReadWithLimit(f, limit) +} + +func readGiteaTemplateFile(tmpDir string) (*giteaTemplateFileMatcher, error) { + localPath := filepath.Join(tmpDir, ".gitea", "template") + content, err := readLocalTmpRepoFileContent(localPath, 1024*1024) + if err != nil { + return nil, err + } return newGiteaTemplateFileMatcher(localPath, content), nil } func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, templateRepo, generateRepo *repo_model.Repository) error { tmpFullPath := filepath.Join(tmpDir, tmpDirSubPath) - if ok, err := util.IsRegularFile(tmpFullPath); !ok { - return err - } - - content, err := os.ReadFile(tmpFullPath) + content, err := readLocalTmpRepoFileContent(tmpFullPath, 1024*1024) if err != nil { - return err + return util.Iif(errors.Is(err, fs.ErrNotExist), nil, err) } if err := util.Remove(tmpFullPath); err != nil { return err @@ -172,7 +180,7 @@ func substGiteaTemplateFile(ctx context.Context, tmpDir, tmpDirSubPath string, t substSubPath := filepath.Clean(filePathSanitize(generateExpansion(ctx, tmpDirSubPath, templateRepo, generateRepo))) newLocalPath := filepath.Join(tmpDir, substSubPath) regular, err := util.IsRegularFile(newLocalPath) - if canWrite := regular || os.IsNotExist(err); !canWrite { + if canWrite := regular || errors.Is(err, fs.ErrNotExist); !canWrite { return nil } if err := os.MkdirAll(filepath.Dir(newLocalPath), 0o755); err != nil { @@ -242,15 +250,15 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r // Variable expansion fileMatcher, err := readGiteaTemplateFile(tmpDir) - if err != nil { - return fmt.Errorf("readGiteaTemplateFile: %w", err) - } - - if fileMatcher != nil { + if err == nil { err = processGiteaTemplateFile(ctx, tmpDir, templateRepo, generateRepo, fileMatcher) if err != nil { - return err + return fmt.Errorf("processGiteaTemplateFile: %w", err) } + } else if errors.Is(err, fs.ErrNotExist) { + log.Debug("skip processing repo template files: no available .gitea/template") + } else { + return fmt.Errorf("readGiteaTemplateFile: %w", err) } if err = git.InitRepository(ctx, tmpDir, false, templateRepo.ObjectFormatName); err != nil { diff --git a/services/repository/generate_test.go b/services/repository/generate_test.go index 19b84c7bde..9c01911ded 100644 --- a/services/repository/generate_test.go +++ b/services/repository/generate_test.go @@ -4,6 +4,7 @@ package repository import ( + "io/fs" "os" "path/filepath" "testing" @@ -175,6 +176,31 @@ func TestProcessGiteaTemplateFile(t *testing.T) { // subst from a link, skip, and the target is unchanged assertSymLink("subst-${TEMPLATE_NAME}-from-link", tmpDir+"/sub/link-target") } + + { + templateFilePath := tmpDir + "/.gitea/template" + + _ = os.Remove(templateFilePath) + _, err := os.Lstat(templateFilePath) + require.ErrorIs(t, err, fs.ErrNotExist) + _, err = readGiteaTemplateFile(tmpDir) // no template file + require.ErrorIs(t, err, fs.ErrNotExist) + + _ = os.WriteFile(templateFilePath+".target", []byte("test-data-target"), 0o644) + _ = os.Symlink(templateFilePath+".target", templateFilePath) + content, _ := os.ReadFile(templateFilePath) + require.Equal(t, "test-data-target", string(content)) + _, err = readGiteaTemplateFile(tmpDir) // symlinked template file + require.ErrorIs(t, err, fs.ErrNotExist) + + _ = os.Remove(templateFilePath) + _ = os.WriteFile(templateFilePath, []byte("test-data-regular"), 0o644) + content, _ = os.ReadFile(templateFilePath) + require.Equal(t, "test-data-regular", string(content)) + fm, err := readGiteaTemplateFile(tmpDir) // regular template file + require.NoError(t, err) + assert.Len(t, fm.globs, 1) + } } func TestTransformers(t *testing.T) { diff --git a/services/webhook/deliver.go b/services/webhook/deliver.go index b6611a3576..58fba9f68d 100644 --- a/services/webhook/deliver.go +++ b/services/webhook/deliver.go @@ -30,6 +30,7 @@ import ( "code.gitea.io/gitea/modules/queue" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" webhook_module "code.gitea.io/gitea/modules/webhook" ) @@ -264,7 +265,7 @@ func Deliver(ctx context.Context, t *webhook_model.HookTask) error { t.ResponseInfo.Headers[k] = strings.Join(vals, ",") } - p, err := io.ReadAll(resp.Body) + p, err := util.ReadWithLimit(resp.Body, 1024*1024) if err != nil { t.ResponseInfo.Body = fmt.Sprintf("read body: %s", err) return fmt.Errorf("unable to deliver webhook task[%d] in %s as unable to read response body: %w", t.ID, w.URL, err) diff --git a/services/wiki/wiki.go b/services/wiki/wiki.go index 25f836dd5d..a9dc726982 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -223,6 +223,7 @@ func updateWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model repo, repo.Name+".wiki", 0, + 0, ), }); err != nil { log.Error("Push failed: %v", err) @@ -341,6 +342,7 @@ func DeleteWikiPage(ctx context.Context, doer *user_model.User, repo *repo_model repo, repo.Name+".wiki", 0, + 0, ), }); err != nil { if git.IsErrPushOutOfDate(err) || git.IsErrPushRejected(err) { diff --git a/templates/projects/view.tmpl b/templates/projects/view.tmpl index 389321cb1c..53e41f35f2 100644 --- a/templates/projects/view.tmpl +++ b/templates/projects/view.tmpl @@ -82,7 +82,9 @@
{{.NumIssues}}
-
{{.Title}}
+
+ {{if .Default}}{{svg "octicon-star"}} {{end}}{{.Title}} +
{{if $canWriteProject}} `, + "html.no-sanitizer": ``, + "bin.no-sanitizer": binaryContent, + }) require.NoError(t, err) t.Run("RenderNoSanitizer", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/file.no-sanitizer") + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/html.no-sanitizer") resp := MakeRequest(t, req, http.StatusOK) - doc := NewHTMLParser(t, resp.Body) - div := doc.Find("div.file-view") + div := NewHTMLParser(t, resp.Body).Find("div.file-view") data, err := div.Html() assert.NoError(t, err) - assert.Equal(t, ``, strings.TrimSpace(data)) + assert.Equal(t, ``, strings.TrimSpace(data)) + + req = NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer") + resp = MakeRequest(t, req, http.StatusOK) + div = NewHTMLParser(t, resp.Body).Find("div.file-view") + data, err = div.Html() + assert.NoError(t, err) + assert.Equal(t, strings.ReplaceAll(binaryContent, "\x00", ""), strings.TrimSpace(data)) // HTML template engine removes the null bytes }) }) t.Run("RenderContentDirectly", func(t *testing.T) { - req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html") resp := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "text/html; charset=utf-8", resp.Header().Get("Content-Type")) @@ -55,18 +72,21 @@ func TestExternalMarkupRenderer(t *testing.T) { div := doc.Find("div.file-view") data, err := div.Html() assert.NoError(t, err) - assert.Equal(t, "
\n\ttest external renderer\n
", strings.TrimSpace(data)) + // the content is fully sanitized + assert.Equal(t, `
<script></script>
`, strings.TrimSpace(data)) }) - // above tested "no-sanitizer" mode, then we test iframe mode below + // above tested in-page rendering (no iframe), then we test iframe mode below r := markup.GetRendererByFileName("any-file.html").(*external.Renderer) defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)() + assert.True(t, r.NeedPostProcess()) r = markup.GetRendererByFileName("any-file.no-sanitizer").(*external.Renderer) defer test.MockVariableValue(&r.RenderContentMode, setting.RenderContentModeIframe)() + assert.False(t, r.NeedPostProcess()) t.Run("RenderContentInIFrame", func(t *testing.T) { t.Run("DefaultSandbox", func(t *testing.T) { - req := NewRequest(t, "GET", "/user30/renderer/src/branch/master/README.html") + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/test.html") t.Run("ParentPage", func(t *testing.T) { respParent := MakeRequest(t, req, http.StatusOK) @@ -77,31 +97,42 @@ func TestExternalMarkupRenderer(t *testing.T) { // default sandbox on parent page assert.Equal(t, "allow-scripts allow-popups", iframe.AttrOr("sandbox", "")) - assert.Equal(t, "/user30/renderer/render/branch/master/README.html", iframe.AttrOr("data-src", "")) + assert.Equal(t, "/user2/repo1/render/branch/master/test.html", iframe.AttrOr("data-src", "")) }) t.Run("SubPage", func(t *testing.T) { - req = NewRequest(t, "GET", "/user30/renderer/render/branch/master/README.html") + req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/test.html") respSub := MakeRequest(t, req, http.StatusOK) assert.Equal(t, "text/html; charset=utf-8", respSub.Header().Get("Content-Type")) // default sandbox in sub page response assert.Equal(t, "frame-src 'self'; sandbox allow-scripts allow-popups", respSub.Header().Get("Content-Security-Policy")) - assert.Equal(t, "
\n\ttest external renderer\n
\n", respSub.Body.String()) + // FIXME: actually here is a bug (legacy design problem), the "PostProcess" will escape "
<script></script>
`, respSub.Body.String()) }) }) t.Run("NoSanitizerNoSandbox", func(t *testing.T) { - req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/file.no-sanitizer") - respParent := MakeRequest(t, req, http.StatusOK) - iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe") - assert.Equal(t, "/user2/repo1/render/branch/master/file.no-sanitizer", iframe.AttrOr("data-src", "")) + t.Run("BinaryContent", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/src/branch/master/bin.no-sanitizer") + respParent := MakeRequest(t, req, http.StatusOK) + iframe := NewHTMLParser(t, respParent.Body).Find("iframe.external-render-iframe") + assert.Equal(t, "/user2/repo1/render/branch/master/bin.no-sanitizer", iframe.AttrOr("data-src", "")) - req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/file.no-sanitizer") - respSub := MakeRequest(t, req, http.StatusOK) + req = NewRequest(t, "GET", "/user2/repo1/render/branch/master/bin.no-sanitizer") + respSub := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, binaryContent, respSub.Body.String()) // raw content should keep the raw bytes (including invalid UTF-8 bytes), and no "external-render-iframe" helpers - // no sandbox (disabled by RENDER_CONTENT_SANDBOX) - assert.Empty(t, iframe.AttrOr("sandbox", "")) - assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) + // no sandbox (disabled by RENDER_CONTENT_SANDBOX) + assert.Empty(t, iframe.AttrOr("sandbox", "")) + assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) + }) + + t.Run("HTMLContentWithExternalRenderIframeHelper", func(t *testing.T) { + req := NewRequest(t, "GET", "/user2/repo1/render/branch/master/html.no-sanitizer") + respSub := MakeRequest(t, req, http.StatusOK) + assert.Equal(t, ``, respSub.Body.String()) + assert.Equal(t, "frame-src 'self'", respSub.Header().Get("Content-Security-Policy")) + }) }) }) } diff --git a/tests/sqlite.ini.tmpl b/tests/sqlite.ini.tmpl index 9d184bce6a..61f7e2a46d 100644 --- a/tests/sqlite.ini.tmpl +++ b/tests/sqlite.ini.tmpl @@ -122,7 +122,7 @@ RENDER_CONTENT_MODE = sanitized [markup.no-sanitizer] ENABLED = true FILE_EXTENSIONS = .no-sanitizer -RENDER_COMMAND = echo '' +RENDER_COMMAND = go run tools/test-echo.go ; This test case is reused, at first it is used to test "no-sanitizer" (sandbox doesn't take effect here) ; Then it will be updated and used to test "iframe + sandbox-disabled" RENDER_CONTENT_MODE = no-sanitizer