From a62ed19da6093c94401cade5701b44684820fcf6 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 6 Apr 2025 17:13:02 +0800 Subject: [PATCH 01/26] Use `overflow-wrap: anywhere` to replace `word-break: break-all` (#34126) --- templates/status/500.tmpl | 2 +- web_src/css/base.css | 1 - web_src/css/markup/content.css | 3 +-- web_src/css/repo.css | 3 +-- web_src/js/components/RepoActionView.vue | 1 - 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/templates/status/500.tmpl b/templates/status/500.tmpl index 198f1ea898..6dfa2d8a8c 100644 --- a/templates/status/500.tmpl +++ b/templates/status/500.tmpl @@ -38,7 +38,7 @@ {{if .ErrorMsg}}

{{ctx.Locale.Tr "error.occurred"}}:

-
{{.ErrorMsg}}
+
{{.ErrorMsg}}
{{end}}
diff --git a/web_src/css/base.css b/web_src/css/base.css index f276ced596..de656c0d95 100644 --- a/web_src/css/base.css +++ b/web_src/css/base.css @@ -878,7 +878,6 @@ overflow-menu .ui.label { .code-inner { font: 12px var(--fonts-monospace); white-space: pre-wrap; - word-break: break-all; overflow-wrap: anywhere; line-height: inherit; /* needed for inline code preview in markup */ } diff --git a/web_src/css/markup/content.css b/web_src/css/markup/content.css index 7368a1fb00..937224a9d7 100644 --- a/web_src/css/markup/content.css +++ b/web_src/css/markup/content.css @@ -447,8 +447,7 @@ margin: 0; font-size: 100%; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; background: transparent; border: 0; } diff --git a/web_src/css/repo.css b/web_src/css/repo.css index 87af299dad..db44e2a778 100644 --- a/web_src/css/repo.css +++ b/web_src/css/repo.css @@ -1724,8 +1724,7 @@ tbody.commit-list { line-height: 18px; margin: 1em; white-space: pre-wrap; - word-break: break-all; - overflow-wrap: break-word; + overflow-wrap: anywhere; } .content-history-detail-dialog .header .avatar { diff --git a/web_src/js/components/RepoActionView.vue b/web_src/js/components/RepoActionView.vue index 2ef528620d..640ad8341f 100644 --- a/web_src/js/components/RepoActionView.vue +++ b/web_src/js/components/RepoActionView.vue @@ -955,7 +955,6 @@ export default defineComponent({ .job-step-logs .job-log-line .log-msg { flex: 1; - word-break: break-all; white-space: break-spaces; margin-left: 10px; overflow-wrap: anywhere; From e94f8d56f113077b2fc8972b6ba31a7ad366cfb9 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Sun, 6 Apr 2025 17:38:08 +0800 Subject: [PATCH 02/26] Correctly handle submodule view and avoid throwing 500 error (#34121) Auto-redirect for in-site links, and show 404 for external links (to avoid open redirect or phishing) --- routers/web/repo/view_home.go | 27 +++++++++++++++++++++++++ routers/web/repo/view_home_test.go | 32 ++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 routers/web/repo/view_home_test.go diff --git a/routers/web/repo/view_home.go b/routers/web/repo/view_home.go index c6f462bccf..3b053821ee 100644 --- a/routers/web/repo/view_home.go +++ b/routers/web/repo/view_home.go @@ -20,6 +20,8 @@ import ( unit_model "code.gitea.io/gitea/models/unit" user_model "code.gitea.io/gitea/models/user" "code.gitea.io/gitea/modules/git" + giturl "code.gitea.io/gitea/modules/git/url" + "code.gitea.io/gitea/modules/httplib" "code.gitea.io/gitea/modules/log" repo_module "code.gitea.io/gitea/modules/repository" "code.gitea.io/gitea/modules/setting" @@ -302,8 +304,33 @@ func handleRepoEmptyOrBroken(ctx *context.Context) { ctx.Redirect(link) } +func handleRepoViewSubmodule(ctx *context.Context, submodule *git.SubModule) { + submoduleRepoURL, err := giturl.ParseRepositoryURL(ctx, submodule.URL) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: ParseRepositoryURL", err) + return + } + submoduleURL := giturl.MakeRepositoryWebLink(submoduleRepoURL) + if httplib.IsCurrentGiteaSiteURL(ctx, submoduleURL) { + ctx.RedirectToCurrentSite(submoduleURL) + } else { + // don't auto-redirect to external URL, to avoid open redirect or phishing + ctx.Data["NotFoundPrompt"] = submoduleURL + ctx.NotFound(nil) + } +} + func prepareToRenderDirOrFile(entry *git.TreeEntry) func(ctx *context.Context) { return func(ctx *context.Context) { + if entry.IsSubModule() { + submodule, err := ctx.Repo.Commit.GetSubModule(entry.Name()) + if err != nil { + HandleGitError(ctx, "prepareToRenderDirOrFile: GetSubModule", err) + return + } + handleRepoViewSubmodule(ctx, submodule) + return + } if entry.IsDir() { prepareToRenderDirectory(ctx) } else { diff --git a/routers/web/repo/view_home_test.go b/routers/web/repo/view_home_test.go new file mode 100644 index 0000000000..6264dba71c --- /dev/null +++ b/routers/web/repo/view_home_test.go @@ -0,0 +1,32 @@ +// Copyright 2025 The Gitea Authors. All rights reserved. +// SPDX-License-Identifier: MIT + +package repo + +import ( + "net/http" + "testing" + + "code.gitea.io/gitea/models/unittest" + git_module "code.gitea.io/gitea/modules/git" + "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/services/contexttest" + + "github.com/stretchr/testify/assert" +) + +func TestViewHomeSubmoduleRedirect(t *testing.T) { + unittest.PrepareTestEnv(t) + + ctx, _ := contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule := &git_module.SubModule{Path: "test-submodule", URL: setting.AppURL + "user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + assert.Equal(t, http.StatusSeeOther, ctx.Resp.WrittenStatus()) + assert.Equal(t, "/user2/repo-other", ctx.Resp.Header().Get("Location")) + + ctx, _ = contexttest.MockContext(t, "/user2/repo1/src/branch/master/test-submodule") + submodule = &git_module.SubModule{Path: "test-submodule", URL: "https://other/user2/repo-other.git"} + handleRepoViewSubmodule(ctx, submodule) + // do not auto-redirect for external URLs, to avoid open redirect or phishing + assert.Equal(t, http.StatusNotFound, ctx.Resp.WrittenStatus()) +} From 3fe082a5a33011f3467eb2ca7dfaf445d283c6db Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 7 Apr 2025 01:08:10 +0800 Subject: [PATCH 03/26] Remove dead code: RepoRef (#34131) The RepoRef is a no-op since Refactor ref type (#33242) (Jan 14) --- routers/web/web.go | 28 ++++++++++++++-------------- services/context/repo.go | 6 ------ 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/routers/web/web.go b/routers/web/web.go index 7948c5f5ff..fcddcad1b1 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -1211,7 +1211,7 @@ func registerWebRoutes(m *web.Router) { m.Get("/comments/{id}/attachments", repo.GetCommentAttachments) m.Get("/labels", repo.RetrieveLabelsForList, repo.Labels) m.Get("/milestones", repo.Milestones) - m.Get("/milestone/{id}", context.RepoRef(), repo.MilestoneIssuesAndPulls) + m.Get("/milestone/{id}", repo.MilestoneIssuesAndPulls) m.Get("/issues/suggestions", repo.IssueSuggestions) }, optSignIn, context.RepoAssignment, reqRepoIssuesOrPullsReader) // issue/pull attachments, labels, milestones // end "/{username}/{reponame}": view milestone, label, issue, pull, etc @@ -1225,9 +1225,9 @@ func registerWebRoutes(m *web.Router) { m.Group("/{username}/{reponame}", func() { // edit issues, pulls, labels, milestones, etc m.Group("/issues", func() { m.Group("/new", func() { - m.Combo("").Get(context.RepoRef(), repo.NewIssue). + m.Combo("").Get(repo.NewIssue). Post(web.Bind(forms.CreateIssueForm{}), repo.NewIssuePost) - m.Get("/choose", context.RepoRef(), repo.NewIssueChooseTemplate) + m.Get("/choose", repo.NewIssueChooseTemplate) }) m.Get("/search", repo.SearchRepoIssuesJSON) }, reqUnitIssuesReader) @@ -1290,7 +1290,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/edit", web.Bind(forms.CreateLabelForm{}), repo.UpdateLabel) m.Post("/delete", repo.DeleteLabel) m.Post("/initialize", web.Bind(forms.InitializeLabelsForm{}), repo.InitializeLabels) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) m.Group("/milestones", func() { m.Combo("/new").Get(repo.NewMilestone). @@ -1299,7 +1299,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/{id}/edit", web.Bind(forms.CreateMilestoneForm{}), repo.EditMilestonePost) m.Post("/{id}/{action}", repo.ChangeMilestoneStatus) m.Post("/delete", repo.DeleteMilestone) - }, reqRepoIssuesOrPullsWriter, context.RepoRef()) + }, reqRepoIssuesOrPullsWriter) // FIXME: many "pulls" requests are sent to "issues" endpoints incorrectly, need to move these routes to the proper place m.Group("/issues", func() { @@ -1377,7 +1377,7 @@ func registerWebRoutes(m *web.Router) { m.Post("/delete", repo.DeleteRelease) m.Post("/attachments", repo.UploadReleaseAttachment) m.Post("/attachments/remove", repo.DeleteAttachment) - }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter, context.RepoRef()) + }, reqSignIn, context.RepoMustNotBeArchived(), reqRepoReleaseWriter) m.Group("/releases", func() { m.Get("/edit/*", repo.EditRelease) m.Post("/edit/*", web.Bind(forms.EditReleaseForm{}), repo.EditReleasePost) @@ -1506,19 +1506,19 @@ func registerWebRoutes(m *web.Router) { m.Get(".diff", repo.DownloadPullDiff) m.Get(".patch", repo.DownloadPullPatch) m.Group("/commits", func() { - m.Get("", context.RepoRef(), repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) - m.Get("/list", context.RepoRef(), repo.GetPullCommits) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) + m.Get("", repo.SetWhitespaceBehavior, repo.GetPullDiffStats, repo.ViewPullCommits) + m.Get("/list", repo.GetPullCommits) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForSingleCommit) }) m.Post("/merge", context.RepoMustNotBeArchived(), web.Bind(forms.MergePullRequestForm{}), repo.MergePullRequest) m.Post("/cancel_auto_merge", context.RepoMustNotBeArchived(), repo.CancelAutoMergePullRequest) m.Post("/update", repo.UpdatePullRequest) m.Post("/set_allow_maintainer_edit", web.Bind(forms.UpdateAllowEditsForm{}), repo.SetAllowEdits) - m.Post("/cleanup", context.RepoMustNotBeArchived(), context.RepoRef(), repo.CleanUpPullRequest) + m.Post("/cleanup", context.RepoMustNotBeArchived(), repo.CleanUpPullRequest) m.Group("/files", func() { - m.Get("", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) - m.Get("/{sha:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) - m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", context.RepoRef(), repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) + m.Get("", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForAllCommitsOfPr) + m.Get("/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesStartingFromCommit) + m.Get("/{shaFrom:[a-f0-9]{7,40}}..{shaTo:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.SetShowOutdatedComments, repo.ViewPullFilesForRange) m.Group("/reviews", func() { m.Get("/new_comment", repo.RenderNewCodeCommentForm) m.Post("/comments", web.Bind(forms.CodeCommentForm{}), repo.SetShowOutdatedComments, repo.CreateCodeComment) @@ -1605,7 +1605,7 @@ func registerWebRoutes(m *web.Router) { m.Get("/tree/*", repo.RedirectRepoTreeToSrc) // redirect "/owner/repo/tree/*" requests to "/owner/repo/src/*" m.Get("/blob/*", repo.RedirectRepoBlobToCommit) // redirect "/owner/repo/blob/*" requests to "/owner/repo/src/commit/*" - m.Get("/forks", context.RepoRef(), repo.Forks) + m.Get("/forks", repo.Forks) m.Get("/commit/{sha:([a-f0-9]{7,64})}.{ext:patch|diff}", repo.MustBeNotEmpty, repo.RawDiff) m.Post("/lastcommit/*", context.RepoRefByType(git.RefTypeCommit), repo.LastCommit) }, optSignIn, context.RepoAssignment, reqUnitCodeReader) diff --git a/services/context/repo.go b/services/context/repo.go index 4e91e53e7d..6f5c772f5e 100644 --- a/services/context/repo.go +++ b/services/context/repo.go @@ -669,12 +669,6 @@ func RepoAssignment(ctx *Context) { const headRefName = "HEAD" -func RepoRef() func(*Context) { - // old code does: return RepoRefByType(git.RefTypeBranch) - // in most cases, it is an abuse, so we just disable it completely and fix the abuses one by one (if there is anything wrong) - return nil -} - func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string { refName := "" parts := strings.Split(path, "/") From bcc38eb35fdd2b46fbefea0a52fbba5e934fee3f Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Mon, 7 Apr 2025 01:34:59 +0800 Subject: [PATCH 04/26] Make markdown render match GitHub's behavior (#34129) For #2246 --- modules/markup/html.go | 3 ++- modules/markup/html_test.go | 12 +++++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/modules/markup/html.go b/modules/markup/html.go index 05701eebde..0e074cbcfa 100644 --- a/modules/markup/html.go +++ b/modules/markup/html.go @@ -85,7 +85,8 @@ var globalVars = sync.OnceValue(func() *globalVarsType { // codePreviewPattern matches "http://domain/.../{owner}/{repo}/src/commit/{commit}/{filepath}#L10-L20" v.codePreviewPattern = regexp.MustCompile(`https?://\S+/([^\s/]+)/([^\s/]+)/src/commit/([0-9a-f]{7,64})(/\S+)#(L\d+(-L\d+)?)`) - v.tagCleaner = regexp.MustCompile(`<((?:/?\w+/\w+)|(?:/[\w ]+/)|(/?[hH][tT][mM][lL]\b)|(/?[hH][eE][aA][dD]\b))`) + // cleans: "go-gitea/gitea#12345`) - // Test that other post processing still works. + // Test that other post-processing still works. test( ":gitea:", `:gitea:`) @@ -499,6 +499,12 @@ func TestPostProcess_RenderDocument(t *testing.T) { `Some text with 😄 in the middle`) test("http://localhost:3000/person/repo/issues/4#issuecomment-1234", `person/repo#4 (comment)`) + + // special tags, GitHub's behavior, and for unclosed tags, output as text content as much as possible + test("", `<script>a</script>`) + test("", `<style>a</STYLE>`) } func TestIssue16020(t *testing.T) { From 8c9d2bdee34ba31bbb8c69b45b746c597505fb1b Mon Sep 17 00:00:00 2001 From: Kerwin Bryant Date: Mon, 7 Apr 2025 03:35:08 +0800 Subject: [PATCH 05/26] Keep file tree view icons consistent with icon theme (#33921) Fix #33914 before: ![3000-gogitea-gitea-y4ulxr46c4k ws-us118 gitpod io_test_test gitea_src_branch_main_ gitmodules](https://github.com/user-attachments/assets/ca50eeff-cc44-4041-b01f-c0c5bdd3b6aa) after: ![3000-gogitea-gitea-y4ulxr46c4k ws-us118 gitpod io_test_test gitea_src_branch_main_README md](https://github.com/user-attachments/assets/3b87fdbd-81d0-4831-8a74-4dbfcd5b6d91) --------- Co-authored-by: wxiaoguang --- modules/fileicon/material.go | 22 +++------ modules/fileicon/render.go | 52 ++++++++++++++++++++++ modules/git/error.go | 12 ++--- modules/git/tree_entry.go | 42 ++++++++--------- modules/git/tree_entry_mode.go | 20 +++------ modules/templates/util_render.go | 9 ---- routers/web/repo/treelist.go | 6 ++- routers/web/repo/view.go | 12 +++++ routers/web/repo/view_readme.go | 2 +- services/repository/files/tree.go | 38 ++++++++++++---- services/repository/files/tree_test.go | 19 ++++++-- templates/repo/view_list.tmpl | 3 +- web_src/js/components/ViewFileTree.vue | 10 +++++ web_src/js/components/ViewFileTreeItem.vue | 9 +++- 14 files changed, 170 insertions(+), 86 deletions(-) create mode 100644 modules/fileicon/render.go diff --git a/modules/fileicon/material.go b/modules/fileicon/material.go index cbdb962ee3..557f7ca9e4 100644 --- a/modules/fileicon/material.go +++ b/modules/fileicon/material.go @@ -13,7 +13,6 @@ import ( "code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/options" - "code.gitea.io/gitea/modules/reqctx" "code.gitea.io/gitea/modules/svg" ) @@ -62,13 +61,7 @@ func (m *MaterialIconProvider) loadData() { log.Debug("Loaded material icon rules and SVG images") } -func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name, svg, extraClass string) template.HTML { - data := ctx.GetData() - renderedSVGs, _ := data["_RenderedSVGs"].(map[string]bool) - if renderedSVGs == nil { - renderedSVGs = make(map[string]bool) - data["_RenderedSVGs"] = renderedSVGs - } +func (m *MaterialIconProvider) renderFileIconSVG(p *RenderedIconPool, name, svg, extraClass string) template.HTML { // This part is a bit hacky, but it works really well. It should be safe to do so because all SVG icons are generated by us. // Will try to refactor this in the future. if !strings.HasPrefix(svg, "
{{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}}
{{ctx.Locale.Tr "repo.settings.convert_fork"}}
@@ -916,7 +916,7 @@
{{end}} - {{if and .Repository.IsFork .Repository.Owner.CanCreateRepo}} + {{if .CanConvertFork}}