From bbffcc3aecda040a40d49078e7141213bc8d78af Mon Sep 17 00:00:00 2001 From: zeripath Date: Tue, 16 Nov 2021 18:18:25 +0000 Subject: [PATCH] Multiple Escaping Improvements (#17551) 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. 1. The first commit in this PR looks through all href, src and links in the Gitea codebase and has attempted to catch all the places where there is potentially incomplete escaping. 2. Whilst doing this we will prefer to use functions that create URLs over recreating them by hand. 3. All uses of strings should be directly escaped - even if they are not currently expected to contain escaping characters. The main benefit to doing this will be that we can consider relaxing the constraints on user names and reponames in future. 4. The next commit looks at escaping in the wiki and re-considers the urls that are used there. Using the improved escaping here wiki files containing '/'. (This implementation will currently still place all of the wiki files the root directory of the repo but this would not be difficult to change.) 5. The title generation in feeds is now properly escaped. 6. EscapePound is no longer needed - urls should be PathEscaped / QueryEscaped as necessary but then re-escaped with Escape when creating html with locales Signed-off-by: Andrew Thornton Signed-off-by: Andrew Thornton --- 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 | 16 ++- modules/context/repo.go | 38 ++--- 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 | 21 ++- modules/upload/upload.go | 3 +- options/locale/locale_en-US.ini | 38 ++--- 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/feed/convert.go | 134 ++++++++++++++---- 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 | 72 +++++----- 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/repo/wiki.go | 65 +++++++-- routers/web/repo/wiki_test.go | 22 +-- routers/web/user/home.go | 2 +- routers/web/user/notification.go | 5 +- routers/web/user/oauth.go | 2 +- routers/web/user/profile.go | 2 +- routers/web/web.go | 28 ++-- 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 +- services/wiki/wiki.go | 2 +- 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 | 28 ++-- 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 | 30 ++-- 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/create.tmpl | 2 +- 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/image_diff.tmpl | 4 +- 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 | 30 ++-- templates/repo/view_list.tmpl | 18 +-- templates/repo/wiki/new.tmpl | 2 +- templates/repo/wiki/pages.tmpl | 2 +- templates/repo/wiki/start.tmpl | 2 +- templates/repo/wiki/view.tmpl | 12 +- templates/shared/issuelist.tmpl | 8 +- templates/user/auth/grant.tmpl | 4 +- templates/user/dashboard/feeds.tmpl | 58 ++++---- 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 +- 153 files changed, 891 insertions(+), 712 deletions(-) diff --git a/integrations/issue_test.go b/integrations/issue_test.go index 03ca382de43..56cddcb0639 100644 --- a/integrations/issue_test.go +++ b/integrations/issue_test.go @@ -65,7 +65,7 @@ func TestViewIssuesSortByType(t *testing.T) { repo := unittest.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 03229e10e12..91166274a23 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 22d71e6ee20..cf6261dffe0 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 7c970e1fdb6..80ac3e16f85 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 ed82aaf483b..34edc676cfd 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" @@ -59,7 +60,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 0a1445d2f2d..da63dfd106a 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 a6ded049c31..df34b93ec53 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 0d34bdcaf33..983fb7aa8dc 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 48249ae84c6..1e180736183 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" @@ -475,7 +476,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 4624791b8ff..f7bd67b8ca7 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 f44fc763a56..16396e181db 100644 --- a/models/repo.go +++ b/models/repo.go @@ -314,7 +314,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 @@ -323,14 +323,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. @@ -709,19 +709,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 @@ -930,11 +925,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 6c5e03c0d0d..aa1b3bc15f3 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 12035dbe423..8146c184e70 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" @@ -315,17 +316,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 cb7131907ef..8adf1f306bd 100644 --- a/modules/context/context.go +++ b/modules/context/context.go @@ -70,6 +70,16 @@ type Context struct { Org *Organization } +// TrHTMLEscapeArgs runs Tr but pre-escapes all arguments with html.EscapeString. +// This is useful if the locale message is intended to only produce HTML content. +func (ctx *Context) TrHTMLEscapeArgs(msg string, args ...string) string { + trArgs := make([]interface{}, len(args)) + for i, arg := range args { + trArgs[i] = html.EscapeString(arg) + } + return ctx.Tr(msg, trArgs...) +} + // GetData returns the data func (ctx *Context) GetData() map[string]interface{} { return ctx.Data @@ -120,9 +130,9 @@ func RedirectToUser(ctx *Context, userName string, redirectUserID int64) { } redirectPath := strings.Replace( - ctx.Req.URL.Path, - userName, - user.Name, + ctx.Req.URL.EscapedPath(), + url.PathEscape(userName), + url.PathEscape(user.Name), 1, ) if ctx.Req.URL.RawQuery != "" { diff --git a/modules/context/repo.go b/modules/context/repo.go index d5763c78a36..3be33f24838 100644 --- a/modules/context/repo.go +++ b/modules/context/repo.go @@ -41,10 +41,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 @@ -189,11 +189,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 "" @@ -321,9 +321,9 @@ 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 != "" { @@ -588,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 @@ -596,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 @@ -621,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}" } @@ -810,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 } @@ -845,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] { @@ -857,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 9f43bb82f8a..9905b51fe45 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,13 +148,13 @@ 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(), Created: commit.Committer.When, }, - 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, @@ -170,7 +171,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(), Created: commit.Committer.When, }, diff --git a/modules/convert/issue.go b/modules/convert/issue.go index 3974d460e0e..7363cfb8fb8 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 fae7be1257d..5f4fef02b9e 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 13926fba72d..6988f31a367 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 d7e3ff45258..0bcdb8c3a1a 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 60a05e280e8..02bc1ebcab9 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 abd14b1db81..40309240176 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 b3edea341f1..81579dccc53 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 c86f0d570be..a545ce952ba 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 991816c1033..8b46ed40cec 100644 --- a/modules/templates/helper.go +++ b/modules/templates/helper.go @@ -139,17 +139,14 @@ func NewFuncMap() []template.FuncMap { } return str[start:end] }, - "EllipsisString": base.EllipsisString, - "DiffTypeToStr": DiffTypeToStr, - "DiffLineTypeToStr": DiffLineTypeToStr, - "Sha1": Sha1, - "ShortSha": base.ShortSha, - "MD5": base.EncodeMD5, - "ActionContent2Commits": ActionContent2Commits, - "PathEscape": url.PathEscape, - "EscapePound": func(str string) string { - return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str) - }, + "EllipsisString": base.EllipsisString, + "DiffTypeToStr": DiffTypeToStr, + "DiffLineTypeToStr": DiffLineTypeToStr, + "Sha1": Sha1, + "ShortSha": base.ShortSha, + "MD5": base.EncodeMD5, + "ActionContent2Commits": ActionContent2Commits, + "PathEscape": url.PathEscape, "PathEscapeSegments": util.PathEscapeSegments, "URLJoin": util.URLJoin, "RenderCommitMessage": RenderCommitMessage, @@ -742,7 +739,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 e430a5d8b3c..097facb4d5f 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/options/locale/locale_en-US.ini b/options/locale/locale_en-US.ini index af688764081..c39063e46e7 100644 --- a/options/locale/locale_en-US.ini +++ b/options/locale/locale_en-US.ini @@ -1409,7 +1409,7 @@ pulls.filter_branch = Filter branch pulls.no_results = No results found. pulls.nothing_to_compare = These branches are equal. There is no need to create a pull request. pulls.nothing_to_compare_and_allow_empty_pr = These branches are equal. This PR will be empty. -pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d` +pulls.has_pull_request = `A pull request between these branches already exists: %[2]s#%[3]d` pulls.create = Create Pull Request pulls.title_desc = wants to merge %[1]d commits from %[2]s into %[3]s pulls.merged_title_desc = merged %[1]d commits from %[2]s into %[3]s %[4]s @@ -2761,32 +2761,32 @@ notices.delete_success = The system notices have been deleted. [action] create_repo = created repository %s rename_repo = renamed repository from %[1]s to %[3]s -commit_repo = pushed to %[3]s at %[4]s -create_issue = `opened issue %s#%[2]s` -close_issue = `closed issue %s#%[2]s` -reopen_issue = `reopened issue %s#%[2]s` -create_pull_request = `created pull request %s#%[2]s` -close_pull_request = `closed pull request %s#%[2]s` -reopen_pull_request = `reopened pull request %s#%[2]s` -comment_issue = `commented on issue %s#%[2]s` -comment_pull = `commented on pull request %s#%[2]s` -merge_pull_request = `merged pull request %s#%[2]s` +commit_repo = pushed to %[3]s at %[4]s +create_issue = `opened issue %[3]s#%[2]s` +close_issue = `closed issue %[3]s#%[2]s` +reopen_issue = `reopened issue %[3]s#%[2]s` +create_pull_request = `created pull request %[3]s#%[2]s` +close_pull_request = `closed pull request %[3]s#%[2]s` +reopen_pull_request = `reopened pull request %[3]s#%[2]s` +comment_issue = `commented on issue %[3]s#%[2]s` +comment_pull = `commented on pull request %[3]s#%[2]s` +merge_pull_request = `merged pull request %[3]s#%[2]s` transfer_repo = transferred repository %s to %s -push_tag = pushed tag %[4]s to %[3]s +push_tag = pushed tag %[3]s to %[4]s delete_tag = deleted tag %[2]s from %[3]s delete_branch = deleted branch %[2]s from %[3]s compare_branch = Compare compare_commits = Compare %d commits compare_commits_general = Compare commits -mirror_sync_push = synced commits to %[3]s at %[4]s from mirror -mirror_sync_create = synced new reference %[2]s to %[3]s from mirror +mirror_sync_push = synced commits to %[3]s at %[4]s from mirror +mirror_sync_create = synced new reference %[3]s to %[4]s from mirror mirror_sync_delete = synced and deleted reference %[2]s at %[3]s from mirror -approve_pull_request = `approved %s#%[2]s` -reject_pull_request = `suggested changes for %s#%[2]s` -publish_release = `released "%[4]s" at %[3]s` -review_dismissed = `dismissed review from %[4]s for %[3]s#%[2]s` +approve_pull_request = `approved %[3]s#%[2]s` +reject_pull_request = `suggested changes for %[3]s#%[2]s` +publish_release = `released "%[4]s" at %[3]s` +review_dismissed = `dismissed review from %[4]s for %[3]s#%[2]s` review_dismissed_reason = Reason: -create_branch = created branch %[3]s in %[4]s +create_branch = created branch %[3]s in %[4]s starred_repo = starred %[2]s watched_repo = started watching %[2]s diff --git a/routers/api/v1/org/member.go b/routers/api/v1/org/member.go index a6f140c38f6..4530349f2c2 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 e304e06740b..29b126db9a4 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 98ee2b4de5c..c20a4776cc0 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 460b740171c..5fd15b5c5a9 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/login" "code.gitea.io/gitea/modules/auth/pam" @@ -396,7 +398,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 @@ -414,7 +416,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 a13f7317e4c..432dd2f6ae6 100644 --- a/routers/web/admin/repos.go +++ b/routers/web/admin/repos.go @@ -58,7 +58,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")), }) } @@ -161,5 +161,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 db7fe7b36f6..8bafd1f19c3 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" @@ -188,7 +188,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 { @@ -366,7 +366,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 @@ -382,12 +382,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/feed/convert.go b/routers/web/feed/convert.go index dfb03785a64..4743668621d 100644 --- a/routers/web/feed/convert.go +++ b/routers/web/feed/convert.go @@ -15,10 +15,35 @@ import ( "code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/templates" + "code.gitea.io/gitea/modules/util" "github.com/gorilla/feeds" ) +func toBranchLink(act *models.Action) string { + return act.GetRepoLink() + "/src/branch/" + util.PathEscapeSegments(act.GetBranch()) +} + +func toTagLink(act *models.Action) string { + return act.GetRepoLink() + "/src/tag/" + util.PathEscapeSegments(act.GetTag()) +} + +func toIssueLink(act *models.Action) string { + return act.GetRepoLink() + "/issues/" + url.PathEscape(act.GetIssueInfos()[0]) +} + +func toPullLink(act *models.Action) string { + return act.GetRepoLink() + "/pulls/" + url.PathEscape(act.GetIssueInfos()[0]) +} + +func toSrcLink(act *models.Action) string { + return act.GetRepoLink() + "/src/" + util.PathEscapeSegments(act.GetBranch()) +} + +func toReleaseLink(act *models.Action) string { + return act.GetRepoLink() + "/releases/tag/" + util.PathEscapeSegments(act.GetBranch()) +} + // feedActionsToFeedItems convert gitea's Action feed to feeds Item func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) { for _, act := range actions { @@ -32,62 +57,111 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite title = act.ActUser.DisplayName() + " " switch act.OpType { case models.ActionCreateRepo: - title += ctx.Tr("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) + title += ctx.TrHTMLEscapeArgs("action.create_repo", act.GetRepoLink(), act.ShortRepoPath()) + link.Href = act.GetRepoLink() case models.ActionRenameRepo: - title += ctx.Tr("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + title += ctx.TrHTMLEscapeArgs("action.rename_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + link.Href = act.GetRepoLink() case models.ActionCommitRepo: - branchLink := act.GetBranch() + link.Href = toBranchLink(act) if len(act.Content) != 0 { - title += ctx.Tr("action.commit_repo", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + title += ctx.TrHTMLEscapeArgs("action.commit_repo", act.GetRepoLink(), link.Href, act.GetBranch(), act.ShortRepoPath()) } else { - title += ctx.Tr("action.create_branch", act.GetRepoLink(), branchLink, act.GetBranch(), act.ShortRepoPath()) + title += ctx.TrHTMLEscapeArgs("action.create_branch", act.GetRepoLink(), link.Href, act.GetBranch(), act.ShortRepoPath()) } case models.ActionCreateIssue: - title += ctx.Tr("action.create_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + link.Href = toIssueLink(act) + title += ctx.TrHTMLEscapeArgs("action.create_issue", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionCreatePullRequest: - title += ctx.Tr("action.create_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + link.Href = toPullLink(act) + title += ctx.TrHTMLEscapeArgs("action.create_pull_request", link.Href, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionTransferRepo: - title += ctx.Tr("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) + link.Href = act.GetRepoLink() + title += ctx.TrHTMLEscapeArgs("action.transfer_repo", act.GetContent(), act.GetRepoLink(), act.ShortRepoPath()) case models.ActionPushTag: - title += ctx.Tr("action.push_tag", act.GetRepoLink(), url.QueryEscape(act.GetTag()), act.ShortRepoPath()) + link.Href = toTagLink(act) + title += ctx.TrHTMLEscapeArgs("action.push_tag", act.GetRepoLink(), link.Href, act.GetTag(), act.ShortRepoPath()) case models.ActionCommentIssue: - title += ctx.Tr("action.comment_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + issueLink := toIssueLink(act) + if link.Href == "#" { + link.Href = issueLink + } + title += ctx.TrHTMLEscapeArgs("action.comment_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionMergePullRequest: - title += ctx.Tr("action.merge_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + pullLink := toPullLink(act) + if link.Href == "#" { + link.Href = pullLink + } + title += ctx.TrHTMLEscapeArgs("action.merge_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionCloseIssue: - title += ctx.Tr("action.close_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + issueLink := toIssueLink(act) + if link.Href == "#" { + link.Href = issueLink + } + title += ctx.TrHTMLEscapeArgs("action.close_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionReopenIssue: - title += ctx.Tr("action.reopen_issue", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + issueLink := toIssueLink(act) + if link.Href == "#" { + link.Href = issueLink + } + title += ctx.TrHTMLEscapeArgs("action.reopen_issue", issueLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionClosePullRequest: - title += ctx.Tr("action.close_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + pullLink := toPullLink(act) + if link.Href == "#" { + link.Href = pullLink + } + title += ctx.TrHTMLEscapeArgs("action.close_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionReopenPullRequest: - title += ctx.Tr("action.reopen_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath) + pullLink := toPullLink(act) + if link.Href == "#" { + link.Href = pullLink + } + title += ctx.TrHTMLEscapeArgs("action.reopen_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionDeleteTag: - title += ctx.Tr("action.delete_tag", act.GetRepoLink(), html.EscapeString(act.GetTag()), act.ShortRepoPath()) + link.Href = act.GetRepoLink() + title += ctx.TrHTMLEscapeArgs("action.delete_tag", act.GetRepoLink(), act.GetTag(), act.ShortRepoPath()) case models.ActionDeleteBranch: - title += ctx.Tr("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + link.Href = act.GetRepoLink() + title += ctx.TrHTMLEscapeArgs("action.delete_branch", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) case models.ActionMirrorSyncPush: - title += ctx.Tr("action.mirror_sync_push", act.GetRepoLink(), url.QueryEscape(act.GetBranch()), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + srcLink := toSrcLink(act) + if link.Href == "#" { + link.Href = srcLink + } + title += ctx.TrHTMLEscapeArgs("action.mirror_sync_push", act.GetRepoLink(), srcLink, act.GetBranch(), act.ShortRepoPath()) case models.ActionMirrorSyncCreate: - title += ctx.Tr("action.mirror_sync_create", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + srcLink := toSrcLink(act) + if link.Href == "#" { + link.Href = srcLink + } + title += ctx.TrHTMLEscapeArgs("action.mirror_sync_create", act.GetRepoLink(), srcLink, act.GetBranch(), act.ShortRepoPath()) case models.ActionMirrorSyncDelete: - title += ctx.Tr("action.mirror_sync_delete", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath()) + link.Href = act.GetRepoLink() + title += ctx.TrHTMLEscapeArgs("action.mirror_sync_delete", act.GetRepoLink(), act.GetBranch(), act.ShortRepoPath()) case models.ActionApprovePullRequest: - title += ctx.Tr("action.approve_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + pullLink := toPullLink(act) + title += ctx.TrHTMLEscapeArgs("action.approve_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionRejectPullRequest: - title += ctx.Tr("action.reject_pull_request", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + pullLink := toPullLink(act) + title += ctx.TrHTMLEscapeArgs("action.reject_pull_request", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionCommentPull: - title += ctx.Tr("action.comment_pull", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath()) + pullLink := toPullLink(act) + title += ctx.TrHTMLEscapeArgs("action.comment_pull", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath()) case models.ActionPublishRelease: - title += ctx.Tr("action.publish_release", act.GetRepoLink(), html.EscapeString(act.GetBranch()), act.ShortRepoPath(), act.Content) + releaseLink := toReleaseLink(act) + if link.Href == "#" { + link.Href = releaseLink + } + title += ctx.TrHTMLEscapeArgs("action.publish_release", act.GetRepoLink(), releaseLink, act.ShortRepoPath(), act.Content) case models.ActionPullReviewDismissed: - title += ctx.Tr("action.review_dismissed", act.GetRepoLink(), act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) + pullLink := toPullLink(act) + title += ctx.TrHTMLEscapeArgs("action.review_dismissed", pullLink, act.GetIssueInfos()[0], act.ShortRepoPath(), act.GetIssueInfos()[1]) case models.ActionStarRepo: - title += ctx.Tr("action.starred_repo", act.GetRepoLink(), act.GetRepoPath()) - link = &feeds.Link{Href: act.GetRepoLink()} + link.Href = act.GetRepoLink() + title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoLink(), act.GetRepoPath()) case models.ActionWatchRepo: - title += ctx.Tr("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) - link = &feeds.Link{Href: act.GetRepoLink()} + link.Href = act.GetRepoLink() + title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) default: return nil, fmt.Errorf("unknown action type: %v", act.OpType) } @@ -104,7 +178,7 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite desc += "\n\n" } desc += fmt.Sprintf("%s\n%s", - fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1), + html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1)), commit.Sha1, templates.RenderCommitMessage(commit.Message, repoLink, nil), ) diff --git a/routers/web/org/setting.go b/routers/web/org/setting.go index d8add77f660..53c31a1c605 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" @@ -76,7 +77,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 3fe97142ae2..2fc72f06208 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" @@ -105,7 +106,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": @@ -119,7 +120,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) } @@ -128,7 +129,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 } @@ -156,7 +157,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: @@ -181,7 +182,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) @@ -204,11 +205,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 @@ -273,7 +274,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 @@ -375,7 +376,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 3632d1846ea..110ec037e1d 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 4c0f94f15db..06cce92417a 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 86ecc2bab16..01c324e9e93 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" @@ -38,7 +38,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 @@ -54,22 +54,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) } } @@ -619,8 +625,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 d9f8c200926..088edbfd29b 100644 --- a/routers/web/repo/editor.go +++ b/routers/web/repo/editor.go @@ -204,7 +204,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 @@ -299,9 +299,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 { @@ -495,7 +495,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 { @@ -602,7 +602,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 @@ -698,7 +698,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 d9e15a784fe..95363258e9d 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" @@ -106,7 +107,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) } } @@ -764,7 +765,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 } } @@ -983,6 +984,7 @@ func NewIssuePost(ctx *context.Context) { issue := &models.Issue{ RepoID: repo.ID, + Repo: repo, Title: form.Title, PosterID: ctx.User.ID, Poster: ctx.User, @@ -1009,9 +1011,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()) } } @@ -1097,13 +1099,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 } @@ -1496,7 +1501,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" } } } @@ -1624,7 +1629,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(unit.TypeProjects) @@ -1773,7 +1778,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, @@ -2205,7 +2210,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 b8efb3b8413..0e9405fde4d 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 5e24cfa3c0a..b15c7628db6 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 d5e0a7696bc..f91c344e94b 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 21e1fb2eab8..eadc89333f9 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 08b285df0a1..437da14d45d 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" @@ -173,7 +174,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 0ac05a76093..43372782143 100644 --- a/routers/web/repo/pull.go +++ b/routers/web/repo/pull.go @@ -10,8 +10,10 @@ import ( "crypto/subtle" "errors" "fmt" + "html" "net/http" - "path" + "net/url" + "strconv" "strings" "time" @@ -34,7 +36,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 ( @@ -109,8 +110,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) @@ -202,7 +202,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 { @@ -248,7 +248,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 { @@ -682,8 +682,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 @@ -746,7 +745,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(issue.Link()) return } @@ -766,7 +765,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) @@ -780,19 +779,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 @@ -805,11 +804,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 } @@ -822,13 +821,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 } @@ -837,11 +836,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 } @@ -849,19 +848,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 } @@ -875,14 +874,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 } @@ -914,14 +913,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) @@ -935,7 +934,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) @@ -949,17 +948,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) @@ -979,7 +978,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) @@ -1008,7 +1007,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 { @@ -1097,6 +1096,7 @@ func CompareAndPullRequestPost(ctx *context.Context) { pullIssue := &models.Issue{ RepoID: repo.ID, + Repo: repo, Title: form.Title, PosterID: ctx.User.ID, Poster: ctx.User, @@ -1138,7 +1138,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) @@ -1146,7 +1146,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 @@ -1261,7 +1261,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(), }) }() @@ -1369,7 +1369,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+"/pulls/"+strconv.FormatInt(err.IssueID, 10)), 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 20f6ddd2a57..3f12ee72bcb 100644 --- a/routers/web/repo/release.go +++ b/routers/web/repo/release.go @@ -20,6 +20,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" @@ -350,7 +351,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 c70dec64811..46cef7664ac 100644 --- a/routers/web/repo/repo.go +++ b/routers/web/repo/repo.go @@ -244,7 +244,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 { @@ -263,7 +263,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 cecd1da07cf..641052316c2 100644 --- a/routers/web/repo/setting.go +++ b/routers/web/repo/setting.go @@ -615,7 +615,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() { @@ -627,7 +627,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) } @@ -647,7 +647,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() { @@ -796,7 +796,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 } @@ -804,7 +804,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) } @@ -813,14 +813,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 } @@ -840,7 +840,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 876ff9ba460..32105b1d43c 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 a180399c9eb..b4d268759c7 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 cecd8437b69..12b3aef505e 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -232,7 +232,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 } } @@ -301,7 +301,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)) } } } @@ -376,7 +376,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) @@ -422,7 +422,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) } } } @@ -628,7 +628,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 } } @@ -684,7 +684,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) @@ -766,7 +766,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 @@ -815,7 +815,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 f47f8d651d7..4f6660926ee 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" @@ -414,7 +415,7 @@ func TelegramHooksNewPost(ctx *context.Context) { w := &webhook.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: webhook.ContentTypeJSON, HookEvent: ParseHookEvent(form.WebhookForm), IsActive: form.Active, @@ -468,7 +469,7 @@ func MatrixHooksNewPost(ctx *context.Context) { w := &webhook.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: webhook.ContentTypeJSON, HTTPMethod: "PUT", HookEvent: ParseHookEvent(form.WebhookForm), @@ -976,7 +977,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 { @@ -1020,7 +1021,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 @@ -1162,7 +1163,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/repo/wiki.go b/routers/web/repo/wiki.go index 16927de2e9f..82f56a8c4a1 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -180,7 +180,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { ctx.Data["Pages"] = pages // get requested pagename - pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) + pageName := wiki_service.NormalizeWikiName(ctx.Params("*")) if len(pageName) == 0 { pageName = "Home" } @@ -193,7 +193,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { //lookup filename in wiki - get filecontent, gitTree entry , real filename data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) if noEntry { - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } if entry == nil || ctx.Written() { if wikiRepo != nil { @@ -276,7 +276,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) } // get requested pagename - pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) + pageName := wiki_service.NormalizeWikiName(ctx.Params("*")) if len(pageName) == 0 { pageName = "Home" } @@ -291,7 +291,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) //lookup filename in wiki - get filecontent, gitTree entry , real filename data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) if noEntry { - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } if entry == nil || ctx.Written() { if wikiRepo != nil { @@ -352,7 +352,7 @@ func renderEditPage(ctx *context.Context) { }() // get requested pagename - pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) + pageName := wiki_service.NormalizeWikiName(ctx.Params("*")) if len(pageName) == 0 { pageName = "Home" } @@ -365,7 +365,7 @@ func renderEditPage(ctx *context.Context) { //lookup filename in wiki - get filecontent, gitTree entry , real filename data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName) if noEntry { - ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") + ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages") } if entry == nil || ctx.Written() { return @@ -378,6 +378,32 @@ func renderEditPage(ctx *context.Context) { ctx.Data["footerContent"] = "" } +// WikiPost renders post of wiki page +func WikiPost(ctx *context.Context) { + switch ctx.FormString("action") { + case "_new": + if !ctx.Repo.CanWrite(unit.TypeWiki) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } + NewWikiPost(ctx) + return + case "_delete": + if !ctx.Repo.CanWrite(unit.TypeWiki) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } + DeleteWikiPagePost(ctx) + return + } + + if !ctx.Repo.CanWrite(unit.TypeWiki) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } + EditWikiPost(ctx) +} + // Wiki renders single wiki page func Wiki(ctx *context.Context) { ctx.Data["PageIsWiki"] = true @@ -389,6 +415,29 @@ func Wiki(ctx *context.Context) { return } + switch ctx.FormString("action") { + case "_pages": + WikiPages(ctx) + return + case "_revision": + WikiRevision(ctx) + return + case "_edit": + if !ctx.Repo.CanWrite(unit.TypeWiki) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } + EditWiki(ctx) + return + case "_new": + if !ctx.Repo.CanWrite(unit.TypeWiki) { + ctx.NotFound(ctx.Req.URL.RequestURI(), nil) + return + } + NewWiki(ctx) + return + } + wikiRepo, entry := renderViewPage(ctx) defer func() { if wikiRepo != nil { @@ -652,7 +701,7 @@ func EditWikiPost(ctx *context.Context) { return } - oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":page")) + oldWikiName := wiki_service.NormalizeWikiName(ctx.Params("*")) newWikiName := wiki_service.NormalizeWikiName(form.Title) if len(form.Message) == 0 { @@ -669,7 +718,7 @@ func EditWikiPost(ctx *context.Context) { // DeleteWikiPagePost delete wiki page func DeleteWikiPagePost(ctx *context.Context) { - wikiName := wiki_service.NormalizeWikiName(ctx.Params(":page")) + wikiName := wiki_service.NormalizeWikiName(ctx.Params("*")) if len(wikiName) == 0 { wikiName = "Home" } diff --git a/routers/web/repo/wiki_test.go b/routers/web/repo/wiki_test.go index cf49f19afe4..87f2779c1a8 100644 --- a/routers/web/repo/wiki_test.go +++ b/routers/web/repo/wiki_test.go @@ -76,8 +76,8 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas interface{}) { func TestWiki(t *testing.T) { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_pages") - ctx.SetParams(":page", "Home") + ctx := test.MockContext(t, "user2/repo1/wiki/?action=_pages") + ctx.SetParams("*", "Home") test.LoadRepo(t, ctx, 1) Wiki(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) @@ -88,7 +88,7 @@ func TestWiki(t *testing.T) { func TestWikiPages(t *testing.T) { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_pages") + ctx := test.MockContext(t, "user2/repo1/wiki/?action=_pages") test.LoadRepo(t, ctx, 1) WikiPages(ctx) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) @@ -98,7 +98,7 @@ func TestWikiPages(t *testing.T) { func TestNewWiki(t *testing.T) { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_new") + ctx := test.MockContext(t, "user2/repo1/wiki/?action=_new") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) NewWiki(ctx) @@ -113,7 +113,7 @@ func TestNewWikiPost(t *testing.T) { } { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_new") + ctx := test.MockContext(t, "user2/repo1/wiki/?action=_new") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) web.SetForm(ctx, &forms.NewWikiForm{ @@ -131,7 +131,7 @@ func TestNewWikiPost(t *testing.T) { func TestNewWikiPost_ReservedName(t *testing.T) { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_new") + ctx := test.MockContext(t, "user2/repo1/wiki/?action=_new") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) web.SetForm(ctx, &forms.NewWikiForm{ @@ -148,8 +148,8 @@ func TestNewWikiPost_ReservedName(t *testing.T) { func TestEditWiki(t *testing.T) { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_edit/Home") - ctx.SetParams(":page", "Home") + ctx := test.MockContext(t, "user2/repo1/wiki/Home?action=_edit") + ctx.SetParams("*", "Home") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) EditWiki(ctx) @@ -164,8 +164,8 @@ func TestEditWikiPost(t *testing.T) { "New/", } { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/_new/Home") - ctx.SetParams(":page", "Home") + ctx := test.MockContext(t, "user2/repo1/wiki/Home?action=_new") + ctx.SetParams("*", "Home") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) web.SetForm(ctx, &forms.NewWikiForm{ @@ -186,7 +186,7 @@ func TestEditWikiPost(t *testing.T) { func TestDeleteWikiPagePost(t *testing.T) { unittest.PrepareTestEnv(t) - ctx := test.MockContext(t, "user2/repo1/wiki/Home/delete") + ctx := test.MockContext(t, "user2/repo1/wiki/Home?action=_delete") test.LoadUser(t, ctx, 2) test.LoadRepo(t, ctx, 1) DeleteWikiPagePost(ctx) diff --git a/routers/web/user/home.go b/routers/web/user/home.go index 1305b0095ad..b9f5d044fa5 100644 --- a/routers/web/user/home.go +++ b/routers/web/user/home.go @@ -887,5 +887,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 080ec4b5822..08cd1b8b310 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 642a7f33b00..3210d033d3c 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 f8fcbf65650..17c4783c694 100644 --- a/routers/web/user/profile.go +++ b/routers/web/user/profile.go @@ -364,6 +364,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/routers/web/web.go b/routers/web/web.go index 69f737c3e1f..a20bf484b3d 100644 --- a/routers/web/web.go +++ b/routers/web/web.go @@ -895,21 +895,23 @@ func RegisterRoutes(m *web.Route) { }, reqRepoProjectsReader, repo.MustEnableProjects) m.Group("/wiki", func() { - m.Get("/", repo.Wiki) - m.Get("/{page}", repo.Wiki) - m.Get("/_pages", repo.WikiPages) - m.Get("/{page}/_revision", repo.WikiRevision) + m.Combo("/"). + Get(repo.Wiki). + Post(context.RepoMustNotBeArchived(), + reqSignIn, + reqRepoWikiWriter, + bindIgnErr(forms.NewWikiForm{}), + repo.WikiPost) + m.Combo("/*"). + Get(repo.Wiki). + Post(context.RepoMustNotBeArchived(), + reqSignIn, + reqRepoWikiWriter, + bindIgnErr(forms.NewWikiForm{}), + repo.WikiPost) m.Get("/commit/{sha:[a-f0-9]{7,40}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff) m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) - - m.Group("", func() { - m.Combo("/_new").Get(repo.NewWiki). - Post(bindIgnErr(forms.NewWikiForm{}), repo.NewWikiPost) - m.Combo("/{page}/_edit").Get(repo.EditWiki). - Post(bindIgnErr(forms.NewWikiForm{}), repo.EditWikiPost) - m.Post("/{page}/delete", repo.DeleteWikiPagePost) - }, context.RepoMustNotBeArchived(), reqSignIn, reqRepoWikiWriter) - }, repo.MustEnableWiki, context.RepoRef(), func(ctx *context.Context) { + }, repo.MustEnableWiki, func(ctx *context.Context) { ctx.Data["PageIsWiki"] = true }) diff --git a/services/lfs/server.go b/services/lfs/server.go index 5ce2a5498a4..78876588165 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" @@ -46,17 +47,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 88e4078922b..a949b073a52 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 3de50a8a2ff..587d2098eba 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 777ae086b5e..32a79c0783f 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 08adaef6fd0..4fd78ff5bb0 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 webhook_model.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 *webhook_model.Webhook, t *webhook_model.HookTask) ( 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 2b88bb23ff4..ae5af8d9b6f 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/services/wiki/wiki.go b/services/wiki/wiki.go index 944099de1fd..9d57ac432f4 100644 --- a/services/wiki/wiki.go +++ b/services/wiki/wiki.go @@ -36,7 +36,7 @@ func nameAllowed(name string) error { // NameToSubURL converts a wiki name to its corresponding sub-URL. func NameToSubURL(name string) string { - return url.QueryEscape(strings.ReplaceAll(name, " ", "-")) + return url.PathEscape(strings.ReplaceAll(name, " ", "-")) } // NormalizeWikiName normalizes a wiki name diff --git a/templates/admin/emails/list.tmpl b/templates/admin/emails/list.tmpl index 2d489a495d1..e73213c1dfa 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 e96d9ebb33d..4059cb5debf 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 ceab7a9b1b0..93e6f38c270 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 bf1fcd24bcb..d529e6bfda7 100644 --- a/templates/base/head.tmpl +++ b/templates/base/head.tmpl @@ -102,10 +102,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 348e7671a57..57ddbf732a2 100644 --- a/templates/base/head_navbar.tmpl +++ b/templates/base/head_navbar.tmpl @@ -63,8 +63,7 @@ {{else if .IsSigned}}