From 79d0129c69a877ee9d4690cb81726d35c0242b94 Mon Sep 17 00:00:00 2001 From: Andrew Thornton Date: Thu, 4 Nov 2021 22:45:54 +0000 Subject: [PATCH 01/11] Multiple Escaping Improvements There are multiple places where Gitea does not properly escape URLs that it is building and there are multiple places where it builds urls when there is already a simpler function available to use this. This is an extensive PR attempting to fix these issues. Signed-off-by: Andrew Thornton --- cmd/web.go | 1 + integrations/issue_test.go | 4 +- integrations/links_test.go | 2 +- integrations/nonascii_branches_test.go | 32 ++++----- models/action.go | 7 +- models/attachment.go | 3 +- models/avatars/avatar.go | 8 +-- models/commit_status.go | 4 +- models/issue.go | 11 +++ models/notification.go | 3 +- models/release.go | 11 ++- models/repo.go | 21 +++--- models/repo_avatar.go | 3 +- models/user.go | 7 +- modules/context/context.go | 1 + modules/context/repo.go | 39 +++++----- modules/convert/git_commit.go | 11 +-- modules/convert/issue.go | 3 +- modules/convert/notification.go | 4 +- modules/git/utils.go | 4 +- modules/repofiles/action.go | 3 +- modules/repofiles/blob.go | 4 +- modules/repofiles/file.go | 8 +-- modules/repofiles/tree.go | 3 +- modules/repository/commits.go | 3 +- modules/templates/helper.go | 2 +- modules/upload/upload.go | 3 +- routers/api/v1/org/member.go | 3 +- routers/api/v1/repo/git_ref.go | 6 +- routers/api/v1/repo/key.go | 11 +-- routers/web/admin/auths.go | 6 +- routers/web/admin/repos.go | 4 +- routers/web/admin/users.go | 10 +-- routers/web/org/setting.go | 3 +- routers/web/org/teams.go | 19 ++--- routers/web/repo/blame.go | 10 +-- routers/web/repo/commit.go | 4 +- routers/web/repo/compare.go | 33 +++++---- routers/web/repo/editor.go | 12 ++-- routers/web/repo/issue.go | 25 ++++--- routers/web/repo/issue_stopwatch.go | 2 + routers/web/repo/lfs.go | 3 +- routers/web/repo/migrate.go | 3 +- routers/web/repo/milestone.go | 3 +- routers/web/repo/projects.go | 3 +- routers/web/repo/pull.go | 71 +++++++++---------- routers/web/repo/release.go | 3 +- routers/web/repo/repo.go | 4 +- routers/web/repo/setting.go | 16 ++--- routers/web/repo/setting_protected_branch.go | 7 +- routers/web/repo/tag.go | 2 +- routers/web/repo/view.go | 16 ++--- routers/web/repo/webhook.go | 11 +-- routers/web/user/home.go | 2 +- routers/web/user/notification.go | 5 +- routers/web/user/oauth.go | 2 +- routers/web/user/profile.go | 2 +- services/lfs/server.go | 7 +- services/webhook/dingtalk.go | 7 +- services/webhook/discord.go | 7 +- services/webhook/general.go | 14 ++-- services/webhook/matrix.go | 12 ++-- services/webhook/msteams.go | 7 +- templates/admin/emails/list.tmpl | 2 +- templates/admin/repo/list.tmpl | 4 +- templates/admin/user/list.tmpl | 2 +- templates/base/head.tmpl | 4 +- templates/base/head_navbar.tmpl | 13 ++-- templates/explore/code.tmpl | 6 +- templates/mail/auth/activate.tmpl | 2 +- templates/mail/auth/activate_email.tmpl | 2 +- templates/mail/auth/register_notify.tmpl | 2 +- templates/mail/auth/reset_passwd.tmpl | 2 +- templates/mail/issue/assigned.tmpl | 4 +- templates/mail/issue/default.tmpl | 10 +-- templates/mail/notify/repo_transfer.tmpl | 2 +- templates/mail/release.tmpl | 10 ++- templates/org/home.tmpl | 6 +- templates/org/team/members.tmpl | 4 +- templates/org/team/navbar.tmpl | 4 +- templates/org/team/new.tmpl | 4 +- templates/org/team/repositories.tmpl | 12 ++-- templates/org/team/sidebar.tmpl | 6 +- templates/org/team/teams.tmpl | 6 +- templates/repo/activity.tmpl | 2 +- templates/repo/blame.tmpl | 8 +-- templates/repo/branch/list.tmpl | 28 ++++---- templates/repo/branch_dropdown.tmpl | 8 +-- templates/repo/commit_page.tmpl | 6 +- templates/repo/commits_list.tmpl | 8 +-- templates/repo/commits_list_small.tmpl | 6 +- templates/repo/commits_table.tmpl | 6 +- templates/repo/diff/blob_excerpt.tmpl | 12 ++-- templates/repo/diff/box.tmpl | 4 +- templates/repo/diff/comments.tmpl | 6 +- templates/repo/diff/compare.tmpl | 38 +++++----- templates/repo/diff/options_dropdown.tmpl | 8 +-- templates/repo/diff/section_split.tmpl | 6 +- templates/repo/diff/section_unified.tmpl | 6 +- templates/repo/editor/commit_form.tmpl | 2 +- templates/repo/editor/edit.tmpl | 10 +-- templates/repo/editor/upload.tmpl | 6 +- templates/repo/forks.tmpl | 4 +- templates/repo/graph/commits.tmpl | 4 +- templates/repo/header.tmpl | 8 +-- templates/repo/home.tmpl | 16 ++--- templates/repo/issue/labels/label.tmpl | 2 +- templates/repo/issue/list.tmpl | 4 +- templates/repo/issue/view.tmpl | 2 +- templates/repo/issue/view_content.tmpl | 8 +-- .../repo/issue/view_content/comments.tmpl | 26 +++---- .../repo/issue/view_content/context_menu.tmpl | 8 +-- templates/repo/issue/view_content/pull.tmpl | 10 +-- .../repo/issue/view_content/sidebar.tmpl | 26 +++---- templates/repo/issue/view_title.tmpl | 4 +- templates/repo/projects/view.tmpl | 6 +- templates/repo/pulls/commits.tmpl | 2 +- templates/repo/pulls/files.tmpl | 2 +- templates/repo/pulls/fork.tmpl | 4 +- templates/repo/pulls/tab_menu.tmpl | 6 +- templates/repo/release/list.tmpl | 34 ++++----- templates/repo/search.tmpl | 8 +-- templates/repo/settings/branches.tmpl | 4 +- templates/repo/settings/collaboration.tmpl | 4 +- templates/repo/settings/githooks.tmpl | 2 +- templates/repo/settings/lfs_file.tmpl | 10 +-- templates/repo/settings/lfs_file_find.tmpl | 2 +- templates/repo/settings/lfs_locks.tmpl | 4 +- templates/repo/settings/tags.tmpl | 2 +- templates/repo/sub_menu.tmpl | 2 +- templates/repo/view_file.tmpl | 28 ++++---- templates/repo/view_list.tmpl | 18 ++--- templates/shared/issuelist.tmpl | 8 +-- templates/user/auth/grant.tmpl | 4 +- templates/user/dashboard/feeds.tmpl | 56 +++++++-------- templates/user/dashboard/issues.tmpl | 18 ++--- templates/user/dashboard/milestones.tmpl | 24 +++---- templates/user/dashboard/navbar.tmpl | 8 +-- templates/user/dashboard/repolist.tmpl | 4 +- .../user/notification/notification_div.tmpl | 4 +- templates/user/settings/repos.tmpl | 8 +-- web_src/js/features/repo-branch.js | 2 +- 142 files changed, 642 insertions(+), 591 deletions(-) diff --git a/cmd/web.go b/cmd/web.go index 8d9387e06f7ab..0e599268dbabb 100644 --- a/cmd/web.go +++ b/cmd/web.go @@ -68,6 +68,7 @@ func runHTTPRedirector() { if len(r.URL.RawQuery) > 0 { target += "?" + r.URL.RawQuery } + // FIXME: Why aren't we adding the Fragment? http.Redirect(w, r, target, http.StatusTemporaryRedirect) }) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 132f0822abc2d..1d7dbdabd77b2 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -65,7 +65,7 @@ func TestViewIssuesSortByType(t *testing.T) { repo := db.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) session := loginUser(t, user.Name) - req := NewRequest(t, "GET", repo.RelLink()+"/issues?type=created_by") + req := NewRequest(t, "GET", repo.Link()+"/issues?type=created_by") resp := session.MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) @@ -97,7 +97,7 @@ func TestViewIssuesKeyword(t *testing.T) { issues.UpdateIssueIndexer(issue) time.Sleep(time.Second * 1) const keyword = "first" - req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.RelLink(), keyword) + req := NewRequestf(t, "GET", "%s/issues?q=%s", repo.Link(), keyword) resp := MakeRequest(t, req, http.StatusOK) htmlDoc := NewHTMLParser(t, resp.Body) diff --git a/integrations/links_test.go b/integrations/links_test.go index 03229e10e1222..91166274a2381 100644 --- a/integrations/links_test.go +++ b/integrations/links_test.go @@ -156,7 +156,7 @@ func testLinksAsUser(userName string, t *testing.T) { "/releases", "/releases/new", //"/wiki/_pages", - "/wiki/_new", + "/wiki/?action=_new", } for _, repo := range apiRepos { diff --git a/integrations/nonascii_branches_test.go b/integrations/nonascii_branches_test.go index 22d71e6ee20e1..cf6261dffe054 100644 --- a/integrations/nonascii_branches_test.go +++ b/integrations/nonascii_branches_test.go @@ -63,17 +63,17 @@ func TestNonasciiBranches(t *testing.T) { }, { from: "ГлавнаяВетка", - to: "branch/%d0%93%d0%bb%d0%b0%d0%b2%d0%bd%d0%b0%d1%8f%d0%92%d0%b5%d1%82%d0%ba%d0%b0", + to: "branch/%D0%93%D0%BB%D0%B0%D0%B2%D0%BD%D0%B0%D1%8F%D0%92%D0%B5%D1%82%D0%BA%D0%B0", status: http.StatusOK, }, { from: "а/б/в", - to: "branch/%d0%b0/%d0%b1/%d0%b2", + to: "branch/%D0%B0/%D0%B1/%D0%B2", status: http.StatusOK, }, { from: "Grüßen/README.md", - to: "branch/Gr%c3%bc%c3%9fen/README.md", + to: "branch/Gr%C3%BC%C3%9Fen/README.md", status: http.StatusOK, }, { @@ -83,7 +83,7 @@ func TestNonasciiBranches(t *testing.T) { }, { from: "Plus+Is+Not+Space/Файл.md", - to: "branch/Plus+Is+Not+Space/%d0%a4%d0%b0%d0%b9%d0%bb.md", + to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", status: http.StatusOK, }, { @@ -93,28 +93,28 @@ func TestNonasciiBranches(t *testing.T) { }, { from: "ブランチ", - to: "branch/%e3%83%96%e3%83%a9%e3%83%b3%e3%83%81", + to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", status: http.StatusOK, }, // Tags { from: "Тэг", - to: "tag/%d0%a2%d1%8d%d0%b3", + to: "tag/%D0%A2%D1%8D%D0%B3", status: http.StatusOK, }, { from: "Ё/人", - to: "tag/%d0%81/%e4%ba%ba", + to: "tag/%D0%81/%E4%BA%BA", status: http.StatusOK, }, { from: "タグ", - to: "tag/%e3%82%bf%e3%82%b0", + to: "tag/%E3%82%BF%E3%82%B0", status: http.StatusOK, }, { from: "タグ/ファイル.md", - to: "tag/%e3%82%bf%e3%82%b0/%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab.md", + to: "tag/%E3%82%BF%E3%82%B0/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md", status: http.StatusOK, }, // Files @@ -125,38 +125,38 @@ func TestNonasciiBranches(t *testing.T) { }, { from: "Файл.md", - to: "branch/Plus+Is+Not+Space/%d0%a4%d0%b0%d0%b9%d0%bb.md", + to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", status: http.StatusOK, }, { from: "ファイル.md", - to: "branch/Plus+Is+Not+Space/%e3%83%95%e3%82%a1%e3%82%a4%e3%83%ab.md", + to: "branch/Plus+Is+Not+Space/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB.md", status: http.StatusNotFound, // it's not on default branch }, // Same but url-encoded (few tests) { from: "%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", - to: "branch/%e3%83%96%e3%83%a9%e3%83%b3%e3%83%81", + to: "branch/%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", status: http.StatusOK, }, { from: "%E3%82%BF%E3%82%b0", - to: "tag/%e3%82%bf%e3%82%b0", + to: "tag/%E3%82%BF%E3%82%B0", status: http.StatusOK, }, { from: "%D0%A4%D0%B0%D0%B9%D0%BB.md", - to: "branch/Plus+Is+Not+Space/%d0%a4%d0%b0%d0%b9%d0%bb.md", + to: "branch/Plus+Is+Not+Space/%D0%A4%D0%B0%D0%B9%D0%BB.md", status: http.StatusOK, }, { from: "%D0%81%2F%E4%BA%BA", - to: "tag/%d0%81/%e4%ba%ba", + to: "tag/%D0%81/%E4%BA%BA", status: http.StatusOK, }, { from: "Ё%2F%E4%BA%BA", - to: "tag/%d0%81/%e4%ba%ba", + to: "tag/%D0%81/%E4%BA%BA", status: http.StatusOK, }, } diff --git a/models/action.go b/models/action.go index 7c970e1fdb6f5..80ac3e16f8524 100644 --- a/models/action.go +++ b/models/action.go @@ -7,6 +7,7 @@ package models import ( "fmt" + "net/url" "path" "strconv" "strings" @@ -185,10 +186,8 @@ func (a *Action) ShortRepoPath() string { // GetRepoLink returns relative link to action repository. func (a *Action) GetRepoLink() string { - if len(setting.AppSubURL) > 0 { - return path.Join(setting.AppSubURL, a.GetRepoPath()) - } - return "/" + a.GetRepoPath() + // path.Join will skip empty strings + return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName())) } // GetRepositoryFromMatch returns a *Repository from a username and repo strings diff --git a/models/attachment.go b/models/attachment.go index f06b389dc697c..6dffab4269917 100644 --- a/models/attachment.go +++ b/models/attachment.go @@ -7,6 +7,7 @@ package models import ( "context" "fmt" + "net/url" "path" "code.gitea.io/gitea/models/db" @@ -58,7 +59,7 @@ func (a *Attachment) RelativePath() string { // DownloadURL returns the download url of the attached file func (a *Attachment) DownloadURL() string { - return fmt.Sprintf("%sattachments/%s", setting.AppURL, a.UUID) + return setting.AppURL + "attachments/" + url.PathEscape(a.UUID) } // LinkedRepository returns the linked repo if any diff --git a/models/avatars/avatar.go b/models/avatars/avatar.go index 0a1445d2f2d09..da63dfd106aa0 100644 --- a/models/avatars/avatar.go +++ b/models/avatars/avatar.go @@ -112,15 +112,15 @@ func GenerateUserAvatarFastLink(userName string, size int) string { if size < 0 { size = 0 } - return setting.AppSubURL + "/user/avatar/" + userName + "/" + strconv.Itoa(size) + return setting.AppSubURL + "/user/avatar/" + url.PathEscape(userName) + "/" + strconv.Itoa(size) } // GenerateUserAvatarImageLink returns a link for `User.Avatar` image file: "/avatars/${User.Avatar}" func GenerateUserAvatarImageLink(userAvatar string, size int) string { if size > 0 { - return setting.AppSubURL + "/avatars/" + userAvatar + "?size=" + strconv.Itoa(size) + return setting.AppSubURL + "/avatars/" + url.PathEscape(userAvatar) + "?size=" + strconv.Itoa(size) } - return setting.AppSubURL + "/avatars/" + userAvatar + return setting.AppSubURL + "/avatars/" + url.PathEscape(userAvatar) } // generateRecognizedAvatarURL generate a recognized avatar (Gravatar/Libravatar) URL, it modifies the URL so the parameter is passed by a copy @@ -155,7 +155,7 @@ func generateEmailAvatarLink(email string, size int, final bool) string { return generateRecognizedAvatarURL(*avatarURL, size) } // for non-final link, we should return fast (use a 302 redirection link) - urlStr := setting.AppSubURL + "/avatar/" + emailHash + urlStr := setting.AppSubURL + "/avatar/" + url.PathEscape(emailHash) if size > 0 { urlStr += "?size=" + strconv.Itoa(size) } diff --git a/models/commit_status.go b/models/commit_status.go index a6ded049c31cd..df34b93ec53cd 100644 --- a/models/commit_status.go +++ b/models/commit_status.go @@ -7,6 +7,7 @@ package models import ( "crypto/sha1" "fmt" + "net/url" "strings" "time" @@ -137,8 +138,7 @@ func (status *CommitStatus) loadAttributes(e db.Engine) (err error) { // APIURL returns the absolute APIURL to this commit-status. func (status *CommitStatus) APIURL() string { _ = status.loadAttributes(db.GetEngine(db.DefaultContext)) - return fmt.Sprintf("%sapi/v1/repos/%s/statuses/%s", - setting.AppURL, status.Repo.FullName(), status.SHA) + return status.Repo.APIURL() + "/statuses/" + url.PathEscape(status.SHA) } // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc diff --git a/models/issue.go b/models/issue.go index a27bb1cc7dec9..34ab813601929 100644 --- a/models/issue.go +++ b/models/issue.go @@ -372,6 +372,17 @@ func (issue *Issue) HTMLURL() string { return fmt.Sprintf("%s/%s/%d", issue.Repo.HTMLURL(), path, issue.Index) } +// Link returns the Link URL to this issue. +func (issue *Issue) Link() string { + var path string + if issue.IsPull { + path = "pulls" + } else { + path = "issues" + } + return fmt.Sprintf("%s/%s/%d", issue.Repo.Link(), path, issue.Index) +} + // DiffURL returns the absolute URL to this diff func (issue *Issue) DiffURL() string { if issue.IsPull { diff --git a/models/notification.go b/models/notification.go index bcbe8b0988e86..cbac67cba628e 100644 --- a/models/notification.go +++ b/models/notification.go @@ -6,6 +6,7 @@ package models import ( "fmt" + "net/url" "strconv" "code.gitea.io/gitea/models/db" @@ -468,7 +469,7 @@ func (n *Notification) HTMLURL() string { } return n.Issue.HTMLURL() case NotificationSourceCommit: - return n.Repository.HTMLURL() + "/commit/" + n.CommitID + return n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID) case NotificationSourceRepository: return n.Repository.HTMLURL() } diff --git a/models/release.go b/models/release.go index 4624791b8ff41..f7bd67b8ca722 100644 --- a/models/release.go +++ b/models/release.go @@ -10,10 +10,10 @@ import ( "errors" "fmt" "sort" + "strconv" "strings" "code.gitea.io/gitea/models/db" - "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/util" @@ -78,23 +78,22 @@ func (r *Release) LoadAttributes() error { // APIURL the api url for a release. release must have attributes loaded func (r *Release) APIURL() string { - return fmt.Sprintf("%sapi/v1/repos/%s/releases/%d", - setting.AppURL, r.Repo.FullName(), r.ID) + return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10) } // ZipURL the zip url for a release. release must have attributes loaded func (r *Release) ZipURL() string { - return fmt.Sprintf("%s/archive/%s.zip", r.Repo.HTMLURL(), r.TagName) + return r.Repo.HTMLURL() + "/archive/" + util.PathEscapeSegments(r.TagName) + ".zip" } // TarURL the tar.gz url for a release. release must have attributes loaded func (r *Release) TarURL() string { - return fmt.Sprintf("%s/archive/%s.tar.gz", r.Repo.HTMLURL(), r.TagName) + return r.Repo.HTMLURL() + "/archive/" + util.PathEscapeSegments(r.TagName) + ".tar.gz" } // HTMLURL the url for a release on the web UI. release must have attributes loaded func (r *Release) HTMLURL() string { - return fmt.Sprintf("%s/releases/tag/%s", r.Repo.HTMLURL(), r.TagName) + return r.Repo.HTMLURL() + "/releases/tag/" + util.PathEscapeSegments(r.TagName) } // IsReleaseExist returns true if release with given tag name already exists. diff --git a/models/repo.go b/models/repo.go index 25b1e65206f77..bb7bdd86b3381 100644 --- a/models/repo.go +++ b/models/repo.go @@ -312,7 +312,7 @@ func (repo *Repository) FullName() string { // HTMLURL returns the repository HTML URL func (repo *Repository) HTMLURL() string { - return setting.AppURL + repo.FullName() + return setting.AppURL + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) } // CommitLink make link to by commit full ID @@ -321,14 +321,14 @@ func (repo *Repository) CommitLink(commitID string) (result string) { if commitID == "" || commitID == "0000000000000000000000000000000000000000" { result = "" } else { - result = repo.HTMLURL() + "/commit/" + commitID + result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID) } return } // APIURL returns the repository API URL func (repo *Repository) APIURL() string { - return setting.AppURL + "api/v1/repos/" + repo.FullName() + return setting.AppURL + "api/v1/repos/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) } // GetCommitsCountCacheKey returns cache key used for commits count caching. @@ -707,19 +707,14 @@ func (repo *Repository) GitConfigPath() string { return GitConfigPath(repo.RepoPath()) } -// RelLink returns the repository relative link -func (repo *Repository) RelLink() string { - return "/" + repo.FullName() -} - // Link returns the repository link func (repo *Repository) Link() string { - return setting.AppSubURL + "/" + repo.FullName() + return setting.AppSubURL + "/" + url.PathEscape(repo.OwnerName) + "/" + url.PathEscape(repo.Name) } // ComposeCompareURL returns the repository comparison URL func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { - return fmt.Sprintf("%s/compare/%s...%s", repo.FullName(), oldCommitID, newCommitID) + return fmt.Sprintf("%s/%s/compare/%s...%s", url.PathEscape(repo.OwnerName), url.PathEscape(repo.Name), util.PathEscapeSegments(oldCommitID), util.PathEscapeSegments(newCommitID)) } // UpdateDefaultBranch updates the default branch @@ -928,11 +923,11 @@ func (repo *Repository) cloneLink(isWiki bool) *CloneLink { } if setting.SSH.Port != 22 { - cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)), repo.OwnerName, repoName) + cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, net.JoinHostPort(setting.SSH.Domain, strconv.Itoa(setting.SSH.Port)), url.PathEscape(repo.OwnerName), url.PathEscape(repoName)) } else if setting.Repository.UseCompatSSHURI { - cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, repo.OwnerName, repoName) + cl.SSH = fmt.Sprintf("ssh://%s@%s/%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName)) } else { - cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, repo.OwnerName, repoName) + cl.SSH = fmt.Sprintf("%s@%s:%s/%s.git", sshUser, sshDomain, url.PathEscape(repo.OwnerName), url.PathEscape(repoName)) } cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName) return cl diff --git a/models/repo_avatar.go b/models/repo_avatar.go index bb5f083dd5e2c..2966092cc9da9 100644 --- a/models/repo_avatar.go +++ b/models/repo_avatar.go @@ -10,6 +10,7 @@ import ( "fmt" "image/png" "io" + "net/url" "strconv" "strings" @@ -96,7 +97,7 @@ func (repo *Repository) relAvatarLink(e db.Engine) string { return "" } } - return setting.AppSubURL + "/repo-avatars/" + repo.Avatar + return setting.AppSubURL + "/repo-avatars/" + url.PathEscape(repo.Avatar) } // AvatarLink returns a link to the repository's avatar. diff --git a/models/user.go b/models/user.go index 3ce23ef2ed534..9b443955339fe 100644 --- a/models/user.go +++ b/models/user.go @@ -13,6 +13,7 @@ import ( "errors" "fmt" _ "image/jpeg" // Needed for jpeg support + "net/url" "os" "path/filepath" "regexp" @@ -309,17 +310,17 @@ func (u *User) DashboardLink() string { // HomeLink returns the user or organization home page link. func (u *User) HomeLink() string { - return setting.AppSubURL + "/" + u.Name + return setting.AppSubURL + "/" + url.PathEscape(u.Name) } // HTMLURL returns the user or organization's full link. func (u *User) HTMLURL() string { - return setting.AppURL + u.Name + return setting.AppURL + url.PathEscape(u.Name) } // OrganisationLink returns the organization sub page link. func (u *User) OrganisationLink() string { - return setting.AppSubURL + "/org/" + u.Name + return setting.AppSubURL + "/org/" + url.PathEscape(u.Name) } // GenerateEmailActivateCode generates an activate code based on user information and given e-mail. diff --git a/modules/context/context.go b/modules/context/context.go index f652d3845aede..fcf3046da0eb2 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -127,6 +127,7 @@ func RedirectToUser(ctx *Context, userName string, redirectUserID int64) { if ctx.Req.URL.RawQuery != "" { redirectPath += "?" + ctx.Req.URL.RawQuery } + // FIXME: Why not Fragment ctx.Redirect(path.Join(setting.AppSubURL, redirectPath)) } diff --git a/modules/context/repo.go b/modules/context/repo.go index 88f4d2765825f..6bc5be1a429ef 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -40,10 +40,10 @@ var IssueTemplateDirCandidates = []string{ // PullRequest contains information to make a pull request type PullRequest struct { - BaseRepo *models.Repository - Allowed bool - SameRepo bool - HeadInfo string // [:] + BaseRepo *models.Repository + Allowed bool + SameRepo bool + HeadInfoSubURL string // [:] url segment } // Repository contains information to operate a repository @@ -188,11 +188,11 @@ func (r *Repository) GetCommitGraphsCount(hidePRRefs bool, branches []string, fi func (r *Repository) BranchNameSubURL() string { switch { case r.IsViewBranch: - return "branch/" + r.BranchName + return "branch/" + util.PathEscapeSegments(r.BranchName) case r.IsViewTag: - return "tag/" + r.BranchName + return "tag/" + util.PathEscapeSegments(r.BranchName) case r.IsViewCommit: - return "commit/" + r.BranchName + return "commit/" + util.PathEscapeSegments(r.BranchName) } log.Error("Unknown view type for repo: %v", r) return "" @@ -320,14 +320,15 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) { } redirectPath := strings.Replace( - ctx.Req.URL.Path, - fmt.Sprintf("%s/%s", ownerName, previousRepoName), - repo.FullName(), + ctx.Req.URL.EscapedPath(), + url.PathEscape(ownerName)+"/"+url.PathEscape(previousRepoName), + url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name), 1, ) if ctx.Req.URL.RawQuery != "" { redirectPath += "?" + ctx.Req.URL.RawQuery } + // FIXME: why not Fragment ctx.Redirect(path.Join(setting.AppSubURL, redirectPath)) } @@ -587,7 +588,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { ctx.Data["BaseRepo"] = repo.BaseRepo ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo ctx.Repo.PullRequest.Allowed = canPush - ctx.Repo.PullRequest.HeadInfo = ctx.Repo.Owner.Name + ":" + ctx.Repo.BranchName + ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.Repo.Owner.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName) } else if repo.AllowsPulls() { // Or, this is repository accepts pull requests between branches. canCompare = true @@ -595,7 +596,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { ctx.Repo.PullRequest.BaseRepo = repo ctx.Repo.PullRequest.Allowed = canPush ctx.Repo.PullRequest.SameRepo = true - ctx.Repo.PullRequest.HeadInfo = ctx.Repo.BranchName + ctx.Repo.PullRequest.HeadInfoSubURL = util.PathEscapeSegments(ctx.Repo.BranchName) } ctx.Data["CanCompareOrPull"] = canCompare ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest @@ -620,7 +621,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) { if ctx.FormString("go-get") == "1" { ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name) - prefix := setting.AppURL + path.Join(owner.Name, repo.Name, "src", "branch", ctx.Repo.BranchName) + prefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName) ctx.Data["GoDocDirectory"] = prefix + "{/dir}" ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" } @@ -809,7 +810,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if isRenamedBranch && has { renamedBranchName := ctx.Data["RenamedBranchName"].(string) ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName)) - link := strings.Replace(ctx.Req.RequestURI, refName, renamedBranchName, 1) + link := setting.AppSubURL + strings.Replace(ctx.Req.URL.EscapedPath(), util.PathEscapeSegments(refName), util.PathEscapeSegments(renamedBranchName), 1) ctx.Redirect(link) return } @@ -844,7 +845,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context // If short commit ID add canonical link header if len(refName) < 40 { ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", - util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), refName, ctx.Repo.Commit.ID.String(), 1)))) + util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1)))) } } else { if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] { @@ -856,11 +857,13 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context if refType == RepoRefLegacy { // redirect from old URL scheme to new URL scheme + prefix := strings.TrimPrefix(setting.AppSubURL+strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), ctx.Repo.RepoLink) + ctx.Redirect(path.Join( - setting.AppSubURL, - strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), + ctx.Repo.RepoLink, + util.PathEscapeSegments(prefix), ctx.Repo.BranchNameSubURL(), - ctx.Repo.TreePath)) + util.PathEscapeSegments(ctx.Repo.TreePath))) return } } diff --git a/modules/convert/git_commit.go b/modules/convert/git_commit.go index fd4f12ecfa6d5..ac292e358d5bf 100644 --- a/modules/convert/git_commit.go +++ b/modules/convert/git_commit.go @@ -5,6 +5,7 @@ package convert import ( + "net/url" "time" "code.gitea.io/gitea/models" @@ -126,7 +127,7 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string] for i := 0; i < commit.ParentCount(); i++ { sha, _ := commit.ParentID(i) apiParents[i] = &api.CommitMeta{ - URL: repo.APIURL() + "/git/commits/" + sha.String(), + URL: repo.APIURL() + "/git/commits/" + url.PathEscape(sha.String()), SHA: sha.String(), } } @@ -147,12 +148,12 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string] return &api.Commit{ CommitMeta: &api.CommitMeta{ - URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), + URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()), SHA: commit.ID.String(), }, - HTMLURL: repo.HTMLURL() + "/commit/" + commit.ID.String(), + HTMLURL: repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()), RepoCommit: &api.RepoCommit{ - URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), + URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()), Author: &api.CommitUser{ Identity: api.Identity{ Name: commit.Author.Name, @@ -169,7 +170,7 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string] }, Message: commit.Message(), Tree: &api.CommitMeta{ - URL: repo.APIURL() + "/git/trees/" + commit.ID.String(), + URL: repo.APIURL() + "/git/trees/" + url.PathEscape(commit.ID.String()), SHA: commit.ID.String(), }, }, diff --git a/modules/convert/issue.go b/modules/convert/issue.go index 3974d460e0e5d..7363cfb8fb892 100644 --- a/modules/convert/issue.go +++ b/modules/convert/issue.go @@ -6,6 +6,7 @@ package convert import ( "fmt" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -191,7 +192,7 @@ func ToLabel(label *models.Label, repo *models.Repository, org *models.User) *ap } } else { // BelongsToOrg if org != nil { - result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, org.Name, label.ID) + result.URL = fmt.Sprintf("%sapi/v1/orgs/%s/labels/%d", setting.AppURL, url.PathEscape(org.Name), label.ID) } else { log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID) } diff --git a/modules/convert/notification.go b/modules/convert/notification.go index fae7be1257d2d..5f4fef02b9e84 100644 --- a/modules/convert/notification.go +++ b/modules/convert/notification.go @@ -5,6 +5,8 @@ package convert import ( + "net/url" + "code.gitea.io/gitea/models" api "code.gitea.io/gitea/modules/structs" ) @@ -58,7 +60,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread { } } case models.NotificationSourceCommit: - url := n.Repository.HTMLURL() + "/commit/" + n.CommitID + url := n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID) result.Subject = &api.NotificationSubject{ Type: api.NotifySubjectCommit, Title: n.CommitID, diff --git a/modules/git/utils.go b/modules/git/utils.go index 13926fba72df3..6988f31a367fc 100644 --- a/modules/git/utils.go +++ b/modules/git/utils.go @@ -11,6 +11,8 @@ import ( "strconv" "strings" "sync" + + "code.gitea.io/gitea/modules/util" ) // ObjectCache provides thread-safe cache operations. @@ -92,7 +94,7 @@ func RefEndName(refStr string) string { // RefURL returns the absolute URL for a ref in a repository func RefURL(repoURL, ref string) string { - refName := RefEndName(ref) + refName := util.PathEscapeSegments(RefEndName(ref)) switch { case strings.HasPrefix(ref, BranchPrefix): return repoURL + "/src/branch/" + refName diff --git a/modules/repofiles/action.go b/modules/repofiles/action.go index d7e3ff452588c..0bcdb8c3a1ac9 100644 --- a/modules/repofiles/action.go +++ b/modules/repofiles/action.go @@ -7,6 +7,7 @@ package repofiles import ( "fmt" "html" + "net/url" "regexp" "strconv" "strings" @@ -175,7 +176,7 @@ func UpdateIssuesCommit(doer *models.User, repo *models.Repository, commits []*r continue } - message := fmt.Sprintf(`%s`, repo.Link(), c.Sha1, html.EscapeString(strings.SplitN(c.Message, "\n", 2)[0])) + message := fmt.Sprintf(`%s`, html.EscapeString(repo.Link()), html.EscapeString(url.PathEscape(c.Sha1)), html.EscapeString(strings.SplitN(c.Message, "\n", 2)[0])) if err = models.CreateRefComment(doer, refRepo, refIssue, message, c.Sha1); err != nil { return err } diff --git a/modules/repofiles/blob.go b/modules/repofiles/blob.go index 60a05e280e864..02bc1ebcab93a 100644 --- a/modules/repofiles/blob.go +++ b/modules/repofiles/blob.go @@ -5,6 +5,8 @@ package repofiles import ( + "net/url" + "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/setting" @@ -31,7 +33,7 @@ func GetBlobBySHA(repo *models.Repository, sha string) (*api.GitBlobResponse, er } return &api.GitBlobResponse{ SHA: gitBlob.ID.String(), - URL: repo.APIURL() + "/git/blobs/" + gitBlob.ID.String(), + URL: repo.APIURL() + "/git/blobs/" + url.PathEscape(gitBlob.ID.String()), Size: gitBlob.Size(), Encoding: "base64", Content: content, diff --git a/modules/repofiles/file.go b/modules/repofiles/file.go index abd14b1db817f..403092401768a 100644 --- a/modules/repofiles/file.go +++ b/modules/repofiles/file.go @@ -36,19 +36,19 @@ func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.Fi if commit == nil { return nil, fmt.Errorf("commit cannot be nil") } - commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + commit.ID.String()) - commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + commit.Tree.ID.String()) + commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String())) + commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String())) parents := make([]*api.CommitMeta, commit.ParentCount()) for i := 0; i <= commit.ParentCount(); i++ { if parent, err := commit.Parent(i); err == nil && parent != nil { - parentCommitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + parent.ID.String()) + parentCommitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(parent.ID.String())) parents[i] = &api.CommitMeta{ SHA: parent.ID.String(), URL: parentCommitURL.String(), } } } - commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + commit.ID.String()) + commitHTMLURL, _ := url.Parse(repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String())) fileCommit := &api.FileCommitResponse{ CommitMeta: api.CommitMeta{ SHA: commit.ID.String(), diff --git a/modules/repofiles/tree.go b/modules/repofiles/tree.go index b3edea341f14c..81579dccc53a7 100644 --- a/modules/repofiles/tree.go +++ b/modules/repofiles/tree.go @@ -6,6 +6,7 @@ package repofiles import ( "fmt" + "net/url" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/git" @@ -28,7 +29,7 @@ func GetTreeBySHA(repo *models.Repository, sha string, page, perPage int, recurs } tree := new(api.GitTreeResponse) tree.SHA = gitTree.ResolvedID.String() - tree.URL = repo.APIURL() + "/git/trees/" + tree.SHA + tree.URL = repo.APIURL() + "/git/trees/" + url.PathEscape(tree.SHA) var entries git.Entries if recursive { entries, err = gitTree.ListEntriesRecursive() diff --git a/modules/repository/commits.go b/modules/repository/commits.go index c86f0d570be4f..a545ce952ba77 100644 --- a/modules/repository/commits.go +++ b/modules/repository/commits.go @@ -6,6 +6,7 @@ package repository import ( "fmt" + "net/url" "time" "code.gitea.io/gitea/models" @@ -81,7 +82,7 @@ func (pc *PushCommits) toAPIPayloadCommit(repoPath, repoLink string, commit *Pus return &api.PayloadCommit{ ID: commit.Sha1, Message: commit.Message, - URL: fmt.Sprintf("%s/commit/%s", repoLink, commit.Sha1), + URL: fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(commit.Sha1)), Author: &api.PayloadUser{ Name: commit.AuthorName, Email: commit.AuthorEmail, diff --git a/modules/templates/helper.go b/modules/templates/helper.go index 991816c10332d..43275a09e80d6 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -742,7 +742,7 @@ func ReactionToEmoji(reaction string) template.HTML { if val != nil { return template.HTML(val.Emoji) } - return template.HTML(fmt.Sprintf(`:%s:`, reaction, setting.StaticURLPrefix, reaction)) + return template.HTML(fmt.Sprintf(`:%s:`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction))) } // RenderNote renders the contents of a git-notes file as a commit message. diff --git a/modules/upload/upload.go b/modules/upload/upload.go index e430a5d8b3c22..097facb4d5fad 100644 --- a/modules/upload/upload.go +++ b/modules/upload/upload.go @@ -6,6 +6,7 @@ package upload import ( "net/http" + "net/url" "path" "regexp" "strings" @@ -83,7 +84,7 @@ func AddUploadContext(ctx *context.Context, uploadType string) { ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove" if len(ctx.Params(":index")) > 0 { - ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + ctx.Params(":index") + "/attachments" + ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/" + url.PathEscape(ctx.Params(":index")) + "/attachments" } else { ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments" } diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index a6f140c38f65e..4530349f2c20c 100644 --- a/routers/api/v1/org/member.go +++ b/routers/api/v1/org/member.go @@ -6,6 +6,7 @@ package org import ( "net/http" + "net/url" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -159,7 +160,7 @@ func IsMember(ctx *context.APIContext) { } } - redirectURL := setting.AppSubURL + "/api/v1/orgs/" + ctx.Org.Organization.Name + "/public_members/" + userToCheck.Name + redirectURL := setting.AppSubURL + "/api/v1/orgs/" + url.PathEscape(ctx.Org.Organization.Name) + "/public_members/" + url.PathEscape(userToCheck.Name) ctx.Redirect(redirectURL, 302) } diff --git a/routers/api/v1/repo/git_ref.go b/routers/api/v1/repo/git_ref.go index e304e06740bea..29b126db9a49a 100644 --- a/routers/api/v1/repo/git_ref.go +++ b/routers/api/v1/repo/git_ref.go @@ -6,9 +6,11 @@ package repo import ( "net/http" + "net/url" "code.gitea.io/gitea/modules/context" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/routers/api/v1/utils" ) @@ -89,11 +91,11 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) { for i := range refs { apiRefs[i] = &api.Reference{ Ref: refs[i].Name, - URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Name, + URL: ctx.Repo.Repository.APIURL() + "/git/" + util.PathEscapeSegments(refs[i].Name), Object: &api.GitObject{ SHA: refs[i].Object.String(), Type: refs[i].Type, - URL: ctx.Repo.Repository.APIURL() + "/git/" + refs[i].Type + "s/" + refs[i].Object.String(), + URL: ctx.Repo.Repository.APIURL() + "/git/" + url.PathEscape(refs[i].Type) + "s/" + url.PathEscape(refs[i].Object.String()), }, } } diff --git a/routers/api/v1/repo/key.go b/routers/api/v1/repo/key.go index 98ee2b4de5cfd..c20a4776cc064 100644 --- a/routers/api/v1/repo/key.go +++ b/routers/api/v1/repo/key.go @@ -8,6 +8,7 @@ package repo import ( "fmt" "net/http" + "net/url" "code.gitea.io/gitea/models" "code.gitea.io/gitea/modules/context" @@ -33,8 +34,8 @@ func appendPrivateInformation(apiKey *api.DeployKey, key *models.DeployKey, repo return apiKey, nil } -func composeDeployKeysAPILink(repoPath string) string { - return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/" +func composeDeployKeysAPILink(owner, name string) string { + return setting.AppURL + "api/v1/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/keys/" } // ListDeployKeys list all the deploy keys of a repository @@ -94,7 +95,7 @@ func ListDeployKeys(ctx *context.APIContext) { return } - apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) + apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) apiKeys := make([]*api.DeployKey, len(keys)) for i := range keys { if err := keys[i].GetContent(); err != nil { @@ -154,7 +155,7 @@ func GetDeployKey(ctx *context.APIContext) { return } - apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) + apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) apiKey := convert.ToDeployKey(apiLink, key) if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) { apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository) @@ -233,7 +234,7 @@ func CreateDeployKey(ctx *context.APIContext) { } key.Content = content - apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name) + apiLink := composeDeployKeysAPILink(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) ctx.JSON(http.StatusCreated, convert.ToDeployKey(apiLink, key)) } diff --git a/routers/web/admin/auths.go b/routers/web/admin/auths.go index 803dcafa28bd9..90cc0d050b9ac 100644 --- a/routers/web/admin/auths.go +++ b/routers/web/admin/auths.go @@ -8,7 +8,9 @@ import ( "errors" "fmt" "net/http" + "net/url" "regexp" + "strconv" "code.gitea.io/gitea/models" "code.gitea.io/gitea/models/login" @@ -397,7 +399,7 @@ func EditAuthSourcePost(ctx *context.Context) { log.Trace("Authentication changed by admin(%s): %d", ctx.User.Name, source.ID) ctx.Flash.Success(ctx.Tr("admin.auths.update_success")) - ctx.Redirect(setting.AppSubURL + "/admin/auths/" + fmt.Sprint(form.ID)) + ctx.Redirect(setting.AppSubURL + "/admin/auths/" + strconv.FormatInt(form.ID, 10)) } // DeleteAuthSource response for deleting an auth source @@ -415,7 +417,7 @@ func DeleteAuthSource(ctx *context.Context) { ctx.Flash.Error(fmt.Sprintf("DeleteLoginSource: %v", err)) } ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/auths/" + ctx.Params(":authid"), + "redirect": setting.AppSubURL + "/admin/auths/" + url.PathEscape(ctx.Params(":authid")), }) return } diff --git a/routers/web/admin/repos.go b/routers/web/admin/repos.go index 2f4d182af8fe4..19f111165ac9f 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -59,7 +59,7 @@ func DeleteRepo(ctx *context.Context) { ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/repos?page=" + ctx.FormString("page") + "&sort=" + ctx.FormString("sort"), + "redirect": setting.AppSubURL + "/admin/repos?page=" + url.QueryEscape(ctx.FormString("page")) + "&sort=" + url.QueryEscape(ctx.FormString("sort")), }) } @@ -162,5 +162,5 @@ func AdoptOrDeleteRepository(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.delete_preexisting_success", dir)) } - ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted?search=true&q=" + url.QueryEscape(q) + "&page=" + page) + ctx.Redirect(setting.AppSubURL + "/admin/repos/unadopted?search=true&q=" + url.QueryEscape(q) + "&page=" + url.QueryEscape(page)) } diff --git a/routers/web/admin/users.go b/routers/web/admin/users.go index 0041f3d07c27c..d70acb2903e9e 100644 --- a/routers/web/admin/users.go +++ b/routers/web/admin/users.go @@ -6,8 +6,8 @@ package admin import ( - "fmt" "net/http" + "net/url" "strconv" "strings" @@ -187,7 +187,7 @@ func NewUserPost(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name)) - ctx.Redirect(setting.AppSubURL + "/admin/users/" + fmt.Sprint(u.ID)) + ctx.Redirect(setting.AppSubURL + "/admin/users/" + strconv.FormatInt(u.ID, 10)) } func prepareUserInfo(ctx *context.Context) *models.User { @@ -365,7 +365,7 @@ func EditUserPost(ctx *context.Context) { log.Trace("Account profile updated by admin (%s): %s", ctx.User.Name, u.Name) ctx.Flash.Success(ctx.Tr("admin.users.update_profile_success")) - ctx.Redirect(setting.AppSubURL + "/admin/users/" + ctx.Params(":userid")) + ctx.Redirect(setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid"))) } // DeleteUser response for deleting a user @@ -381,12 +381,12 @@ func DeleteUser(ctx *context.Context) { case models.IsErrUserOwnRepos(err): ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo")) ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"), + "redirect": setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")), }) case models.IsErrUserHasOrgs(err): ctx.Flash.Error(ctx.Tr("admin.users.still_has_org")) ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": setting.AppSubURL + "/admin/users/" + ctx.Params(":userid"), + "redirect": setting.AppSubURL + "/admin/users/" + url.PathEscape(ctx.Params(":userid")), }) default: ctx.ServerError("DeleteUser", err) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index 277ff9d97359b..b6cbd8c49cfcd 100644 --- a/routers/web/org/setting.go +++ b/routers/web/org/setting.go @@ -7,6 +7,7 @@ package org import ( "net/http" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -75,7 +76,7 @@ func SettingsPost(ctx *context.Context) { return } // reset ctx.org.OrgLink with new name - ctx.Org.OrgLink = setting.AppSubURL + "/org/" + form.Name + ctx.Org.OrgLink = setting.AppSubURL + "/org/" + url.PathEscape(form.Name) log.Trace("Organization name changed: %s -> %s", org.Name, form.Name) nameChanged = false } diff --git a/routers/web/org/teams.go b/routers/web/org/teams.go index c93fcd062b5a8..a043b502952cc 100644 --- a/routers/web/org/teams.go +++ b/routers/web/org/teams.go @@ -7,6 +7,7 @@ package org import ( "net/http" + "net/url" "path" "strings" @@ -104,7 +105,7 @@ func TeamsAction(ctx *context.Context) { } ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName, + "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName), }) return case "add": @@ -118,7 +119,7 @@ func TeamsAction(ctx *context.Context) { if err != nil { if models.IsErrUserNotExist(err) { ctx.Flash.Error(ctx.Tr("form.user_not_exist")) - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName) + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName)) } else { ctx.ServerError(" GetUserByName", err) } @@ -127,7 +128,7 @@ func TeamsAction(ctx *context.Context) { if u.IsOrganization() { ctx.Flash.Error(ctx.Tr("form.cannot_add_org_to_team")) - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName) + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName)) return } @@ -155,7 +156,7 @@ func TeamsAction(ctx *context.Context) { switch page { case "team": - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName) + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName)) case "home": ctx.Redirect(ctx.Org.Organization.HomeLink()) default: @@ -180,7 +181,7 @@ func TeamsRepoAction(ctx *context.Context) { if err != nil { if models.IsErrRepoNotExist(err) { ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo")) - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName + "/repositories") + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories") return } ctx.ServerError("GetRepositoryByName", err) @@ -203,11 +204,11 @@ func TeamsRepoAction(ctx *context.Context) { if action == "addall" || action == "removeall" { ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName + "/repositories", + "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories", }) return } - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName + "/repositories") + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName) + "/repositories") } // NewTeam render create new team page @@ -272,7 +273,7 @@ func NewTeamPost(ctx *context.Context) { return } log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName) + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) } // TeamMembers render team members page @@ -374,7 +375,7 @@ func EditTeamPost(ctx *context.Context) { } return } - ctx.Redirect(ctx.Org.OrgLink + "/teams/" + t.LowerName) + ctx.Redirect(ctx.Org.OrgLink + "/teams/" + url.PathEscape(t.LowerName)) } // DeleteTeam response for the delete team request diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 3632d1846eaeb..110ec037e1dc3 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -8,6 +8,7 @@ import ( "fmt" gotemplate "html/template" "net/http" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -17,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/timeutil" + "code.gitea.io/gitea/modules/util" ) const ( @@ -54,7 +56,7 @@ func RefBlame(ctx *context.Context) { rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() if len(ctx.Repo.TreePath) > 0 { - treeLink += "/" + ctx.Repo.TreePath + treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } var treeNames []string @@ -85,7 +87,7 @@ func RefBlame(ctx *context.Context) { ctx.Data["TreeNames"] = treeNames ctx.Data["BranchLink"] = branchLink - ctx.Data["RawFileLink"] = rawLink + "/" + ctx.Repo.TreePath + ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) ctx.Data["PageIsViewCode"] = true ctx.Data["IsBlame"] = true @@ -236,8 +238,8 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m br.RepoLink = repoLink br.PartSha = part.Sha br.PreviousSha = previousSha - br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, previousSha, ctx.Repo.TreePath) - br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, part.Sha) + br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, url.PathEscape(previousSha), util.PathEscapeSegments(ctx.Repo.TreePath)) + br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha)) br.CommitMessage = commit.CommitMessage br.CommitSince = commitSince } diff --git a/routers/web/repo/commit.go b/routers/web/repo/commit.go index 4c0f94f15db7a..06cce92417a20 100644 --- a/routers/web/repo/commit.go +++ b/routers/web/repo/commit.go @@ -8,7 +8,6 @@ package repo import ( "errors" "net/http" - "path" "strings" "code.gitea.io/gitea/models" @@ -323,8 +322,7 @@ func Diff(ctx *context.Context) { return } } - headTarget := path.Join(userName, repoName) - setCompareContext(ctx, parentCommit, commit, headTarget) + setCompareContext(ctx, parentCommit, commit, userName, repoName) ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Commit"] = commit ctx.Data["Diff"] = diff diff --git a/routers/web/repo/compare.go b/routers/web/repo/compare.go index 555abc1c8bfcb..988f311d6990f 100644 --- a/routers/web/repo/compare.go +++ b/routers/web/repo/compare.go @@ -12,7 +12,7 @@ import ( "html" "io" "net/http" - "path" + "net/url" "path/filepath" "strings" @@ -37,7 +37,7 @@ const ( ) // setCompareContext sets context data. -func setCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit, headTarget string) { +func setCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit, headOwner, headName string) { ctx.Data["BaseCommit"] = base ctx.Data["HeadCommit"] = head @@ -53,22 +53,28 @@ func setCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit, return blob } - setPathsCompareContext(ctx, base, head, headTarget) + setPathsCompareContext(ctx, base, head, headOwner, headName) setImageCompareContext(ctx) setCsvCompareContext(ctx) } -// setPathsCompareContext sets context data for source and raw paths -func setPathsCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit, headTarget string) { - sourcePath := setting.AppSubURL + "/%s/src/commit/%s" - rawPath := setting.AppSubURL + "/%s/raw/commit/%s" +// SourceCommitURL creates a relative URL for a commit in the given repository +func SourceCommitURL(owner, name string, commit *git.Commit) string { + return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/src/commit/" + url.PathEscape(commit.ID.String()) +} - ctx.Data["SourcePath"] = fmt.Sprintf(sourcePath, headTarget, head.ID) - ctx.Data["RawPath"] = fmt.Sprintf(rawPath, headTarget, head.ID) +// RawCommitURL creates a relative URL for the raw commit in the given repository +func RawCommitURL(owner, name string, commit *git.Commit) string { + return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/raw/commit/" + url.PathEscape(commit.ID.String()) +} + +// setPathsCompareContext sets context data for source and raw paths +func setPathsCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit, headOwner, headName string) { + ctx.Data["SourcePath"] = SourceCommitURL(headOwner, headName, head) + ctx.Data["RawPath"] = RawCommitURL(headOwner, headName, head) if base != nil { - baseTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) - ctx.Data["BeforeSourcePath"] = fmt.Sprintf(sourcePath, baseTarget, base.ID) - ctx.Data["BeforeRawPath"] = fmt.Sprintf(rawPath, baseTarget, base.ID) + ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, head) + ctx.Data["BeforeRawPath"] = RawCommitURL(headOwner, headName, head) } } @@ -618,8 +624,7 @@ func PrepareCompareDiff( ctx.Data["Username"] = ci.HeadUser.Name ctx.Data["Reponame"] = ci.HeadRepo.Name - headTarget := path.Join(ci.HeadUser.Name, repo.Name) - setCompareContext(ctx, baseCommit, headCommit, headTarget) + setCompareContext(ctx, baseCommit, headCommit, ci.HeadUser.Name, repo.Name) return false } diff --git a/routers/web/repo/editor.go b/routers/web/repo/editor.go index f272583499539..b65454c20f05c 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -203,7 +203,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ctx.Data["TreePath"] = form.TreePath ctx.Data["TreeNames"] = treeNames ctx.Data["TreePaths"] = treePaths - ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + ctx.Repo.BranchName + ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName) ctx.Data["FileContent"] = form.Content ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_message"] = form.CommitMessage @@ -298,9 +298,9 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b ctx.Error(http.StatusInternalServerError, err.Error()) } } else if models.IsErrCommitIDDoesNotMatch(err) { - ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplEditFile, &form) + ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplEditFile, &form) } else if git.IsErrPushOutOfDate(err) { - ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+util.PathEscapeSegments(form.NewBranchName)), tplEditFile, &form) + ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplEditFile, &form) } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) if len(errPushRej.Message) == 0 { @@ -494,7 +494,7 @@ func DeleteFilePost(ctx *context.Context) { ctx.Error(http.StatusInternalServerError, err.Error()) } } else if models.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) { - ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+form.LastCommit+"..."+ctx.Repo.CommitID), tplDeleteFile, &form) + ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_deleting", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(form.LastCommit)+"..."+util.PathEscapeSegments(ctx.Repo.CommitID)), tplDeleteFile, &form) } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) if len(errPushRej.Message) == 0 { @@ -601,7 +601,7 @@ func UploadFilePost(ctx *context.Context) { ctx.Data["TreePath"] = form.TreePath ctx.Data["TreeNames"] = treeNames ctx.Data["TreePaths"] = treePaths - ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + branchName + ctx.Data["BranchLink"] = ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_message"] = form.CommitMessage ctx.Data["commit_choice"] = form.CommitChoice @@ -697,7 +697,7 @@ func UploadFilePost(ctx *context.Context) { branchErr := err.(models.ErrBranchAlreadyExists) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form) } else if git.IsErrPushOutOfDate(err) { - ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+ctx.Repo.CommitID+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form) + ctx.RenderWithErr(ctx.Tr("repo.editor.file_changed_while_editing", ctx.Repo.RepoLink+"/compare/"+util.PathEscapeSegments(ctx.Repo.CommitID)+"..."+util.PathEscapeSegments(form.NewBranchName)), tplUploadFile, &form) } else if git.IsErrPushRejected(err) { errPushRej := err.(*git.ErrPushRejected) if len(errPushRej.Message) == 0 { diff --git a/routers/web/repo/issue.go b/routers/web/repo/issue.go index 173cb49e45127..e1e70b5444167 100644 --- a/routers/web/repo/issue.go +++ b/routers/web/repo/issue.go @@ -11,6 +11,7 @@ import ( "fmt" "io" "net/http" + "net/url" "path" "strconv" "strings" @@ -105,7 +106,7 @@ func MustAllowPulls(ctx *context.Context) { // User can send pull request if owns a forked repository. if ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) { ctx.Repo.PullRequest.Allowed = true - ctx.Repo.PullRequest.HeadInfo = ctx.User.Name + ":" + ctx.Repo.BranchName + ctx.Repo.PullRequest.HeadInfoSubURL = url.PathEscape(ctx.User.Name) + ":" + util.PathEscapeSegments(ctx.Repo.BranchName) } } @@ -763,7 +764,7 @@ func setTemplateIfExists(ctx *context.Context, ctxDataKey string, possibleDirs [ for _, repoLabel := range repoLabels { if strings.EqualFold(repoLabel.Name, metaLabel) { repoLabel.IsChecked = true - labelIDs = append(labelIDs, fmt.Sprintf("%d", repoLabel.ID)) + labelIDs = append(labelIDs, strconv.FormatInt(repoLabel.ID, 10)) break } } @@ -982,6 +983,7 @@ func NewIssuePost(ctx *context.Context) { issue := &models.Issue{ RepoID: repo.ID, + Repo: repo, Title: form.Title, PosterID: ctx.User.ID, Poster: ctx.User, @@ -1008,9 +1010,9 @@ func NewIssuePost(ctx *context.Context) { log.Trace("Issue created: %d/%d", repo.ID, issue.ID) if ctx.FormString("redirect_after_creation") == "project" { - ctx.Redirect(ctx.Repo.RepoLink + "/projects/" + fmt.Sprint(form.ProjectID)) + ctx.Redirect(ctx.Repo.RepoLink + "/projects/" + strconv.FormatInt(form.ProjectID, 10)) } else { - ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) } } @@ -1088,13 +1090,16 @@ func ViewIssue(ctx *context.Context) { } return } + if issue.Repo == nil { + issue.Repo = ctx.Repo.Repository + } // Make sure type and URL matches. if ctx.Params(":type") == "issues" && issue.IsPull { - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } else if ctx.Params(":type") == "pulls" && !issue.IsPull { - ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } @@ -1487,7 +1492,7 @@ func ViewIssue(ctx *context.Context) { log.Error("IsProtectedBranch: %v", err) } else if !protected { canDelete = true - ctx.Data["DeleteBranchLink"] = ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index) + "/cleanup" + ctx.Data["DeleteBranchLink"] = issue.Link() + "/cleanup" } } } @@ -1611,7 +1616,7 @@ func ViewIssue(ctx *context.Context) { ctx.Data["NumParticipants"] = len(participants) ctx.Data["Issue"] = issue ctx.Data["ReadOnly"] = false - ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + ctx.Data["Link"].(string) + ctx.Data["SignInLink"] = setting.AppSubURL + "/user/login?redirect_to=" + url.QueryEscape(ctx.Data["Link"].(string)) ctx.Data["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID) ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(models.UnitTypeProjects) @@ -1760,7 +1765,7 @@ func UpdateIssueContent(ctx *context.Context) { } content, err := markdown.RenderString(&markup.RenderContext{ - URLPrefix: ctx.FormString("context"), + URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? Metas: ctx.Repo.Repository.ComposeMetas(), GitRepo: ctx.Repo.GitRepo, Ctx: ctx, @@ -2192,7 +2197,7 @@ func UpdateCommentContent(ctx *context.Context) { } content, err := markdown.RenderString(&markup.RenderContext{ - URLPrefix: ctx.FormString("context"), + URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ? Metas: ctx.Repo.Repository.ComposeMetas(), GitRepo: ctx.Repo.GitRepo, Ctx: ctx, diff --git a/routers/web/repo/issue_stopwatch.go b/routers/web/repo/issue_stopwatch.go index b8efb3b841363..0e9405fde4dbd 100644 --- a/routers/web/repo/issue_stopwatch.go +++ b/routers/web/repo/issue_stopwatch.go @@ -94,6 +94,7 @@ func GetActiveStopwatch(c *context.Context) { } c.Data["ActiveStopwatch"] = StopwatchTmplInfo{ + issue.Link(), issue.Repo.FullName(), issue.Index, sw.Seconds() + 1, // ensure time is never zero in ui @@ -102,6 +103,7 @@ func GetActiveStopwatch(c *context.Context) { // StopwatchTmplInfo is a view on a stopwatch specifically for template rendering type StopwatchTmplInfo struct { + IssueLink string RepoSlug string IssueIndex int64 Seconds int64 diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go index 5e24cfa3c0a88..b15c7628db6ed 100644 --- a/routers/web/repo/lfs.go +++ b/routers/web/repo/lfs.go @@ -10,6 +10,7 @@ import ( gotemplate "html/template" "io" "net/http" + "net/url" "path" "strconv" "strings" @@ -285,7 +286,7 @@ func LFSFileGet(ctx *context.Context) { fileSize := meta.Size ctx.Data["FileSize"] = meta.Size - ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, "direct") + ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s/%s.git/info/lfs/objects/%s/%s", setting.AppURL, url.PathEscape(ctx.Repo.Repository.OwnerName), url.PathEscape(ctx.Repo.Repository.Name), url.PathEscape(meta.Oid), "direct") switch { case isRepresentableAsText: if st.IsSvgImage() { diff --git a/routers/web/repo/migrate.go b/routers/web/repo/migrate.go index b2e6fa890be06..f62f396239d0a 100644 --- a/routers/web/repo/migrate.go +++ b/routers/web/repo/migrate.go @@ -7,6 +7,7 @@ package repo import ( "net/http" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -237,7 +238,7 @@ func MigratePost(ctx *context.Context) { err = task.MigrateRepository(ctx.User, ctxUser, opts) if err == nil { - ctx.Redirect(ctxUser.HomeLink() + "/" + opts.RepoName) + ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(opts.RepoName)) return } diff --git a/routers/web/repo/milestone.go b/routers/web/repo/milestone.go index 21e1fb2eab8d9..eadc89333f90b 100644 --- a/routers/web/repo/milestone.go +++ b/routers/web/repo/milestone.go @@ -6,6 +6,7 @@ package repo import ( "net/http" + "net/url" "time" "code.gitea.io/gitea/models" @@ -244,7 +245,7 @@ func ChangeMilestoneStatus(ctx *context.Context) { } return } - ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=" + ctx.Params(":action")) + ctx.Redirect(ctx.Repo.RepoLink + "/milestones?state=" + url.QueryEscape(ctx.Params(":action"))) } // DeleteMilestone delete a milestone diff --git a/routers/web/repo/projects.go b/routers/web/repo/projects.go index e98d189c1ae14..b35d17b90663d 100644 --- a/routers/web/repo/projects.go +++ b/routers/web/repo/projects.go @@ -7,6 +7,7 @@ package repo import ( "fmt" "net/http" + "net/url" "strings" "code.gitea.io/gitea/models" @@ -172,7 +173,7 @@ func ChangeProjectStatus(ctx *context.Context) { } return } - ctx.Redirect(ctx.Repo.RepoLink + "/projects?state=" + ctx.Params(":action")) + ctx.Redirect(ctx.Repo.RepoLink + "/projects?state=" + url.QueryEscape(ctx.Params(":action"))) } // DeleteProject delete a project diff --git a/routers/web/repo/pull.go b/routers/web/repo/pull.go index 153ebc306fbe7..8029da6903ad0 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -10,8 +10,9 @@ import ( "crypto/subtle" "errors" "fmt" + "html" "net/http" - "path" + "net/url" "strings" "time" @@ -33,7 +34,6 @@ import ( "code.gitea.io/gitea/services/gitdiff" pull_service "code.gitea.io/gitea/services/pull" repo_service "code.gitea.io/gitea/services/repository" - "github.com/unknwon/com" ) const ( @@ -108,8 +108,7 @@ func getForkRepository(ctx *context.Context) *models.Repository { ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID) - ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name - ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID + ctx.Data["ForkRepo"] = forkRepo if err := ctx.User.GetOwnedOrganizations(); err != nil { ctx.ServerError("GetOwnedOrganizations", err) @@ -201,7 +200,7 @@ func ForkPost(ctx *context.Context) { } repo, has := models.HasForkedRepo(ctxUser.ID, traverseParentRepo.ID) if has { - ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) + ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name)) return } if !traverseParentRepo.IsFork { @@ -247,7 +246,7 @@ func ForkPost(ctx *context.Context) { } log.Trace("Repository forked[%d]: %s/%s", forkRepo.ID, ctxUser.Name, repo.Name) - ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) + ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name)) } func checkPullInfo(ctx *context.Context) *models.Issue { @@ -681,8 +680,7 @@ func ViewPullFiles(ctx *context.Context) { } } - headTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) - setCompareContext(ctx, baseCommit, commit, headTarget) + setCompareContext(ctx, baseCommit, commit, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireSimpleMDE"] = true @@ -745,7 +743,7 @@ func UpdatePullRequest(ctx *context.Context) { // ToDo: add check if maintainers are allowed to change branch ... (need migration & co) if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) { ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + url.PathEscape(fmt.Sprint(issue.Index))) return } @@ -765,7 +763,7 @@ func UpdatePullRequest(ctx *context.Context) { return } ctx.Flash.Error(flashError) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } else if models.IsErrRebaseConflicts(err) { conflictError := err.(models.ErrRebaseConflicts) @@ -779,19 +777,19 @@ func UpdatePullRequest(ctx *context.Context) { return } ctx.Flash.Error(flashError) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } ctx.Flash.Error(err.Error()) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } time.Sleep(1 * time.Second) ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) } // MergePullRequest response for merging pull request @@ -804,11 +802,11 @@ func MergePullRequest(ctx *context.Context) { if issue.IsClosed { if issue.IsPull { ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) - ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } @@ -821,13 +819,13 @@ func MergePullRequest(ctx *context.Context) { } if !allowedMerge { ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) + ctx.Redirect(issue.Link()) return } if pr.HasMerged { ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + ctx.Redirect(issue.Link()) return } @@ -836,11 +834,11 @@ func MergePullRequest(ctx *context.Context) { if err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + ctx.Redirect(issue.Link()) return } else if strings.Contains(err.Error(), "Wrong commit ID") { ctx.Flash.Error(ctx.Tr("repo.pulls.wrong_commit_id")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + ctx.Redirect(issue.Link()) return } @@ -848,19 +846,19 @@ func MergePullRequest(ctx *context.Context) { return } - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + ctx.Redirect(issue.Link()) return } if !pr.CanAutoMerge() { ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) + ctx.Redirect(issue.Link()) return } if pr.IsWorkInProgress() { ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } @@ -874,14 +872,14 @@ func MergePullRequest(ctx *context.Context) { return } else if !isRepoAdmin { ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } } if ctx.HasError() { ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } @@ -913,14 +911,14 @@ func MergePullRequest(ctx *context.Context) { if !noDeps { ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil { if models.IsErrInvalidMergeStyle(err) { ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } else if models.IsErrMergeConflicts(err) { conflictError := err.(models.ErrMergeConflicts) @@ -934,7 +932,7 @@ func MergePullRequest(ctx *context.Context) { return } ctx.Flash.Error(flashError) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } else if models.IsErrRebaseConflicts(err) { conflictError := err.(models.ErrRebaseConflicts) @@ -948,17 +946,17 @@ func MergePullRequest(ctx *context.Context) { return } ctx.Flash.Error(flashError) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } else if models.IsErrMergeUnrelatedHistories(err) { log.Debug("MergeUnrelatedHistories error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } else if git.IsErrPushOutOfDate(err) { log.Debug("MergePushOutOfDate error: %v", err) ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date")) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } else if git.IsErrPushRejected(err) { log.Debug("MergePushRejected error: %v", err) @@ -978,7 +976,7 @@ func MergePullRequest(ctx *context.Context) { } ctx.Flash.Error(flashError) } - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) return } ctx.ServerError("Merge", err) @@ -1007,7 +1005,7 @@ func MergePullRequest(ctx *context.Context) { deleteBranch(ctx, pr, headRepo) } - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) + ctx.Redirect(issue.Link()) } func stopTimerIfAvailable(user *models.User, issue *models.Issue) error { @@ -1096,6 +1094,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { pullIssue := &models.Issue{ RepoID: repo.ID, + Repo: repo, Title: form.Title, PosterID: ctx.User.ID, Poster: ctx.User, @@ -1137,7 +1136,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { } ctx.Flash.Error(flashError) } - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index)) + ctx.Redirect(pullIssue.Link()) return } ctx.ServerError("NewPullRequest", err) @@ -1145,7 +1144,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { } log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) - ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index)) + ctx.Redirect(pullIssue.Link()) } // TriggerTask response for a trigger task request @@ -1260,7 +1259,7 @@ func CleanUpPullRequest(ctx *context.Context) { defer func() { ctx.JSON(http.StatusOK, map[string]interface{}{ - "redirect": pr.BaseRepo.Link() + "/pulls/" + fmt.Sprint(issue.Index), + "redirect": issue.Link(), }) }() @@ -1368,7 +1367,7 @@ func UpdatePullRequestTarget(ctx *context.Context) { err := err.(models.ErrPullRequestAlreadyExists) RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name - errorMessage := ctx.Tr("repo.pulls.has_pull_request", ctx.Repo.RepoLink, RepoRelPath, err.IssueID) + errorMessage := ctx.Tr("repo.pulls.has_pull_request", html.EscapeString(ctx.Repo.RepoLink), html.EscapeString(RepoRelPath), err.IssueID) // FIXME: Creates url insidde locale string ctx.Flash.Error(errorMessage) ctx.JSON(http.StatusConflict, map[string]interface{}{ diff --git a/routers/web/repo/release.go b/routers/web/repo/release.go index df1fd745d8677..02d94780808b0 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -19,6 +19,7 @@ import ( "code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/upload" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" releaseservice "code.gitea.io/gitea/services/release" @@ -349,7 +350,7 @@ func NewReleasePost(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.tag.create_success", form.TagName)) - ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + form.TagName) + ctx.Redirect(ctx.Repo.RepoLink + "/src/tag/" + util.PathEscapeSegments(form.TagName)) return } diff --git a/routers/web/repo/repo.go b/routers/web/repo/repo.go index 735bf4fe9f067..35a1e49a841f8 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -243,7 +243,7 @@ func CreatePost(ctx *context.Context) { repo, err = repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts) if err == nil { log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) - ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) + ctx.Redirect(repo.Link()) return } } else { @@ -262,7 +262,7 @@ func CreatePost(ctx *context.Context) { }) if err == nil { log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) - ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) + ctx.Redirect(repo.Link()) return } } diff --git a/routers/web/repo/setting.go b/routers/web/repo/setting.go index e71a5bf482bbf..f35616472635c 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -614,7 +614,7 @@ func SettingsPost(ctx *context.Context) { log.Trace("Repository transfer process was started: %s/%s -> %s", ctx.Repo.Owner.Name, repo.Name, newOwner) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_started", newOwner.DisplayName())) - ctx.Redirect(ctx.Repo.Owner.HomeLink() + "/" + repo.Name + "/settings") + ctx.Redirect(repo.Link() + "/settings") case "cancel_transfer": if !ctx.Repo.IsOwner() { @@ -626,7 +626,7 @@ func SettingsPost(ctx *context.Context) { if err != nil { if models.IsErrNoPendingTransfer(err) { ctx.Flash.Error("repo.settings.transfer_abort_invalid") - ctx.Redirect(ctx.User.HomeLink() + "/" + repo.Name + "/settings") + ctx.Redirect(repo.Link() + "/settings") } else { ctx.ServerError("GetPendingRepositoryTransfer", err) } @@ -646,7 +646,7 @@ func SettingsPost(ctx *context.Context) { log.Trace("Repository transfer process was cancelled: %s/%s ", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.transfer_abort_success", repoTransfer.Recipient.Name)) - ctx.Redirect(ctx.Repo.Owner.HomeLink() + "/" + repo.Name + "/settings") + ctx.Redirect(repo.Link() + "/settings") case "delete": if !ctx.Repo.IsOwner() { @@ -795,7 +795,7 @@ func Collaboration(ctx *context.Context) { func CollaborationPost(ctx *context.Context) { name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator"))) if len(name) == 0 || ctx.Repo.Owner.LowerName == name { - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) return } @@ -803,7 +803,7 @@ func CollaborationPost(ctx *context.Context) { if err != nil { if models.IsErrUserNotExist(err) { ctx.Flash.Error(ctx.Tr("form.user_not_exist")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) } else { ctx.ServerError("GetUserByName", err) } @@ -812,14 +812,14 @@ func CollaborationPost(ctx *context.Context) { if !u.IsActive { ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) return } // Organization is not allowed to be added as a collaborator. if u.IsOrganization() { ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) return } @@ -839,7 +839,7 @@ func CollaborationPost(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) } // ChangeCollaborationAccessMode response for changing access of a collaboration diff --git a/routers/web/repo/setting_protected_branch.go b/routers/web/repo/setting_protected_branch.go index 876ff9ba460b6..32105b1d43cd9 100644 --- a/routers/web/repo/setting_protected_branch.go +++ b/routers/web/repo/setting_protected_branch.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/services/forms" pull_service "code.gitea.io/gitea/services/pull" @@ -89,7 +90,7 @@ func ProtectedBranchPost(ctx *context.Context) { log.Trace("Repository basic settings updated: %s/%s", ctx.Repo.Owner.Name, repo.Name) ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) default: ctx.NotFound("", nil) } @@ -197,7 +198,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { } if f.RequiredApprovals < 0 { ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min")) - ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, branch)) + ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch))) } var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64 @@ -274,7 +275,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) { return } ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch)) - ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, branch)) + ctx.Redirect(fmt.Sprintf("%s/settings/branches/%s", ctx.Repo.RepoLink, util.PathEscapeSegments(branch))) } else { if protectBranch != nil { if err := ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil { diff --git a/routers/web/repo/tag.go b/routers/web/repo/tag.go index a180399c9ebe0..b4d268759c7ff 100644 --- a/routers/web/repo/tag.go +++ b/routers/web/repo/tag.go @@ -58,7 +58,7 @@ func NewProtectedTagPost(ctx *context.Context) { } ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) - ctx.Redirect(setting.AppSubURL + ctx.Req.URL.Path) + ctx.Redirect(setting.AppSubURL + ctx.Req.URL.EscapedPath()) } // EditProtectedTag render the page to edit a protect tag diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 90be631c73499..14614aac0c968 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -231,7 +231,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { } if readmeFile != nil { readmeFile.name = entry.Name() + "/" + readmeFile.name - readmeTreelink = treeLink + "/" + entry.GetSubJumpablePathName() + readmeTreelink = treeLink + "/" + util.PathEscapeSegments(entry.GetSubJumpablePathName()) break } } @@ -300,7 +300,7 @@ func renderDirectory(ctx *context.Context, treeLink string) { fileSize = meta.Size ctx.Data["FileSize"] = meta.Size filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name)) - ctx.Data["RawFileLink"] = fmt.Sprintf("%s%s.git/info/lfs/objects/%s/%s", setting.AppURL, ctx.Repo.Repository.FullName(), meta.Oid, filenameBase64) + ctx.Data["RawFileLink"] = fmt.Sprintf("%s.git/info/lfs/objects/%s/%s", ctx.Repo.Repository.HTMLURL(), url.PathEscape(meta.Oid), url.PathEscape(filenameBase64)) } } } @@ -375,7 +375,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st fileSize := blob.Size() ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileName"] = blob.Name() - ctx.Data["RawFileLink"] = rawLink + "/" + ctx.Repo.TreePath + ctx.Data["RawFileLink"] = rawLink + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) buf := make([]byte, 1024) n, _ := util.ReadAtMost(dataRc, buf) @@ -421,7 +421,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st isTextFile = st.IsText() fileSize = meta.Size - ctx.Data["RawFileLink"] = fmt.Sprintf("%s/media/%s/%s", ctx.Repo.RepoLink, ctx.Repo.BranchNameSubURL(), ctx.Repo.TreePath) + ctx.Data["RawFileLink"] = ctx.Repo.RepoLink + "/media/" + ctx.Repo.BranchNameSubURL() + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } } } @@ -620,7 +620,7 @@ func checkHomeCodeViewable(ctx *context.Context) { } if firstUnit != nil { - ctx.Redirect(fmt.Sprintf("%s/%s%s", setting.AppSubURL, ctx.Repo.Repository.FullName(), firstUnit.URI)) + ctx.Redirect(fmt.Sprintf("%s%s", ctx.Repo.Repository.Link(), firstUnit.URI)) return } } @@ -676,7 +676,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri return nil } - ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + ctx.Repo.CommitID + "/" + ctx.Repo.TreePath + ctx.Data["LastCommitLoaderURL"] = ctx.Repo.RepoLink + "/lastcommit/" + url.PathEscape(ctx.Repo.CommitID) + "/" + util.PathEscapeSegments(ctx.Repo.TreePath) // Get current entry user currently looking at. entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) @@ -758,7 +758,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri treeLink := branchLink if len(ctx.Repo.TreePath) > 0 { - treeLink += "/" + ctx.Repo.TreePath + treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } ctx.Data["TreeLink"] = treeLink @@ -807,7 +807,7 @@ func renderCode(ctx *context.Context) { rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() if len(ctx.Repo.TreePath) > 0 { - treeLink += "/" + ctx.Repo.TreePath + treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath) } // Get Topics of this repo diff --git a/routers/web/repo/webhook.go b/routers/web/repo/webhook.go index 0ceb9bc1dc0eb..57307f0c0df47 100644 --- a/routers/web/repo/webhook.go +++ b/routers/web/repo/webhook.go @@ -9,6 +9,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "path" "strings" @@ -412,7 +413,7 @@ func TelegramHooksNewPost(ctx *context.Context) { w := &models.Webhook{ RepoID: orCtx.RepoID, - URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", form.BotToken, form.ChatID), + URL: fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)), ContentType: models.ContentTypeJSON, HookEvent: ParseHookEvent(form.WebhookForm), IsActive: form.Active, @@ -466,7 +467,7 @@ func MatrixHooksNewPost(ctx *context.Context) { w := &models.Webhook{ RepoID: orCtx.RepoID, - URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, form.RoomID), + URL: fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)), ContentType: models.ContentTypeJSON, HTTPMethod: "PUT", HookEvent: ParseHookEvent(form.WebhookForm), @@ -974,7 +975,7 @@ func TelegramHooksEditPost(ctx *context.Context) { return } w.Meta = string(meta) - w.URL = fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", form.BotToken, form.ChatID) + w.URL = fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage?chat_id=%s", url.PathEscape(form.BotToken), url.QueryEscape(form.ChatID)) w.HookEvent = ParseHookEvent(form.WebhookForm) w.IsActive = form.Active if err := w.UpdateEvent(); err != nil { @@ -1018,7 +1019,7 @@ func MatrixHooksEditPost(ctx *context.Context) { return } w.Meta = string(meta) - w.URL = fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, form.RoomID) + w.URL = fmt.Sprintf("%s/_matrix/client/r0/rooms/%s/send/m.room.message", form.HomeserverURL, url.PathEscape(form.RoomID)) w.HookEvent = ParseHookEvent(form.WebhookForm) w.IsActive = form.Active @@ -1160,7 +1161,7 @@ func TestWebhook(ctx *context.Context) { apiCommit := &api.PayloadCommit{ ID: commit.ID.String(), Message: commit.Message(), - URL: ctx.Repo.Repository.HTMLURL() + "/commit/" + commit.ID.String(), + URL: ctx.Repo.Repository.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()), Author: &api.PayloadUser{ Name: commit.Author.Name, Email: commit.Author.Email, diff --git a/routers/web/user/home.go b/routers/web/user/home.go index af5750e616057..708e37a67b99c 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -886,5 +886,5 @@ func Email2User(ctx *context.Context) { } return } - ctx.Redirect(setting.AppSubURL + "/user/" + u.Name) + ctx.Redirect(u.HomeLink()) } diff --git a/routers/web/user/notification.go b/routers/web/user/notification.go index 080ec4b582217..08cd1b8b310e3 100644 --- a/routers/web/user/notification.go +++ b/routers/web/user/notification.go @@ -167,7 +167,7 @@ func NotificationStatusPost(c *context.Context) { } if !c.FormBool("noredirect") { - url := fmt.Sprintf("%s/notifications?page=%s", setting.AppSubURL, c.FormString("page")) + url := fmt.Sprintf("%s/notifications?page=%s", setting.AppSubURL, url.QueryEscape(c.FormString("page"))) c.Redirect(url, http.StatusSeeOther) } @@ -189,6 +189,5 @@ func NotificationPurgePost(c *context.Context) { return } - url := fmt.Sprintf("%s/notifications", setting.AppSubURL) - c.Redirect(url, http.StatusSeeOther) + c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther) } diff --git a/routers/web/user/oauth.go b/routers/web/user/oauth.go index 642a7f33b00a8..3210d033d3c22 100644 --- a/routers/web/user/oauth.go +++ b/routers/web/user/oauth.go @@ -454,7 +454,7 @@ func AuthorizeOAuth(ctx *context.Context) { ctx.Data["State"] = form.State ctx.Data["Scope"] = form.Scope ctx.Data["Nonce"] = form.Nonce - ctx.Data["ApplicationUserLink"] = "@" + html.EscapeString(user.Name) + "" + ctx.Data["ApplicationUserLink"] = "@" + html.EscapeString(user.Name) + "" ctx.Data["ApplicationRedirectDomainHTML"] = "" + html.EscapeString(form.RedirectURI) + "" // TODO document SESSION <=> FORM err = ctx.Session.Set("client_id", app.ClientID) diff --git a/routers/web/user/profile.go b/routers/web/user/profile.go index d2a8d83faa1a5..553cd62493768 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -363,6 +363,6 @@ func Action(ctx *context.Context) { ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) return } - + // FIXME: We should check this URL and make sure that it's a valid Gitea URL ctx.RedirectToFirst(ctx.FormString("redirect_to"), u.HomeLink()) } diff --git a/services/lfs/server.go b/services/lfs/server.go index 946437fb274df..71e54f2674856 100644 --- a/services/lfs/server.go +++ b/services/lfs/server.go @@ -12,6 +12,7 @@ import ( "fmt" "io" "net/http" + "net/url" "path" "regexp" "strconv" @@ -45,17 +46,17 @@ type Claims struct { // DownloadLink builds a URL to download the object. func (rc *requestContext) DownloadLink(p lfs_module.Pointer) string { - return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/objects", p.Oid) + return setting.AppURL + path.Join(url.PathEscape(rc.User), url.PathEscape(rc.Repo+".git"), "info/lfs/objects", url.PathEscape(p.Oid)) } // UploadLink builds a URL to upload the object. func (rc *requestContext) UploadLink(p lfs_module.Pointer) string { - return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/objects", p.Oid, strconv.FormatInt(p.Size, 10)) + return setting.AppURL + path.Join(url.PathEscape(rc.User), url.PathEscape(rc.Repo+".git"), "info/lfs/objects", url.PathEscape(p.Oid), strconv.FormatInt(p.Size, 10)) } // VerifyLink builds a URL for verifying the object. func (rc *requestContext) VerifyLink(p lfs_module.Pointer) string { - return setting.AppURL + path.Join(rc.User, rc.Repo+".git", "info/lfs/verify") + return setting.AppURL + path.Join(url.PathEscape(rc.User), url.PathEscape(rc.Repo+".git"), "info/lfs/verify") } // CheckAcceptMediaType checks if the client accepts the LFS media type. diff --git a/services/webhook/dingtalk.go b/services/webhook/dingtalk.go index 7d352db18c69a..6a449fa6e5664 100644 --- a/services/webhook/dingtalk.go +++ b/services/webhook/dingtalk.go @@ -13,6 +13,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" dingtalk "github.com/lunny/dingtalk_webhook" ) @@ -41,7 +42,7 @@ func (d *DingtalkPayload) Create(p *api.CreatePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) - return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+refName), nil + return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil } // Delete implements PayloadConvertor Delete method @@ -50,7 +51,7 @@ func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) - return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+refName), nil + return createDingtalkPayload(title, title, fmt.Sprintf("view ref %s", refName), p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName)), nil } // Fork implements PayloadConvertor Fork method @@ -78,7 +79,7 @@ func (d *DingtalkPayload) Push(p *api.PushPayload) (api.Payloader, error) { linkText = fmt.Sprintf("view commit %s...%s", p.Commits[0].ID[:7], p.Commits[len(p.Commits)-1].ID[:7]) } if titleLink == "" { - titleLink = p.Repo.HTMLURL + "/src/" + branchName + titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName) } title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc) diff --git a/services/webhook/discord.go b/services/webhook/discord.go index 9a8f8a27a57d7..67357522e393e 100644 --- a/services/webhook/discord.go +++ b/services/webhook/discord.go @@ -16,6 +16,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) type ( @@ -115,7 +116,7 @@ func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) - return d.createPayload(p.Sender, title, "", p.Repo.HTMLURL+"/src/"+refName, greenColor), nil + return d.createPayload(p.Sender, title, "", p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), greenColor), nil } // Delete implements PayloadConvertor Delete method @@ -124,7 +125,7 @@ func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { refName := git.RefEndName(p.Ref) title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) - return d.createPayload(p.Sender, title, "", p.Repo.HTMLURL+"/src/"+refName, redColor), nil + return d.createPayload(p.Sender, title, "", p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), redColor), nil } // Fork implements PayloadConvertor Fork method @@ -150,7 +151,7 @@ func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) { titleLink = p.CompareURL } if titleLink == "" { - titleLink = p.Repo.HTMLURL + "/src/" + branchName + titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName) } title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc) diff --git a/services/webhook/general.go b/services/webhook/general.go index 777ae086b5e96..32a79c0783f05 100644 --- a/services/webhook/general.go +++ b/services/webhook/general.go @@ -7,10 +7,12 @@ package webhook import ( "fmt" "html" + "net/url" "strings" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) type linkFormatter = func(string, string) string @@ -22,7 +24,7 @@ func noneLinkFormatter(url string, text string) string { // htmlLinkFormatter creates a HTML link func htmlLinkFormatter(url string, text string) string { - return fmt.Sprintf(`%s`, url, html.EscapeString(text)) + return fmt.Sprintf(`%s`, html.EscapeString(url), html.EscapeString(text)) } func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { @@ -46,7 +48,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, with case api.HookIssueAssigned: list := make([]string, len(p.Issue.Assignees)) for i, user := range p.Issue.Assignees { - list[i] = linkFormatter(setting.AppURL+user.UserName, user.UserName) + list[i] = linkFormatter(setting.AppURL+url.PathEscape(user.UserName), user.UserName) } text = fmt.Sprintf("[%s] Issue assigned to %s: %s", repoLink, strings.Join(list, ", "), titleLink) color = greenColor @@ -66,7 +68,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, with text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) } var attachmentText string @@ -139,7 +141,7 @@ func getPullRequestPayloadInfo(p *api.PullRequestPayload, linkFormatter linkForm func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) { repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) - refLink := linkFormatter(p.Repository.HTMLURL+"/src/"+p.Release.TagName, p.Release.TagName) + refLink := linkFormatter(p.Repository.HTMLURL+"/src/"+util.PathEscapeSegments(p.Release.TagName), p.Release.TagName) switch p.Action { case api.HookReleasePublished: @@ -153,7 +155,7 @@ func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, w color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) } return text, color @@ -189,7 +191,7 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo color = redColor } if withSender { - text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName)) + text += fmt.Sprintf(" by %s", linkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName)) } return text, issueTitle, color diff --git a/services/webhook/matrix.go b/services/webhook/matrix.go index 10e0c8fd15543..79a05092dae7e 100644 --- a/services/webhook/matrix.go +++ b/services/webhook/matrix.go @@ -10,6 +10,7 @@ import ( "fmt" "html" "net/http" + "net/url" "regexp" "strings" @@ -19,6 +20,7 @@ import ( "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) const matrixPayloadSizeLimit = 1024 * 64 @@ -94,11 +96,11 @@ func MatrixLinkToRef(repoURL, ref string) string { refName := git.RefEndName(ref) switch { case strings.HasPrefix(ref, git.BranchPrefix): - return MatrixLinkFormatter(repoURL+"/src/branch/"+refName, refName) + return MatrixLinkFormatter(repoURL+"/src/branch/"+util.PathEscapeSegments(refName), refName) case strings.HasPrefix(ref, git.TagPrefix): - return MatrixLinkFormatter(repoURL+"/src/tag/"+refName, refName) + return MatrixLinkFormatter(repoURL+"/src/tag/"+util.PathEscapeSegments(refName), refName) default: - return MatrixLinkFormatter(repoURL+"/src/commit/"+refName, refName) + return MatrixLinkFormatter(repoURL+"/src/commit/"+util.PathEscapeSegments(refName), refName) } } @@ -186,7 +188,7 @@ func (m *MatrixPayloadUnsafe) PullRequest(p *api.PullRequestPayload) (api.Payloa // Review implements PayloadConvertor Review method func (m *MatrixPayloadUnsafe) Review(p *api.PullRequestPayload, event models.HookEventType) (api.Payloader, error) { - senderLink := MatrixLinkFormatter(setting.AppURL+p.Sender.UserName, p.Sender.UserName) + senderLink := MatrixLinkFormatter(setting.AppURL+url.PathEscape(p.Sender.UserName), p.Sender.UserName) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title) titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) @@ -281,7 +283,7 @@ func getMatrixHookRequest(w *models.Webhook, t *models.HookTask) (*http.Request, return nil, fmt.Errorf("getMatrixHookRequest: unable to hash payload: %+v", err) } - url := fmt.Sprintf("%s/%s", w.URL, txnID) + url := fmt.Sprintf("%s/%s", w.URL, url.PathEscape(txnID)) req, err := http.NewRequest(w.HTTPMethod, url, strings.NewReader(string(payload))) if err != nil { diff --git a/services/webhook/msteams.go b/services/webhook/msteams.go index ed5e6590582c1..8ddf931da2659 100644 --- a/services/webhook/msteams.go +++ b/services/webhook/msteams.go @@ -12,6 +12,7 @@ import ( "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/json" api "code.gitea.io/gitea/modules/structs" + "code.gitea.io/gitea/modules/util" ) type ( @@ -79,7 +80,7 @@ func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) { p.Sender, title, "", - p.Repo.HTMLURL+"/src/"+refName, + p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), greenColor, &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, ), nil @@ -96,7 +97,7 @@ func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) { p.Sender, title, "", - p.Repo.HTMLURL+"/src/"+refName, + p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName), yellowColor, &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, ), nil @@ -133,7 +134,7 @@ func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) { titleLink = p.CompareURL } if titleLink == "" { - titleLink = p.Repo.HTMLURL + "/src/" + branchName + titleLink = p.Repo.HTMLURL + "/src/" + util.PathEscapeSegments(branchName) } title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc) diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 2d489a495d184..e73213c1dfa7f 100644 --- a/templates/admin/emails/list.tmpl +++ b/templates/admin/emails/list.tmpl @@ -49,7 +49,7 @@ {{range .Emails}} - {{.Name}} + {{.Name}} {{.FullName}} {{if .IsPrimary}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}} diff --git a/templates/admin/repo/list.tmpl b/templates/admin/repo/list.tmpl index e96d9ebb33dc2..4059cb5debf00 100644 --- a/templates/admin/repo/list.tmpl +++ b/templates/admin/repo/list.tmpl @@ -45,13 +45,13 @@ {{.ID}} - {{.Owner.Name}} + {{.Owner.Name}} {{if .Owner.Visibility.IsPrivate}} {{svg "octicon-lock"}} {{end}} - {{.Name}} + {{.Name}} {{if .IsArchived}} {{$.i18n.Tr "repo.desc.archived"}} {{end}} diff --git a/templates/admin/user/list.tmpl b/templates/admin/user/list.tmpl index ceab7a9b1b0ae..93e6f38c27018 100644 --- a/templates/admin/user/list.tmpl +++ b/templates/admin/user/list.tmpl @@ -87,7 +87,7 @@ {{range .Users}} {{.ID}} - {{.Name}} + {{.Name}} {{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}} {{if .IsAdmin}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}} diff --git a/templates/base/head.tmpl b/templates/base/head.tmpl index 23d1190d94589..f05daaa421208 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -98,10 +98,10 @@ {{if .IsSigned }} {{ if ne .SignedUser.Theme "gitea" }} - + {{end}} {{else if ne DefaultTheme "gitea"}} - + {{end}} {{template "custom/header" .}} diff --git a/templates/base/head_navbar.tmpl b/templates/base/head_navbar.tmpl index 348e7671a57e0..57ddbf732a2c2 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -63,8 +63,7 @@ {{else if .IsSigned}}