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 <art27@cantab.net>

Signed-off-by: Andrew Thornton <art27@cantab.net>
tokarchuk/v1.17
zeripath 3 years ago committed by GitHub
parent 7e1ae38097
commit bbffcc3aec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      integrations/issue_test.go
  2. 2
      integrations/links_test.go
  3. 32
      integrations/nonascii_branches_test.go
  4. 7
      models/action.go
  5. 3
      models/attachment.go
  6. 8
      models/avatars/avatar.go
  7. 4
      models/commit_status.go
  8. 11
      models/issue.go
  9. 3
      models/notification.go
  10. 11
      models/release.go
  11. 21
      models/repo.go
  12. 3
      models/repo_avatar.go
  13. 7
      models/user.go
  14. 16
      modules/context/context.go
  15. 38
      modules/context/repo.go
  16. 11
      modules/convert/git_commit.go
  17. 3
      modules/convert/issue.go
  18. 4
      modules/convert/notification.go
  19. 4
      modules/git/utils.go
  20. 3
      modules/repofiles/action.go
  21. 4
      modules/repofiles/blob.go
  22. 8
      modules/repofiles/file.go
  23. 3
      modules/repofiles/tree.go
  24. 3
      modules/repository/commits.go
  25. 21
      modules/templates/helper.go
  26. 3
      modules/upload/upload.go
  27. 38
      options/locale/locale_en-US.ini
  28. 3
      routers/api/v1/org/member.go
  29. 6
      routers/api/v1/repo/git_ref.go
  30. 11
      routers/api/v1/repo/key.go
  31. 6
      routers/web/admin/auths.go
  32. 4
      routers/web/admin/repos.go
  33. 10
      routers/web/admin/users.go
  34. 134
      routers/web/feed/convert.go
  35. 3
      routers/web/org/setting.go
  36. 19
      routers/web/org/teams.go
  37. 10
      routers/web/repo/blame.go
  38. 4
      routers/web/repo/commit.go
  39. 33
      routers/web/repo/compare.go
  40. 12
      routers/web/repo/editor.go
  41. 25
      routers/web/repo/issue.go
  42. 2
      routers/web/repo/issue_stopwatch.go
  43. 3
      routers/web/repo/lfs.go
  44. 3
      routers/web/repo/migrate.go
  45. 3
      routers/web/repo/milestone.go
  46. 3
      routers/web/repo/projects.go
  47. 72
      routers/web/repo/pull.go
  48. 3
      routers/web/repo/release.go
  49. 4
      routers/web/repo/repo.go
  50. 16
      routers/web/repo/setting.go
  51. 7
      routers/web/repo/setting_protected_branch.go
  52. 2
      routers/web/repo/tag.go
  53. 16
      routers/web/repo/view.go
  54. 11
      routers/web/repo/webhook.go
  55. 65
      routers/web/repo/wiki.go
  56. 22
      routers/web/repo/wiki_test.go
  57. 2
      routers/web/user/home.go
  58. 5
      routers/web/user/notification.go
  59. 2
      routers/web/user/oauth.go
  60. 2
      routers/web/user/profile.go
  61. 28
      routers/web/web.go
  62. 7
      services/lfs/server.go
  63. 7
      services/webhook/dingtalk.go
  64. 7
      services/webhook/discord.go
  65. 14
      services/webhook/general.go
  66. 12
      services/webhook/matrix.go
  67. 7
      services/webhook/msteams.go
  68. 2
      services/wiki/wiki.go
  69. 2
      templates/admin/emails/list.tmpl
  70. 4
      templates/admin/repo/list.tmpl
  71. 2
      templates/admin/user/list.tmpl
  72. 4
      templates/base/head.tmpl
  73. 13
      templates/base/head_navbar.tmpl
  74. 6
      templates/explore/code.tmpl
  75. 2
      templates/mail/auth/activate.tmpl
  76. 2
      templates/mail/auth/activate_email.tmpl
  77. 2
      templates/mail/auth/register_notify.tmpl
  78. 2
      templates/mail/auth/reset_passwd.tmpl
  79. 4
      templates/mail/issue/assigned.tmpl
  80. 28
      templates/mail/issue/default.tmpl
  81. 2
      templates/mail/notify/repo_transfer.tmpl
  82. 10
      templates/mail/release.tmpl
  83. 6
      templates/org/home.tmpl
  84. 4
      templates/org/team/members.tmpl
  85. 4
      templates/org/team/navbar.tmpl
  86. 4
      templates/org/team/new.tmpl
  87. 12
      templates/org/team/repositories.tmpl
  88. 6
      templates/org/team/sidebar.tmpl
  89. 6
      templates/org/team/teams.tmpl
  90. 2
      templates/repo/activity.tmpl
  91. 8
      templates/repo/blame.tmpl
  92. 30
      templates/repo/branch/list.tmpl
  93. 8
      templates/repo/branch_dropdown.tmpl
  94. 6
      templates/repo/commit_page.tmpl
  95. 8
      templates/repo/commits_list.tmpl
  96. 6
      templates/repo/commits_list_small.tmpl
  97. 6
      templates/repo/commits_table.tmpl
  98. 2
      templates/repo/create.tmpl
  99. 12
      templates/repo/diff/blob_excerpt.tmpl
  100. 4
      templates/repo/diff/box.tmpl
  101. Some files were not shown because too many files have changed in this diff Show More

@ -65,7 +65,7 @@ func TestViewIssuesSortByType(t *testing.T) {
repo := unittest.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository) repo := unittest.AssertExistsAndLoadBean(t, &models.Repository{ID: 1}).(*models.Repository)
session := loginUser(t, user.Name) 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) resp := session.MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)
@ -97,7 +97,7 @@ func TestViewIssuesKeyword(t *testing.T) {
issues.UpdateIssueIndexer(issue) issues.UpdateIssueIndexer(issue)
time.Sleep(time.Second * 1) time.Sleep(time.Second * 1)
const keyword = "first" 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) resp := MakeRequest(t, req, http.StatusOK)
htmlDoc := NewHTMLParser(t, resp.Body) htmlDoc := NewHTMLParser(t, resp.Body)

@ -156,7 +156,7 @@ func testLinksAsUser(userName string, t *testing.T) {
"/releases", "/releases",
"/releases/new", "/releases/new",
//"/wiki/_pages", //"/wiki/_pages",
"/wiki/_new", "/wiki/?action=_new",
} }
for _, repo := range apiRepos { for _, repo := range apiRepos {

@ -63,17 +63,17 @@ func TestNonasciiBranches(t *testing.T) {
}, },
{ {
from: "ГлавнаяВетка", 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, status: http.StatusOK,
}, },
{ {
from: "а/б/в", from: "а/б/в",
to: "branch/%d0%b0/%d0%b1/%d0%b2", to: "branch/%D0%B0/%D0%B1/%D0%B2",
status: http.StatusOK, status: http.StatusOK,
}, },
{ {
from: "Grüßen/README.md", 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, status: http.StatusOK,
}, },
{ {
@ -83,7 +83,7 @@ func TestNonasciiBranches(t *testing.T) {
}, },
{ {
from: "Plus+Is+Not+Space/Файл.md", 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, status: http.StatusOK,
}, },
{ {
@ -93,28 +93,28 @@ func TestNonasciiBranches(t *testing.T) {
}, },
{ {
from: "ブランチ", 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, status: http.StatusOK,
}, },
// Tags // Tags
{ {
from: "Тэг", from: "Тэг",
to: "tag/%d0%a2%d1%8d%d0%b3", to: "tag/%D0%A2%D1%8D%D0%B3",
status: http.StatusOK, status: http.StatusOK,
}, },
{ {
from: "Ё/人", from: "Ё/人",
to: "tag/%d0%81/%e4%ba%ba", to: "tag/%D0%81/%E4%BA%BA",
status: http.StatusOK, status: http.StatusOK,
}, },
{ {
from: "タグ", from: "タグ",
to: "tag/%e3%82%bf%e3%82%b0", to: "tag/%E3%82%BF%E3%82%B0",
status: http.StatusOK, status: http.StatusOK,
}, },
{ {
from: "タグ/ファイル.md", 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, status: http.StatusOK,
}, },
// Files // Files
@ -125,38 +125,38 @@ func TestNonasciiBranches(t *testing.T) {
}, },
{ {
from: "Файл.md", 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, status: http.StatusOK,
}, },
{ {
from: "ファイル.md", 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 status: http.StatusNotFound, // it's not on default branch
}, },
// Same but url-encoded (few tests) // Same but url-encoded (few tests)
{ {
from: "%E3%83%96%E3%83%A9%E3%83%B3%E3%83%81", 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, status: http.StatusOK,
}, },
{ {
from: "%E3%82%BF%E3%82%b0", 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, status: http.StatusOK,
}, },
{ {
from: "%D0%A4%D0%B0%D0%B9%D0%BB.md", 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, status: http.StatusOK,
}, },
{ {
from: "%D0%81%2F%E4%BA%BA", from: "%D0%81%2F%E4%BA%BA",
to: "tag/%d0%81/%e4%ba%ba", to: "tag/%D0%81/%E4%BA%BA",
status: http.StatusOK, status: http.StatusOK,
}, },
{ {
from: "Ё%2F%E4%BA%BA", from: "Ё%2F%E4%BA%BA",
to: "tag/%d0%81/%e4%ba%ba", to: "tag/%D0%81/%E4%BA%BA",
status: http.StatusOK, status: http.StatusOK,
}, },
} }

@ -7,6 +7,7 @@ package models
import ( import (
"fmt" "fmt"
"net/url"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -185,10 +186,8 @@ func (a *Action) ShortRepoPath() string {
// GetRepoLink returns relative link to action repository. // GetRepoLink returns relative link to action repository.
func (a *Action) GetRepoLink() string { func (a *Action) GetRepoLink() string {
if len(setting.AppSubURL) > 0 { // path.Join will skip empty strings
return path.Join(setting.AppSubURL, a.GetRepoPath()) return path.Join(setting.AppSubURL, "/", url.PathEscape(a.GetRepoUserName()), url.PathEscape(a.GetRepoName()))
}
return "/" + a.GetRepoPath()
} }
// GetRepositoryFromMatch returns a *Repository from a username and repo strings // GetRepositoryFromMatch returns a *Repository from a username and repo strings

@ -7,6 +7,7 @@ package models
import ( import (
"context" "context"
"fmt" "fmt"
"net/url"
"path" "path"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -59,7 +60,7 @@ func (a *Attachment) RelativePath() string {
// DownloadURL returns the download url of the attached file // DownloadURL returns the download url of the attached file
func (a *Attachment) DownloadURL() string { 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 // LinkedRepository returns the linked repo if any

@ -112,15 +112,15 @@ func GenerateUserAvatarFastLink(userName string, size int) string {
if size < 0 { if size < 0 {
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}" // GenerateUserAvatarImageLink returns a link for `User.Avatar` image file: "/avatars/${User.Avatar}"
func GenerateUserAvatarImageLink(userAvatar string, size int) string { func GenerateUserAvatarImageLink(userAvatar string, size int) string {
if size > 0 { 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 // 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) return generateRecognizedAvatarURL(*avatarURL, size)
} }
// for non-final link, we should return fast (use a 302 redirection link) // 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 { if size > 0 {
urlStr += "?size=" + strconv.Itoa(size) urlStr += "?size=" + strconv.Itoa(size)
} }

@ -7,6 +7,7 @@ package models
import ( import (
"crypto/sha1" "crypto/sha1"
"fmt" "fmt"
"net/url"
"strings" "strings"
"time" "time"
@ -137,8 +138,7 @@ func (status *CommitStatus) loadAttributes(e db.Engine) (err error) {
// APIURL returns the absolute APIURL to this commit-status. // APIURL returns the absolute APIURL to this commit-status.
func (status *CommitStatus) APIURL() string { func (status *CommitStatus) APIURL() string {
_ = status.loadAttributes(db.GetEngine(db.DefaultContext)) _ = status.loadAttributes(db.GetEngine(db.DefaultContext))
return fmt.Sprintf("%sapi/v1/repos/%s/statuses/%s", return status.Repo.APIURL() + "/statuses/" + url.PathEscape(status.SHA)
setting.AppURL, status.Repo.FullName(), status.SHA)
} }
// CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc // CalcCommitStatus returns commit status state via some status, the commit statues should order by id desc

@ -372,6 +372,17 @@ func (issue *Issue) HTMLURL() string {
return fmt.Sprintf("%s/%s/%d", issue.Repo.HTMLURL(), path, issue.Index) 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 // DiffURL returns the absolute URL to this diff
func (issue *Issue) DiffURL() string { func (issue *Issue) DiffURL() string {
if issue.IsPull { if issue.IsPull {

@ -6,6 +6,7 @@ package models
import ( import (
"fmt" "fmt"
"net/url"
"strconv" "strconv"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
@ -475,7 +476,7 @@ func (n *Notification) HTMLURL() string {
} }
return n.Issue.HTMLURL() return n.Issue.HTMLURL()
case NotificationSourceCommit: case NotificationSourceCommit:
return n.Repository.HTMLURL() + "/commit/" + n.CommitID return n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID)
case NotificationSourceRepository: case NotificationSourceRepository:
return n.Repository.HTMLURL() return n.Repository.HTMLURL()
} }

@ -10,10 +10,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"sort" "sort"
"strconv"
"strings" "strings"
"code.gitea.io/gitea/models/db" "code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/structs" "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util" "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 // APIURL the api url for a release. release must have attributes loaded
func (r *Release) APIURL() string { func (r *Release) APIURL() string {
return fmt.Sprintf("%sapi/v1/repos/%s/releases/%d", return r.Repo.APIURL() + "/releases/" + strconv.FormatInt(r.ID, 10)
setting.AppURL, r.Repo.FullName(), r.ID)
} }
// ZipURL the zip url for a release. release must have attributes loaded // ZipURL the zip url for a release. release must have attributes loaded
func (r *Release) ZipURL() string { 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 // TarURL the tar.gz url for a release. release must have attributes loaded
func (r *Release) TarURL() string { 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 // HTMLURL the url for a release on the web UI. release must have attributes loaded
func (r *Release) HTMLURL() string { 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. // IsReleaseExist returns true if release with given tag name already exists.

@ -314,7 +314,7 @@ func (repo *Repository) FullName() string {
// HTMLURL returns the repository HTML URL // HTMLURL returns the repository HTML URL
func (repo *Repository) HTMLURL() string { 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 // CommitLink make link to by commit full ID
@ -323,14 +323,14 @@ func (repo *Repository) CommitLink(commitID string) (result string) {
if commitID == "" || commitID == "0000000000000000000000000000000000000000" { if commitID == "" || commitID == "0000000000000000000000000000000000000000" {
result = "" result = ""
} else { } else {
result = repo.HTMLURL() + "/commit/" + commitID result = repo.HTMLURL() + "/commit/" + url.PathEscape(commitID)
} }
return return
} }
// APIURL returns the repository API URL // APIURL returns the repository API URL
func (repo *Repository) APIURL() string { 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. // GetCommitsCountCacheKey returns cache key used for commits count caching.
@ -709,19 +709,14 @@ func (repo *Repository) GitConfigPath() string {
return GitConfigPath(repo.RepoPath()) return GitConfigPath(repo.RepoPath())
} }
// RelLink returns the repository relative link
func (repo *Repository) RelLink() string {
return "/" + repo.FullName()
}
// Link returns the repository link // Link returns the repository link
func (repo *Repository) Link() string { 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 // ComposeCompareURL returns the repository comparison URL
func (repo *Repository) ComposeCompareURL(oldCommitID, newCommitID string) string { 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 // UpdateDefaultBranch updates the default branch
@ -930,11 +925,11 @@ func (repo *Repository) cloneLink(isWiki bool) *CloneLink {
} }
if setting.SSH.Port != 22 { 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 { } 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 { } 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) cl.HTTPS = ComposeHTTPSCloneURL(repo.OwnerName, repoName)
return cl return cl

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"image/png" "image/png"
"io" "io"
"net/url"
"strconv" "strconv"
"strings" "strings"
@ -96,7 +97,7 @@ func (repo *Repository) relAvatarLink(e db.Engine) string {
return "" 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. // AvatarLink returns a link to the repository's avatar.

@ -13,6 +13,7 @@ import (
"errors" "errors"
"fmt" "fmt"
_ "image/jpeg" // Needed for jpeg support _ "image/jpeg" // Needed for jpeg support
"net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
@ -315,17 +316,17 @@ func (u *User) DashboardLink() string {
// HomeLink returns the user or organization home page link. // HomeLink returns the user or organization home page link.
func (u *User) HomeLink() string { 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. // HTMLURL returns the user or organization's full link.
func (u *User) HTMLURL() string { func (u *User) HTMLURL() string {
return setting.AppURL + u.Name return setting.AppURL + url.PathEscape(u.Name)
} }
// OrganisationLink returns the organization sub page link. // OrganisationLink returns the organization sub page link.
func (u *User) OrganisationLink() string { 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. // GenerateEmailActivateCode generates an activate code based on user information and given e-mail.

@ -70,6 +70,16 @@ type Context struct {
Org *Organization 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 // GetData returns the data
func (ctx *Context) GetData() map[string]interface{} { func (ctx *Context) GetData() map[string]interface{} {
return ctx.Data return ctx.Data
@ -120,9 +130,9 @@ func RedirectToUser(ctx *Context, userName string, redirectUserID int64) {
} }
redirectPath := strings.Replace( redirectPath := strings.Replace(
ctx.Req.URL.Path, ctx.Req.URL.EscapedPath(),
userName, url.PathEscape(userName),
user.Name, url.PathEscape(user.Name),
1, 1,
) )
if ctx.Req.URL.RawQuery != "" { if ctx.Req.URL.RawQuery != "" {

@ -41,10 +41,10 @@ var IssueTemplateDirCandidates = []string{
// PullRequest contains information to make a pull request // PullRequest contains information to make a pull request
type PullRequest struct { type PullRequest struct {
BaseRepo *models.Repository BaseRepo *models.Repository
Allowed bool Allowed bool
SameRepo bool SameRepo bool
HeadInfo string // [<user>:]<branch> HeadInfoSubURL string // [<user>:]<branch> url segment
} }
// Repository contains information to operate a repository // 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 { func (r *Repository) BranchNameSubURL() string {
switch { switch {
case r.IsViewBranch: case r.IsViewBranch:
return "branch/" + r.BranchName return "branch/" + util.PathEscapeSegments(r.BranchName)
case r.IsViewTag: case r.IsViewTag:
return "tag/" + r.BranchName return "tag/" + util.PathEscapeSegments(r.BranchName)
case r.IsViewCommit: case r.IsViewCommit:
return "commit/" + r.BranchName return "commit/" + util.PathEscapeSegments(r.BranchName)
} }
log.Error("Unknown view type for repo: %v", r) log.Error("Unknown view type for repo: %v", r)
return "" return ""
@ -321,9 +321,9 @@ func RedirectToRepo(ctx *Context, redirectRepoID int64) {
} }
redirectPath := strings.Replace( redirectPath := strings.Replace(
ctx.Req.URL.Path, ctx.Req.URL.EscapedPath(),
fmt.Sprintf("%s/%s", ownerName, previousRepoName), url.PathEscape(ownerName)+"/"+url.PathEscape(previousRepoName),
repo.FullName(), url.PathEscape(repo.OwnerName)+"/"+url.PathEscape(repo.Name),
1, 1,
) )
if ctx.Req.URL.RawQuery != "" { if ctx.Req.URL.RawQuery != "" {
@ -588,7 +588,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
ctx.Data["BaseRepo"] = repo.BaseRepo ctx.Data["BaseRepo"] = repo.BaseRepo
ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo ctx.Repo.PullRequest.BaseRepo = repo.BaseRepo
ctx.Repo.PullRequest.Allowed = canPush 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() { } else if repo.AllowsPulls() {
// Or, this is repository accepts pull requests between branches. // Or, this is repository accepts pull requests between branches.
canCompare = true canCompare = true
@ -596,7 +596,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
ctx.Repo.PullRequest.BaseRepo = repo ctx.Repo.PullRequest.BaseRepo = repo
ctx.Repo.PullRequest.Allowed = canPush ctx.Repo.PullRequest.Allowed = canPush
ctx.Repo.PullRequest.SameRepo = true 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["CanCompareOrPull"] = canCompare
ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest ctx.Data["PullRequestCtx"] = ctx.Repo.PullRequest
@ -621,7 +621,7 @@ func RepoAssignment(ctx *Context) (cancel context.CancelFunc) {
if ctx.FormString("go-get") == "1" { if ctx.FormString("go-get") == "1" {
ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name) 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["GoDocDirectory"] = prefix + "{/dir}"
ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}" ctx.Data["GoDocFile"] = prefix + "{/dir}/{file}#L{line}"
} }
@ -810,7 +810,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if isRenamedBranch && has { if isRenamedBranch && has {
renamedBranchName := ctx.Data["RenamedBranchName"].(string) renamedBranchName := ctx.Data["RenamedBranchName"].(string)
ctx.Flash.Info(ctx.Tr("repo.branch.renamed", refName, renamedBranchName)) 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) ctx.Redirect(link)
return return
} }
@ -845,7 +845,7 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
// If short commit ID add canonical link header // If short commit ID add canonical link header
if len(refName) < 40 { if len(refName) < 40 {
ctx.Header().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"", 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 { } else {
if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] { if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] {
@ -857,11 +857,13 @@ func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context
if refType == RepoRefLegacy { if refType == RepoRefLegacy {
// redirect from old URL scheme to new URL scheme // 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( ctx.Redirect(path.Join(
setting.AppSubURL, ctx.Repo.RepoLink,
strings.TrimSuffix(ctx.Req.URL.Path, ctx.Params("*")), util.PathEscapeSegments(prefix),
ctx.Repo.BranchNameSubURL(), ctx.Repo.BranchNameSubURL(),
ctx.Repo.TreePath)) util.PathEscapeSegments(ctx.Repo.TreePath)))
return return
} }
} }

@ -5,6 +5,7 @@
package convert package convert
import ( import (
"net/url"
"time" "time"
"code.gitea.io/gitea/models" "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++ { for i := 0; i < commit.ParentCount(); i++ {
sha, _ := commit.ParentID(i) sha, _ := commit.ParentID(i)
apiParents[i] = &api.CommitMeta{ apiParents[i] = &api.CommitMeta{
URL: repo.APIURL() + "/git/commits/" + sha.String(), URL: repo.APIURL() + "/git/commits/" + url.PathEscape(sha.String()),
SHA: sha.String(), SHA: sha.String(),
} }
} }
@ -147,13 +148,13 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string]
return &api.Commit{ return &api.Commit{
CommitMeta: &api.CommitMeta{ 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(), SHA: commit.ID.String(),
Created: commit.Committer.When, Created: commit.Committer.When,
}, },
HTMLURL: repo.HTMLURL() + "/commit/" + commit.ID.String(), HTMLURL: repo.HTMLURL() + "/commit/" + url.PathEscape(commit.ID.String()),
RepoCommit: &api.RepoCommit{ RepoCommit: &api.RepoCommit{
URL: repo.APIURL() + "/git/commits/" + commit.ID.String(), URL: repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()),
Author: &api.CommitUser{ Author: &api.CommitUser{
Identity: api.Identity{ Identity: api.Identity{
Name: commit.Author.Name, Name: commit.Author.Name,
@ -170,7 +171,7 @@ func ToCommit(repo *models.Repository, commit *git.Commit, userCache map[string]
}, },
Message: commit.Message(), Message: commit.Message(),
Tree: &api.CommitMeta{ 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(), SHA: commit.ID.String(),
Created: commit.Committer.When, Created: commit.Committer.When,
}, },

@ -6,6 +6,7 @@ package convert
import ( import (
"fmt" "fmt"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -191,7 +192,7 @@ func ToLabel(label *models.Label, repo *models.Repository, org *models.User) *ap
} }
} else { // BelongsToOrg } else { // BelongsToOrg
if org != nil { 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 { } else {
log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID) log.Error("ToLabel did not get org to calculate url for label with id '%d'", label.ID)
} }

@ -5,6 +5,8 @@
package convert package convert
import ( import (
"net/url"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
) )
@ -58,7 +60,7 @@ func ToNotificationThread(n *models.Notification) *api.NotificationThread {
} }
} }
case models.NotificationSourceCommit: case models.NotificationSourceCommit:
url := n.Repository.HTMLURL() + "/commit/" + n.CommitID url := n.Repository.HTMLURL() + "/commit/" + url.PathEscape(n.CommitID)
result.Subject = &api.NotificationSubject{ result.Subject = &api.NotificationSubject{
Type: api.NotifySubjectCommit, Type: api.NotifySubjectCommit,
Title: n.CommitID, Title: n.CommitID,

@ -11,6 +11,8 @@ import (
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
"code.gitea.io/gitea/modules/util"
) )
// ObjectCache provides thread-safe cache operations. // 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 // RefURL returns the absolute URL for a ref in a repository
func RefURL(repoURL, ref string) string { func RefURL(repoURL, ref string) string {
refName := RefEndName(ref) refName := util.PathEscapeSegments(RefEndName(ref))
switch { switch {
case strings.HasPrefix(ref, BranchPrefix): case strings.HasPrefix(ref, BranchPrefix):
return repoURL + "/src/branch/" + refName return repoURL + "/src/branch/" + refName

@ -7,6 +7,7 @@ package repofiles
import ( import (
"fmt" "fmt"
"html" "html"
"net/url"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
@ -175,7 +176,7 @@ func UpdateIssuesCommit(doer *models.User, repo *models.Repository, commits []*r
continue continue
} }
message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, repo.Link(), c.Sha1, html.EscapeString(strings.SplitN(c.Message, "\n", 2)[0])) message := fmt.Sprintf(`<a href="%s/commit/%s">%s</a>`, 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 { if err = models.CreateRefComment(doer, refRepo, refIssue, message, c.Sha1); err != nil {
return err return err
} }

@ -5,6 +5,8 @@
package repofiles package repofiles
import ( import (
"net/url"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
@ -31,7 +33,7 @@ func GetBlobBySHA(repo *models.Repository, sha string) (*api.GitBlobResponse, er
} }
return &api.GitBlobResponse{ return &api.GitBlobResponse{
SHA: gitBlob.ID.String(), 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(), Size: gitBlob.Size(),
Encoding: "base64", Encoding: "base64",
Content: content, Content: content,

@ -36,19 +36,19 @@ func GetFileCommitResponse(repo *models.Repository, commit *git.Commit) (*api.Fi
if commit == nil { if commit == nil {
return nil, fmt.Errorf("commit cannot be nil") return nil, fmt.Errorf("commit cannot be nil")
} }
commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + commit.ID.String()) commitURL, _ := url.Parse(repo.APIURL() + "/git/commits/" + url.PathEscape(commit.ID.String()))
commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + commit.Tree.ID.String()) commitTreeURL, _ := url.Parse(repo.APIURL() + "/git/trees/" + url.PathEscape(commit.Tree.ID.String()))
parents := make([]*api.CommitMeta, commit.ParentCount()) parents := make([]*api.CommitMeta, commit.ParentCount())
for i := 0; i <= commit.ParentCount(); i++ { for i := 0; i <= commit.ParentCount(); i++ {
if parent, err := commit.Parent(i); err == nil && parent != nil { 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{ parents[i] = &api.CommitMeta{
SHA: parent.ID.String(), SHA: parent.ID.String(),
URL: parentCommitURL.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{ fileCommit := &api.FileCommitResponse{
CommitMeta: api.CommitMeta{ CommitMeta: api.CommitMeta{
SHA: commit.ID.String(), SHA: commit.ID.String(),

@ -6,6 +6,7 @@ package repofiles
import ( import (
"fmt" "fmt"
"net/url"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/git" "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 := new(api.GitTreeResponse)
tree.SHA = gitTree.ResolvedID.String() 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 var entries git.Entries
if recursive { if recursive {
entries, err = gitTree.ListEntriesRecursive() entries, err = gitTree.ListEntriesRecursive()

@ -6,6 +6,7 @@ package repository
import ( import (
"fmt" "fmt"
"net/url"
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -81,7 +82,7 @@ func (pc *PushCommits) toAPIPayloadCommit(repoPath, repoLink string, commit *Pus
return &api.PayloadCommit{ return &api.PayloadCommit{
ID: commit.Sha1, ID: commit.Sha1,
Message: commit.Message, 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{ Author: &api.PayloadUser{
Name: commit.AuthorName, Name: commit.AuthorName,
Email: commit.AuthorEmail, Email: commit.AuthorEmail,

@ -139,17 +139,14 @@ func NewFuncMap() []template.FuncMap {
} }
return str[start:end] return str[start:end]
}, },
"EllipsisString": base.EllipsisString, "EllipsisString": base.EllipsisString,
"DiffTypeToStr": DiffTypeToStr, "DiffTypeToStr": DiffTypeToStr,
"DiffLineTypeToStr": DiffLineTypeToStr, "DiffLineTypeToStr": DiffLineTypeToStr,
"Sha1": Sha1, "Sha1": Sha1,
"ShortSha": base.ShortSha, "ShortSha": base.ShortSha,
"MD5": base.EncodeMD5, "MD5": base.EncodeMD5,
"ActionContent2Commits": ActionContent2Commits, "ActionContent2Commits": ActionContent2Commits,
"PathEscape": url.PathEscape, "PathEscape": url.PathEscape,
"EscapePound": func(str string) string {
return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
},
"PathEscapeSegments": util.PathEscapeSegments, "PathEscapeSegments": util.PathEscapeSegments,
"URLJoin": util.URLJoin, "URLJoin": util.URLJoin,
"RenderCommitMessage": RenderCommitMessage, "RenderCommitMessage": RenderCommitMessage,
@ -742,7 +739,7 @@ func ReactionToEmoji(reaction string) template.HTML {
if val != nil { if val != nil {
return template.HTML(val.Emoji) return template.HTML(val.Emoji)
} }
return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, reaction)) return template.HTML(fmt.Sprintf(`<img alt=":%s:" src="%s/assets/img/emoji/%s.png"></img>`, reaction, setting.StaticURLPrefix, url.PathEscape(reaction)))
} }
// RenderNote renders the contents of a git-notes file as a commit message. // RenderNote renders the contents of a git-notes file as a commit message.

@ -6,6 +6,7 @@ package upload
import ( import (
"net/http" "net/http"
"net/url"
"path" "path"
"regexp" "regexp"
"strings" "strings"
@ -83,7 +84,7 @@ func AddUploadContext(ctx *context.Context, uploadType string) {
ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments" ctx.Data["UploadUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove" ctx.Data["UploadRemoveUrl"] = ctx.Repo.RepoLink + "/issues/attachments/remove"
if len(ctx.Params(":index")) > 0 { 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 { } else {
ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments" ctx.Data["UploadLinkUrl"] = ctx.Repo.RepoLink + "/issues/attachments"
} }

@ -1409,7 +1409,7 @@ pulls.filter_branch = Filter branch
pulls.no_results = No results found. 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 = 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.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: <a href="%[1]s/pulls/%[3]d">%[2]s#%[3]d</a>` pulls.has_pull_request = `A pull request between these branches already exists: <a href="%[1]s">%[2]s#%[3]d</a>`
pulls.create = Create Pull Request pulls.create = Create Pull Request
pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code id="branch_target">%[3]s</code> pulls.title_desc = wants to merge %[1]d commits from <code>%[2]s</code> into <code id="branch_target">%[3]s</code>
pulls.merged_title_desc = merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s pulls.merged_title_desc = merged %[1]d commits from <code>%[2]s</code> into <code>%[3]s</code> %[4]s
@ -2761,32 +2761,32 @@ notices.delete_success = The system notices have been deleted.
[action] [action]
create_repo = created repository <a href="%s">%s</a> create_repo = created repository <a href="%s">%s</a>
rename_repo = renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a> rename_repo = renamed repository from <code>%[1]s</code> to <a href="%[2]s">%[3]s</a>
commit_repo = pushed to <a href="%[1]s/src/branch/%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a> commit_repo = pushed to <a href="%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a>
create_issue = `opened issue <a href="%s/issues/%s">%s#%[2]s</a>` create_issue = `opened issue <a href="%[1]s">%[3]s#%[2]s</a>`
close_issue = `closed issue <a href="%s/issues/%s">%s#%[2]s</a>` close_issue = `closed issue <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_issue = `reopened issue <a href="%s/issues/%s">%s#%[2]s</a>` reopen_issue = `reopened issue <a href="%[1]s">%[3]s#%[2]s</a>`
create_pull_request = `created pull request <a href="%s/pulls/%s">%s#%[2]s</a>` create_pull_request = `created pull request <a href="%[1]s">%[3]s#%[2]s</a>`
close_pull_request = `closed pull request <a href="%s/pulls/%s">%s#%[2]s</a>` close_pull_request = `closed pull request <a href="%[1]s">%[3]s#%[2]s</a>`
reopen_pull_request = `reopened pull request <a href="%s/pulls/%s">%s#%[2]s</a>` reopen_pull_request = `reopened pull request <a href="%[1]s">%[3]s#%[2]s</a>`
comment_issue = `commented on issue <a href="%s/issues/%s">%s#%[2]s</a>` comment_issue = `commented on issue <a href="%[1]s">%[3]s#%[2]s</a>`
comment_pull = `commented on pull request <a href="%s/pulls/%s">%s#%[2]s</a>` comment_pull = `commented on pull request <a href="%[1]s">%[3]s#%[2]s</a>`
merge_pull_request = `merged pull request <a href="%s/pulls/%s">%s#%[2]s</a>` merge_pull_request = `merged pull request <a href="%[1]s">%[3]s#%[2]s</a>`
transfer_repo = transferred repository <code>%s</code> to <a href="%s">%s</a> transfer_repo = transferred repository <code>%s</code> to <a href="%s">%s</a>
push_tag = pushed tag <a href="%s/src/tag/%s">%[4]s</a> to <a href="%[1]s">%[3]s</a> push_tag = pushed tag <a href="%[2]s">%[3]s</a> to <a href="%[1]s">%[4]s</a>
delete_tag = deleted tag %[2]s from <a href="%[1]s">%[3]s</a> delete_tag = deleted tag %[2]s from <a href="%[1]s">%[3]s</a>
delete_branch = deleted branch %[2]s from <a href="%[1]s">%[3]s</a> delete_branch = deleted branch %[2]s from <a href="%[1]s">%[3]s</a>
compare_branch = Compare compare_branch = Compare
compare_commits = Compare %d commits compare_commits = Compare %d commits
compare_commits_general = Compare commits compare_commits_general = Compare commits
mirror_sync_push = synced commits to <a href="%[1]s/src/%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a> from mirror mirror_sync_push = synced commits to <a href="%[2]s">%[3]s</a> at <a href="%[1]s">%[4]s</a> from mirror
mirror_sync_create = synced new reference <a href="%s/src/%s">%[2]s</a> to <a href="%[1]s">%[3]s</a> from mirror mirror_sync_create = synced new reference <a href="%[2]s">%[3]s</a> to <a href="%[1]s">%[4]s</a> from mirror
mirror_sync_delete = synced and deleted reference <code>%[2]s</code> at <a href="%[1]s">%[3]s</a> from mirror mirror_sync_delete = synced and deleted reference <code>%[2]s</code> at <a href="%[1]s">%[3]s</a> from mirror
approve_pull_request = `approved <a href="%s/pulls/%s">%s#%[2]s</a>` approve_pull_request = `approved <a href="%[1]s">%[3]s#%[2]s</a>`
reject_pull_request = `suggested changes for <a href="%s/pulls/%s">%s#%[2]s</a>` reject_pull_request = `suggested changes for <a href="%[1]s">%[3]s#%[2]s</a>`
publish_release = `released <a href="%s/releases/tag/%s"> "%[4]s" </a> at <a href="%[1]s">%[3]s</a>` publish_release = `released <a href="%[2]s"> "%[4]s" </a> at <a href="%[1]s">%[3]s</a>`
review_dismissed = `dismissed review from <b>%[4]s</b> for <a href="%[1]s/pulls/%[2]s">%[3]s#%[2]s</a>` review_dismissed = `dismissed review from <b>%[4]s</b> for <a href="%[1]s">%[3]s#%[2]s</a>`
review_dismissed_reason = Reason: review_dismissed_reason = Reason:
create_branch = created branch <a href="%[1]s/src/branch/%[2]s">%[3]s</a> in <a href="%[1]s">%[4]s</a> create_branch = created branch <a href="%[2]s">%[3]s</a> in <a href="%[1]s">%[4]s</a>
starred_repo = starred <a href="%[1]s">%[2]s</a> starred_repo = starred <a href="%[1]s">%[2]s</a>
watched_repo = started watching <a href="%[1]s">%[2]s</a> watched_repo = started watching <a href="%[1]s">%[2]s</a>

@ -6,6 +6,7 @@ package org
import ( import (
"net/http" "net/http"
"net/url"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "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) ctx.Redirect(redirectURL, 302)
} }

@ -6,9 +6,11 @@ package repo
import ( import (
"net/http" "net/http"
"net/url"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/v1/utils" "code.gitea.io/gitea/routers/api/v1/utils"
) )
@ -89,11 +91,11 @@ func getGitRefsInternal(ctx *context.APIContext, filter string) {
for i := range refs { for i := range refs {
apiRefs[i] = &api.Reference{ apiRefs[i] = &api.Reference{
Ref: refs[i].Name, 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{ Object: &api.GitObject{
SHA: refs[i].Object.String(), SHA: refs[i].Object.String(),
Type: refs[i].Type, 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()),
}, },
} }
} }

@ -8,6 +8,7 @@ package repo
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
@ -33,8 +34,8 @@ func appendPrivateInformation(apiKey *api.DeployKey, key *models.DeployKey, repo
return apiKey, nil return apiKey, nil
} }
func composeDeployKeysAPILink(repoPath string) string { func composeDeployKeysAPILink(owner, name string) string {
return setting.AppURL + "api/v1/repos/" + repoPath + "/keys/" return setting.AppURL + "api/v1/repos/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/keys/"
} }
// ListDeployKeys list all the deploy keys of a repository // ListDeployKeys list all the deploy keys of a repository
@ -94,7 +95,7 @@ func ListDeployKeys(ctx *context.APIContext) {
return 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)) apiKeys := make([]*api.DeployKey, len(keys))
for i := range keys { for i := range keys {
if err := keys[i].GetContent(); err != nil { if err := keys[i].GetContent(); err != nil {
@ -154,7 +155,7 @@ func GetDeployKey(ctx *context.APIContext) {
return 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) apiKey := convert.ToDeployKey(apiLink, key)
if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) { if ctx.User.IsAdmin || ((ctx.Repo.Repository.ID == key.RepoID) && (ctx.User.ID == ctx.Repo.Owner.ID)) {
apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository) apiKey, _ = appendPrivateInformation(apiKey, key, ctx.Repo.Repository)
@ -233,7 +234,7 @@ func CreateDeployKey(ctx *context.APIContext) {
} }
key.Content = content 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)) ctx.JSON(http.StatusCreated, convert.ToDeployKey(apiLink, key))
} }

@ -8,7 +8,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"regexp" "regexp"
"strconv"
"code.gitea.io/gitea/models/login" "code.gitea.io/gitea/models/login"
"code.gitea.io/gitea/modules/auth/pam" "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) log.Trace("Authentication changed by admin(%s): %d", ctx.User.Name, source.ID)
ctx.Flash.Success(ctx.Tr("admin.auths.update_success")) 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 // 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.Flash.Error(fmt.Sprintf("DeleteLoginSource: %v", err))
} }
ctx.JSON(http.StatusOK, map[string]interface{}{ 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 return
} }

@ -58,7 +58,7 @@ func DeleteRepo(ctx *context.Context) {
ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success")) ctx.Flash.Success(ctx.Tr("repo.settings.deletion_success"))
ctx.JSON(http.StatusOK, map[string]interface{}{ 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.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))
} }

@ -6,8 +6,8 @@
package admin package admin
import ( import (
"fmt"
"net/http" "net/http"
"net/url"
"strconv" "strconv"
"strings" "strings"
@ -188,7 +188,7 @@ func NewUserPost(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("admin.users.new_success", u.Name)) 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 { 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) 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.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 // DeleteUser response for deleting a user
@ -382,12 +382,12 @@ func DeleteUser(ctx *context.Context) {
case models.IsErrUserOwnRepos(err): case models.IsErrUserOwnRepos(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo")) ctx.Flash.Error(ctx.Tr("admin.users.still_own_repo"))
ctx.JSON(http.StatusOK, map[string]interface{}{ 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): case models.IsErrUserHasOrgs(err):
ctx.Flash.Error(ctx.Tr("admin.users.still_has_org")) ctx.Flash.Error(ctx.Tr("admin.users.still_has_org"))
ctx.JSON(http.StatusOK, map[string]interface{}{ 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: default:
ctx.ServerError("DeleteUser", err) ctx.ServerError("DeleteUser", err)

@ -15,10 +15,35 @@ import (
"code.gitea.io/gitea/modules/context" "code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/util"
"github.com/gorilla/feeds" "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 // feedActionsToFeedItems convert gitea's Action feed to feeds Item
func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) { func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (items []*feeds.Item, err error) {
for _, act := range actions { for _, act := range actions {
@ -32,62 +57,111 @@ func feedActionsToFeedItems(ctx *context.Context, actions []*models.Action) (ite
title = act.ActUser.DisplayName() + " " title = act.ActUser.DisplayName() + " "
switch act.OpType { switch act.OpType {
case models.ActionCreateRepo: 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: 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: case models.ActionCommitRepo:
branchLink := act.GetBranch() link.Href = toBranchLink(act)
if len(act.Content) != 0 { 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 { } 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: 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: case models.ActionStarRepo:
title += ctx.Tr("action.starred_repo", act.GetRepoLink(), act.GetRepoPath()) link.Href = act.GetRepoLink()
link = &feeds.Link{Href: act.GetRepoLink()} title += ctx.TrHTMLEscapeArgs("action.starred_repo", act.GetRepoLink(), act.GetRepoPath())
case models.ActionWatchRepo: case models.ActionWatchRepo:
title += ctx.Tr("action.watched_repo", act.GetRepoLink(), act.GetRepoPath()) link.Href = act.GetRepoLink()
link = &feeds.Link{Href: act.GetRepoLink()} title += ctx.TrHTMLEscapeArgs("action.watched_repo", act.GetRepoLink(), act.GetRepoPath())
default: default:
return nil, fmt.Errorf("unknown action type: %v", act.OpType) 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 += "\n\n"
} }
desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s", desc += fmt.Sprintf("<a href=\"%s\">%s</a>\n%s",
fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1), html.EscapeString(fmt.Sprintf("%s/commit/%s", act.GetRepoLink(), commit.Sha1)),
commit.Sha1, commit.Sha1,
templates.RenderCommitMessage(commit.Message, repoLink, nil), templates.RenderCommitMessage(commit.Message, repoLink, nil),
) )

@ -7,6 +7,7 @@ package org
import ( import (
"net/http" "net/http"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -76,7 +77,7 @@ func SettingsPost(ctx *context.Context) {
return return
} }
// reset ctx.org.OrgLink with new name // 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) log.Trace("Organization name changed: %s -> %s", org.Name, form.Name)
nameChanged = false nameChanged = false
} }

@ -7,6 +7,7 @@ package org
import ( import (
"net/http" "net/http"
"net/url"
"path" "path"
"strings" "strings"
@ -105,7 +106,7 @@ func TeamsAction(ctx *context.Context) {
} }
ctx.JSON(http.StatusOK, ctx.JSON(http.StatusOK,
map[string]interface{}{ map[string]interface{}{
"redirect": ctx.Org.OrgLink + "/teams/" + ctx.Org.Team.LowerName, "redirect": ctx.Org.OrgLink + "/teams/" + url.PathEscape(ctx.Org.Team.LowerName),
}) })
return return
case "add": case "add":
@ -119,7 +120,7 @@ func TeamsAction(ctx *context.Context) {
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Flash.Error(ctx.Tr("form.user_not_exist")) 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 { } else {
ctx.ServerError(" GetUserByName", err) ctx.ServerError(" GetUserByName", err)
} }
@ -128,7 +129,7 @@ func TeamsAction(ctx *context.Context) {
if u.IsOrganization() { if u.IsOrganization() {
ctx.Flash.Error(ctx.Tr("form.cannot_add_org_to_team")) 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 return
} }
@ -156,7 +157,7 @@ func TeamsAction(ctx *context.Context) {
switch page { switch page {
case "team": 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": case "home":
ctx.Redirect(ctx.Org.Organization.HomeLink()) ctx.Redirect(ctx.Org.Organization.HomeLink())
default: default:
@ -181,7 +182,7 @@ func TeamsRepoAction(ctx *context.Context) {
if err != nil { if err != nil {
if models.IsErrRepoNotExist(err) { if models.IsErrRepoNotExist(err) {
ctx.Flash.Error(ctx.Tr("org.teams.add_nonexistent_repo")) 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 return
} }
ctx.ServerError("GetRepositoryByName", err) ctx.ServerError("GetRepositoryByName", err)
@ -204,11 +205,11 @@ func TeamsRepoAction(ctx *context.Context) {
if action == "addall" || action == "removeall" { if action == "addall" || action == "removeall" {
ctx.JSON(http.StatusOK, map[string]interface{}{ 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 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 // NewTeam render create new team page
@ -273,7 +274,7 @@ func NewTeamPost(ctx *context.Context) {
return return
} }
log.Trace("Team created: %s/%s", ctx.Org.Organization.Name, t.Name) 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 // TeamMembers render team members page
@ -375,7 +376,7 @@ func EditTeamPost(ctx *context.Context) {
} }
return 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 // DeleteTeam response for the delete team request

@ -8,6 +8,7 @@ import (
"fmt" "fmt"
gotemplate "html/template" gotemplate "html/template"
"net/http" "net/http"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -17,6 +18,7 @@ import (
"code.gitea.io/gitea/modules/highlight" "code.gitea.io/gitea/modules/highlight"
"code.gitea.io/gitea/modules/templates" "code.gitea.io/gitea/modules/templates"
"code.gitea.io/gitea/modules/timeutil" "code.gitea.io/gitea/modules/timeutil"
"code.gitea.io/gitea/modules/util"
) )
const ( const (
@ -54,7 +56,7 @@ func RefBlame(ctx *context.Context) {
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
if len(ctx.Repo.TreePath) > 0 { if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + ctx.Repo.TreePath treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
} }
var treeNames []string var treeNames []string
@ -85,7 +87,7 @@ func RefBlame(ctx *context.Context) {
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["BranchLink"] = branchLink 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["PageIsViewCode"] = true
ctx.Data["IsBlame"] = true ctx.Data["IsBlame"] = true
@ -236,8 +238,8 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m
br.RepoLink = repoLink br.RepoLink = repoLink
br.PartSha = part.Sha br.PartSha = part.Sha
br.PreviousSha = previousSha br.PreviousSha = previousSha
br.PreviousShaURL = fmt.Sprintf("%s/blame/commit/%s/%s", repoLink, previousSha, ctx.Repo.TreePath) 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, part.Sha) br.CommitURL = fmt.Sprintf("%s/commit/%s", repoLink, url.PathEscape(part.Sha))
br.CommitMessage = commit.CommitMessage br.CommitMessage = commit.CommitMessage
br.CommitSince = commitSince br.CommitSince = commitSince
} }

@ -8,7 +8,6 @@ package repo
import ( import (
"errors" "errors"
"net/http" "net/http"
"path"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -323,8 +322,7 @@ func Diff(ctx *context.Context) {
return return
} }
} }
headTarget := path.Join(userName, repoName) setCompareContext(ctx, parentCommit, commit, userName, repoName)
setCompareContext(ctx, parentCommit, commit, headTarget)
ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID) ctx.Data["Title"] = commit.Summary() + " · " + base.ShortSha(commitID)
ctx.Data["Commit"] = commit ctx.Data["Commit"] = commit
ctx.Data["Diff"] = diff ctx.Data["Diff"] = diff

@ -12,7 +12,7 @@ import (
"html" "html"
"io" "io"
"net/http" "net/http"
"path" "net/url"
"path/filepath" "path/filepath"
"strings" "strings"
@ -38,7 +38,7 @@ const (
) )
// setCompareContext sets context data. // 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["BaseCommit"] = base
ctx.Data["HeadCommit"] = head ctx.Data["HeadCommit"] = head
@ -54,22 +54,28 @@ func setCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit,
return blob return blob
} }
setPathsCompareContext(ctx, base, head, headTarget) setPathsCompareContext(ctx, base, head, headOwner, headName)
setImageCompareContext(ctx) setImageCompareContext(ctx)
setCsvCompareContext(ctx) setCsvCompareContext(ctx)
} }
// setPathsCompareContext sets context data for source and raw paths // SourceCommitURL creates a relative URL for a commit in the given repository
func setPathsCompareContext(ctx *context.Context, base *git.Commit, head *git.Commit, headTarget string) { func SourceCommitURL(owner, name string, commit *git.Commit) string {
sourcePath := setting.AppSubURL + "/%s/src/commit/%s" return setting.AppSubURL + "/" + url.PathEscape(owner) + "/" + url.PathEscape(name) + "/src/commit/" + url.PathEscape(commit.ID.String())
rawPath := setting.AppSubURL + "/%s/raw/commit/%s" }
ctx.Data["SourcePath"] = fmt.Sprintf(sourcePath, headTarget, head.ID) // RawCommitURL creates a relative URL for the raw commit in the given repository
ctx.Data["RawPath"] = fmt.Sprintf(rawPath, headTarget, head.ID) 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 { if base != nil {
baseTarget := path.Join(ctx.Repo.Owner.Name, ctx.Repo.Repository.Name) ctx.Data["BeforeSourcePath"] = SourceCommitURL(headOwner, headName, head)
ctx.Data["BeforeSourcePath"] = fmt.Sprintf(sourcePath, baseTarget, base.ID) ctx.Data["BeforeRawPath"] = RawCommitURL(headOwner, headName, head)
ctx.Data["BeforeRawPath"] = fmt.Sprintf(rawPath, baseTarget, base.ID)
} }
} }
@ -619,8 +625,7 @@ func PrepareCompareDiff(
ctx.Data["Username"] = ci.HeadUser.Name ctx.Data["Username"] = ci.HeadUser.Name
ctx.Data["Reponame"] = ci.HeadRepo.Name ctx.Data["Reponame"] = ci.HeadRepo.Name
headTarget := path.Join(ci.HeadUser.Name, repo.Name) setCompareContext(ctx, baseCommit, headCommit, ci.HeadUser.Name, repo.Name)
setCompareContext(ctx, baseCommit, headCommit, headTarget)
return false return false
} }

@ -204,7 +204,7 @@ func editFilePost(ctx *context.Context, form forms.EditRepoFileForm, isNewFile b
ctx.Data["TreePath"] = form.TreePath ctx.Data["TreePath"] = form.TreePath
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths 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["FileContent"] = form.Content
ctx.Data["commit_summary"] = form.CommitSummary ctx.Data["commit_summary"] = form.CommitSummary
ctx.Data["commit_message"] = form.CommitMessage 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()) ctx.Error(http.StatusInternalServerError, err.Error())
} }
} else if models.IsErrCommitIDDoesNotMatch(err) { } 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) { } 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) { } else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected) errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 { if len(errPushRej.Message) == 0 {
@ -495,7 +495,7 @@ func DeleteFilePost(ctx *context.Context) {
ctx.Error(http.StatusInternalServerError, err.Error()) ctx.Error(http.StatusInternalServerError, err.Error())
} }
} else if models.IsErrCommitIDDoesNotMatch(err) || git.IsErrPushOutOfDate(err) { } 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) { } else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected) errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 { if len(errPushRej.Message) == 0 {
@ -602,7 +602,7 @@ func UploadFilePost(ctx *context.Context) {
ctx.Data["TreePath"] = form.TreePath ctx.Data["TreePath"] = form.TreePath
ctx.Data["TreeNames"] = treeNames ctx.Data["TreeNames"] = treeNames
ctx.Data["TreePaths"] = treePaths 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_summary"] = form.CommitSummary
ctx.Data["commit_message"] = form.CommitMessage ctx.Data["commit_message"] = form.CommitMessage
ctx.Data["commit_choice"] = form.CommitChoice ctx.Data["commit_choice"] = form.CommitChoice
@ -698,7 +698,7 @@ func UploadFilePost(ctx *context.Context) {
branchErr := err.(models.ErrBranchAlreadyExists) branchErr := err.(models.ErrBranchAlreadyExists)
ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form) ctx.RenderWithErr(ctx.Tr("repo.editor.branch_already_exists", branchErr.BranchName), tplUploadFile, &form)
} else if git.IsErrPushOutOfDate(err) { } 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) { } else if git.IsErrPushRejected(err) {
errPushRej := err.(*git.ErrPushRejected) errPushRej := err.(*git.ErrPushRejected)
if len(errPushRej.Message) == 0 { if len(errPushRej.Message) == 0 {

@ -11,6 +11,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -106,7 +107,7 @@ func MustAllowPulls(ctx *context.Context) {
// User can send pull request if owns a forked repository. // User can send pull request if owns a forked repository.
if ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) { if ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID) {
ctx.Repo.PullRequest.Allowed = true 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 { for _, repoLabel := range repoLabels {
if strings.EqualFold(repoLabel.Name, metaLabel) { if strings.EqualFold(repoLabel.Name, metaLabel) {
repoLabel.IsChecked = true repoLabel.IsChecked = true
labelIDs = append(labelIDs, fmt.Sprintf("%d", repoLabel.ID)) labelIDs = append(labelIDs, strconv.FormatInt(repoLabel.ID, 10))
break break
} }
} }
@ -983,6 +984,7 @@ func NewIssuePost(ctx *context.Context) {
issue := &models.Issue{ issue := &models.Issue{
RepoID: repo.ID, RepoID: repo.ID,
Repo: repo,
Title: form.Title, Title: form.Title,
PosterID: ctx.User.ID, PosterID: ctx.User.ID,
Poster: ctx.User, Poster: ctx.User,
@ -1009,9 +1011,9 @@ func NewIssuePost(ctx *context.Context) {
log.Trace("Issue created: %d/%d", repo.ID, issue.ID) log.Trace("Issue created: %d/%d", repo.ID, issue.ID)
if ctx.FormString("redirect_after_creation") == "project" { 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 { } 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 return
} }
if issue.Repo == nil {
issue.Repo = ctx.Repo.Repository
}
// Make sure type and URL matches. // Make sure type and URL matches.
if ctx.Params(":type") == "issues" && issue.IsPull { if ctx.Params(":type") == "issues" && issue.IsPull {
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} else if ctx.Params(":type") == "pulls" && !issue.IsPull { } else if ctx.Params(":type") == "pulls" && !issue.IsPull {
ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} }
@ -1496,7 +1501,7 @@ func ViewIssue(ctx *context.Context) {
log.Error("IsProtectedBranch: %v", err) log.Error("IsProtectedBranch: %v", err)
} else if !protected { } else if !protected {
canDelete = true 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["NumParticipants"] = len(participants)
ctx.Data["Issue"] = issue ctx.Data["Issue"] = issue
ctx.Data["ReadOnly"] = false 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["IsIssuePoster"] = ctx.IsSigned && issue.IsPoster(ctx.User.ID)
ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull) ctx.Data["HasIssuesOrPullsWritePermission"] = ctx.Repo.CanWriteIssuesOrPulls(issue.IsPull)
ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(unit.TypeProjects) ctx.Data["HasProjectsWritePermission"] = ctx.Repo.CanWrite(unit.TypeProjects)
@ -1773,7 +1778,7 @@ func UpdateIssueContent(ctx *context.Context) {
} }
content, err := markdown.RenderString(&markup.RenderContext{ content, err := markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.FormString("context"), URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
Metas: ctx.Repo.Repository.ComposeMetas(), Metas: ctx.Repo.Repository.ComposeMetas(),
GitRepo: ctx.Repo.GitRepo, GitRepo: ctx.Repo.GitRepo,
Ctx: ctx, Ctx: ctx,
@ -2205,7 +2210,7 @@ func UpdateCommentContent(ctx *context.Context) {
} }
content, err := markdown.RenderString(&markup.RenderContext{ content, err := markdown.RenderString(&markup.RenderContext{
URLPrefix: ctx.FormString("context"), URLPrefix: ctx.FormString("context"), // FIXME: <- IS THIS SAFE ?
Metas: ctx.Repo.Repository.ComposeMetas(), Metas: ctx.Repo.Repository.ComposeMetas(),
GitRepo: ctx.Repo.GitRepo, GitRepo: ctx.Repo.GitRepo,
Ctx: ctx, Ctx: ctx,

@ -94,6 +94,7 @@ func GetActiveStopwatch(c *context.Context) {
} }
c.Data["ActiveStopwatch"] = StopwatchTmplInfo{ c.Data["ActiveStopwatch"] = StopwatchTmplInfo{
issue.Link(),
issue.Repo.FullName(), issue.Repo.FullName(),
issue.Index, issue.Index,
sw.Seconds() + 1, // ensure time is never zero in ui 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 // StopwatchTmplInfo is a view on a stopwatch specifically for template rendering
type StopwatchTmplInfo struct { type StopwatchTmplInfo struct {
IssueLink string
RepoSlug string RepoSlug string
IssueIndex int64 IssueIndex int64
Seconds int64 Seconds int64

@ -10,6 +10,7 @@ import (
gotemplate "html/template" gotemplate "html/template"
"io" "io"
"net/http" "net/http"
"net/url"
"path" "path"
"strconv" "strconv"
"strings" "strings"
@ -285,7 +286,7 @@ func LFSFileGet(ctx *context.Context) {
fileSize := meta.Size fileSize := meta.Size
ctx.Data["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 { switch {
case isRepresentableAsText: case isRepresentableAsText:
if st.IsSvgImage() { if st.IsSvgImage() {

@ -7,6 +7,7 @@ package repo
import ( import (
"net/http" "net/http"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -237,7 +238,7 @@ func MigratePost(ctx *context.Context) {
err = task.MigrateRepository(ctx.User, ctxUser, opts) err = task.MigrateRepository(ctx.User, ctxUser, opts)
if err == nil { if err == nil {
ctx.Redirect(ctxUser.HomeLink() + "/" + opts.RepoName) ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(opts.RepoName))
return return
} }

@ -6,6 +6,7 @@ package repo
import ( import (
"net/http" "net/http"
"net/url"
"time" "time"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -244,7 +245,7 @@ func ChangeMilestoneStatus(ctx *context.Context) {
} }
return 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 // DeleteMilestone delete a milestone

@ -7,6 +7,7 @@ package repo
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/models" "code.gitea.io/gitea/models"
@ -173,7 +174,7 @@ func ChangeProjectStatus(ctx *context.Context) {
} }
return 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 // DeleteProject delete a project

@ -10,8 +10,10 @@ import (
"crypto/subtle" "crypto/subtle"
"errors" "errors"
"fmt" "fmt"
"html"
"net/http" "net/http"
"path" "net/url"
"strconv"
"strings" "strings"
"time" "time"
@ -34,7 +36,6 @@ import (
"code.gitea.io/gitea/services/gitdiff" "code.gitea.io/gitea/services/gitdiff"
pull_service "code.gitea.io/gitea/services/pull" pull_service "code.gitea.io/gitea/services/pull"
repo_service "code.gitea.io/gitea/services/repository" repo_service "code.gitea.io/gitea/services/repository"
"github.com/unknwon/com"
) )
const ( const (
@ -109,8 +110,7 @@ func getForkRepository(ctx *context.Context) *models.Repository {
ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate ctx.Data["IsPrivate"] = forkRepo.IsPrivate || forkRepo.Owner.Visibility == structs.VisibleTypePrivate
canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID) canForkToUser := forkRepo.OwnerID != ctx.User.ID && !ctx.User.HasForkedRepo(forkRepo.ID)
ctx.Data["ForkFrom"] = forkRepo.Owner.Name + "/" + forkRepo.Name ctx.Data["ForkRepo"] = forkRepo
ctx.Data["ForkFromOwnerID"] = forkRepo.Owner.ID
if err := ctx.User.GetOwnedOrganizations(); err != nil { if err := ctx.User.GetOwnedOrganizations(); err != nil {
ctx.ServerError("GetOwnedOrganizations", err) ctx.ServerError("GetOwnedOrganizations", err)
@ -202,7 +202,7 @@ func ForkPost(ctx *context.Context) {
} }
repo, has := models.HasForkedRepo(ctxUser.ID, traverseParentRepo.ID) repo, has := models.HasForkedRepo(ctxUser.ID, traverseParentRepo.ID)
if has { if has {
ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) ctx.Redirect(ctxUser.HomeLink() + "/" + url.PathEscape(repo.Name))
return return
} }
if !traverseParentRepo.IsFork { 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) 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 { 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, ctx.Repo.Owner.Name, ctx.Repo.Repository.Name)
setCompareContext(ctx, baseCommit, commit, headTarget)
ctx.Data["RequireHighlightJS"] = true ctx.Data["RequireHighlightJS"] = true
ctx.Data["RequireSimpleMDE"] = 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) // ToDo: add check if maintainers are allowed to change branch ... (need migration & co)
if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) { if (!allowedUpdateByMerge && !rebase) || (rebase && !allowedUpdateByRebase) {
ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) 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 return
} }
@ -766,7 +765,7 @@ func UpdatePullRequest(ctx *context.Context) {
return return
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} else if models.IsErrRebaseConflicts(err) { } else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts) conflictError := err.(models.ErrRebaseConflicts)
@ -780,19 +779,19 @@ func UpdatePullRequest(ctx *context.Context) {
return return
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} }
ctx.Flash.Error(err.Error()) ctx.Flash.Error(err.Error())
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} }
time.Sleep(1 * time.Second) time.Sleep(1 * time.Second)
ctx.Flash.Success(ctx.Tr("repo.pulls.update_branch_success")) 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 // MergePullRequest response for merging pull request
@ -805,11 +804,11 @@ func MergePullRequest(ctx *context.Context) {
if issue.IsClosed { if issue.IsClosed {
if issue.IsPull { if issue.IsPull {
ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed")) ctx.Flash.Error(ctx.Tr("repo.pulls.is_closed"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} }
ctx.Flash.Error(ctx.Tr("repo.issues.closed_title")) ctx.Flash.Error(ctx.Tr("repo.issues.closed_title"))
ctx.Redirect(ctx.Repo.RepoLink + "/issues/" + fmt.Sprint(issue.Index)) ctx.Redirect(issue.Link())
return return
} }
@ -822,13 +821,13 @@ func MergePullRequest(ctx *context.Context) {
} }
if !allowedMerge { if !allowedMerge {
ctx.Flash.Error(ctx.Tr("repo.pulls.update_not_allowed")) 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 return
} }
if pr.HasMerged { if pr.HasMerged {
ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged")) ctx.Flash.Error(ctx.Tr("repo.pulls.has_merged"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) ctx.Redirect(issue.Link())
return 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 err = pull_service.MergedManually(pr, ctx.User, ctx.Repo.GitRepo, form.MergeCommitID); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) 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 return
} else if strings.Contains(err.Error(), "Wrong commit ID") { } else if strings.Contains(err.Error(), "Wrong commit ID") {
ctx.Flash.Error(ctx.Tr("repo.pulls.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 return
} }
@ -849,19 +848,19 @@ func MergePullRequest(ctx *context.Context) {
return return
} }
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + com.ToStr(issue.Index)) ctx.Redirect(issue.Link())
return return
} }
if !pr.CanAutoMerge() { if !pr.CanAutoMerge() {
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) 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 return
} }
if pr.IsWorkInProgress() { if pr.IsWorkInProgress() {
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_wip")) 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 return
} }
@ -875,14 +874,14 @@ func MergePullRequest(ctx *context.Context) {
return return
} else if !isRepoAdmin { } else if !isRepoAdmin {
ctx.Flash.Error(ctx.Tr("repo.pulls.no_merge_not_ready")) 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 return
} }
} }
if ctx.HasError() { if ctx.HasError() {
ctx.Flash.Error(ctx.Data["ErrorMsg"].(string)) ctx.Flash.Error(ctx.Data["ErrorMsg"].(string))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) ctx.Redirect(issue.Link())
return return
} }
@ -914,14 +913,14 @@ func MergePullRequest(ctx *context.Context) {
if !noDeps { if !noDeps {
ctx.Flash.Error(ctx.Tr("repo.issues.dependency.pr_close_blocked")) 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 return
} }
if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil { if err = pull_service.Merge(pr, ctx.User, ctx.Repo.GitRepo, models.MergeStyle(form.Do), message); err != nil {
if models.IsErrInvalidMergeStyle(err) { if models.IsErrInvalidMergeStyle(err) {
ctx.Flash.Error(ctx.Tr("repo.pulls.invalid_merge_option")) 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 return
} else if models.IsErrMergeConflicts(err) { } else if models.IsErrMergeConflicts(err) {
conflictError := err.(models.ErrMergeConflicts) conflictError := err.(models.ErrMergeConflicts)
@ -935,7 +934,7 @@ func MergePullRequest(ctx *context.Context) {
return return
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) ctx.Redirect(issue.Link())
return return
} else if models.IsErrRebaseConflicts(err) { } else if models.IsErrRebaseConflicts(err) {
conflictError := err.(models.ErrRebaseConflicts) conflictError := err.(models.ErrRebaseConflicts)
@ -949,17 +948,17 @@ func MergePullRequest(ctx *context.Context) {
return return
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) ctx.Redirect(issue.Link())
return return
} else if models.IsErrMergeUnrelatedHistories(err) { } else if models.IsErrMergeUnrelatedHistories(err) {
log.Debug("MergeUnrelatedHistories error: %v", err) log.Debug("MergeUnrelatedHistories error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories")) ctx.Flash.Error(ctx.Tr("repo.pulls.unrelated_histories"))
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) ctx.Redirect(issue.Link())
return return
} else if git.IsErrPushOutOfDate(err) { } else if git.IsErrPushOutOfDate(err) {
log.Debug("MergePushOutOfDate error: %v", err) log.Debug("MergePushOutOfDate error: %v", err)
ctx.Flash.Error(ctx.Tr("repo.pulls.merge_out_of_date")) 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 return
} else if git.IsErrPushRejected(err) { } else if git.IsErrPushRejected(err) {
log.Debug("MergePushRejected error: %v", err) log.Debug("MergePushRejected error: %v", err)
@ -979,7 +978,7 @@ func MergePullRequest(ctx *context.Context) {
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
} }
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pr.Index)) ctx.Redirect(issue.Link())
return return
} }
ctx.ServerError("Merge", err) ctx.ServerError("Merge", err)
@ -1008,7 +1007,7 @@ func MergePullRequest(ctx *context.Context) {
deleteBranch(ctx, pr, headRepo) 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 { func stopTimerIfAvailable(user *models.User, issue *models.Issue) error {
@ -1097,6 +1096,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
pullIssue := &models.Issue{ pullIssue := &models.Issue{
RepoID: repo.ID, RepoID: repo.ID,
Repo: repo,
Title: form.Title, Title: form.Title,
PosterID: ctx.User.ID, PosterID: ctx.User.ID,
Poster: ctx.User, Poster: ctx.User,
@ -1138,7 +1138,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
} }
ctx.Flash.Error(flashError) ctx.Flash.Error(flashError)
} }
ctx.Redirect(ctx.Repo.RepoLink + "/pulls/" + fmt.Sprint(pullIssue.Index)) ctx.Redirect(pullIssue.Link())
return return
} }
ctx.ServerError("NewPullRequest", err) ctx.ServerError("NewPullRequest", err)
@ -1146,7 +1146,7 @@ func CompareAndPullRequestPost(ctx *context.Context) {
} }
log.Trace("Pull request created: %d/%d", repo.ID, pullIssue.ID) 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 // TriggerTask response for a trigger task request
@ -1261,7 +1261,7 @@ func CleanUpPullRequest(ctx *context.Context) {
defer func() { defer func() {
ctx.JSON(http.StatusOK, map[string]interface{}{ 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) err := err.(models.ErrPullRequestAlreadyExists)
RepoRelPath := ctx.Repo.Owner.Name + "/" + ctx.Repo.Repository.Name 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.Flash.Error(errorMessage)
ctx.JSON(http.StatusConflict, map[string]interface{}{ ctx.JSON(http.StatusConflict, map[string]interface{}{

@ -20,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/markup/markdown" "code.gitea.io/gitea/modules/markup/markdown"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/upload" "code.gitea.io/gitea/modules/upload"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
releaseservice "code.gitea.io/gitea/services/release" 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.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 return
} }

@ -244,7 +244,7 @@ func CreatePost(ctx *context.Context) {
repo, err = repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts) repo, err = repo_service.GenerateRepository(ctx.User, ctxUser, templateRepo, opts)
if err == nil { if err == nil {
log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) log.Trace("Repository generated [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) ctx.Redirect(repo.Link())
return return
} }
} else { } else {
@ -263,7 +263,7 @@ func CreatePost(ctx *context.Context) {
}) })
if err == nil { if err == nil {
log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name) log.Trace("Repository created [%d]: %s/%s", repo.ID, ctxUser.Name, repo.Name)
ctx.Redirect(ctxUser.HomeLink() + "/" + repo.Name) ctx.Redirect(repo.Link())
return return
} }
} }

@ -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) 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.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": case "cancel_transfer":
if !ctx.Repo.IsOwner() { if !ctx.Repo.IsOwner() {
@ -627,7 +627,7 @@ func SettingsPost(ctx *context.Context) {
if err != nil { if err != nil {
if models.IsErrNoPendingTransfer(err) { if models.IsErrNoPendingTransfer(err) {
ctx.Flash.Error("repo.settings.transfer_abort_invalid") ctx.Flash.Error("repo.settings.transfer_abort_invalid")
ctx.Redirect(ctx.User.HomeLink() + "/" + repo.Name + "/settings") ctx.Redirect(repo.Link() + "/settings")
} else { } else {
ctx.ServerError("GetPendingRepositoryTransfer", err) 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) 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.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": case "delete":
if !ctx.Repo.IsOwner() { if !ctx.Repo.IsOwner() {
@ -796,7 +796,7 @@ func Collaboration(ctx *context.Context) {
func CollaborationPost(ctx *context.Context) { func CollaborationPost(ctx *context.Context) {
name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator"))) name := utils.RemoveUsernameParameterSuffix(strings.ToLower(ctx.FormString("collaborator")))
if len(name) == 0 || ctx.Repo.Owner.LowerName == name { 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 return
} }
@ -804,7 +804,7 @@ func CollaborationPost(ctx *context.Context) {
if err != nil { if err != nil {
if models.IsErrUserNotExist(err) { if models.IsErrUserNotExist(err) {
ctx.Flash.Error(ctx.Tr("form.user_not_exist")) 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 { } else {
ctx.ServerError("GetUserByName", err) ctx.ServerError("GetUserByName", err)
} }
@ -813,14 +813,14 @@ func CollaborationPost(ctx *context.Context) {
if !u.IsActive { if !u.IsActive {
ctx.Flash.Error(ctx.Tr("repo.settings.add_collaborator_inactive_user")) 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 return
} }
// Organization is not allowed to be added as a collaborator. // Organization is not allowed to be added as a collaborator.
if u.IsOrganization() { if u.IsOrganization() {
ctx.Flash.Error(ctx.Tr("repo.settings.org_not_allowed_to_be_collaborator")) 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 return
} }
@ -840,7 +840,7 @@ func CollaborationPost(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("repo.settings.add_collaborator_success")) 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 // ChangeCollaborationAccessMode response for changing access of a collaboration

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/web" "code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/services/forms" "code.gitea.io/gitea/services/forms"
pull_service "code.gitea.io/gitea/services/pull" 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) 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.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: default:
ctx.NotFound("", nil) ctx.NotFound("", nil)
} }
@ -197,7 +198,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
} }
if f.RequiredApprovals < 0 { if f.RequiredApprovals < 0 {
ctx.Flash.Error(ctx.Tr("repo.settings.protected_branch_required_approvals_min")) 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 var whitelistUsers, whitelistTeams, mergeWhitelistUsers, mergeWhitelistTeams, approvalsWhitelistUsers, approvalsWhitelistTeams []int64
@ -274,7 +275,7 @@ func SettingsProtectedBranchPost(ctx *context.Context) {
return return
} }
ctx.Flash.Success(ctx.Tr("repo.settings.update_protect_branch_success", branch)) 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 { } else {
if protectBranch != nil { if protectBranch != nil {
if err := ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil { if err := ctx.Repo.Repository.DeleteProtectedBranch(protectBranch.ID); err != nil {

@ -58,7 +58,7 @@ func NewProtectedTagPost(ctx *context.Context) {
} }
ctx.Flash.Success(ctx.Tr("repo.settings.update_settings_success")) 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 // EditProtectedTag render the page to edit a protect tag

@ -232,7 +232,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
} }
if readmeFile != nil { if readmeFile != nil {
readmeFile.name = entry.Name() + "/" + readmeFile.name readmeFile.name = entry.Name() + "/" + readmeFile.name
readmeTreelink = treeLink + "/" + entry.GetSubJumpablePathName() readmeTreelink = treeLink + "/" + util.PathEscapeSegments(entry.GetSubJumpablePathName())
break break
} }
} }
@ -301,7 +301,7 @@ func renderDirectory(ctx *context.Context, treeLink string) {
fileSize = meta.Size fileSize = meta.Size
ctx.Data["FileSize"] = meta.Size ctx.Data["FileSize"] = meta.Size
filenameBase64 := base64.RawURLEncoding.EncodeToString([]byte(readmeFile.name)) 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() fileSize := blob.Size()
ctx.Data["FileIsSymlink"] = entry.IsLink() ctx.Data["FileIsSymlink"] = entry.IsLink()
ctx.Data["FileName"] = blob.Name() 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) buf := make([]byte, 1024)
n, _ := util.ReadAtMost(dataRc, buf) n, _ := util.ReadAtMost(dataRc, buf)
@ -422,7 +422,7 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st
isTextFile = st.IsText() isTextFile = st.IsText()
fileSize = meta.Size 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 { 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 return
} }
} }
@ -684,7 +684,7 @@ func renderDirectoryFiles(ctx *context.Context, timeout time.Duration) git.Entri
return nil 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. // Get current entry user currently looking at.
entry, err := ctx.Repo.Commit.GetTreeEntryByPath(ctx.Repo.TreePath) 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 treeLink := branchLink
if len(ctx.Repo.TreePath) > 0 { if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + ctx.Repo.TreePath treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
} }
ctx.Data["TreeLink"] = treeLink ctx.Data["TreeLink"] = treeLink
@ -815,7 +815,7 @@ func renderCode(ctx *context.Context) {
rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL() rawLink := ctx.Repo.RepoLink + "/raw/" + ctx.Repo.BranchNameSubURL()
if len(ctx.Repo.TreePath) > 0 { if len(ctx.Repo.TreePath) > 0 {
treeLink += "/" + ctx.Repo.TreePath treeLink += "/" + util.PathEscapeSegments(ctx.Repo.TreePath)
} }
// Get Topics of this repo // Get Topics of this repo

@ -9,6 +9,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"net/url"
"path" "path"
"strings" "strings"
@ -414,7 +415,7 @@ func TelegramHooksNewPost(ctx *context.Context) {
w := &webhook.Webhook{ w := &webhook.Webhook{
RepoID: orCtx.RepoID, 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, ContentType: webhook.ContentTypeJSON,
HookEvent: ParseHookEvent(form.WebhookForm), HookEvent: ParseHookEvent(form.WebhookForm),
IsActive: form.Active, IsActive: form.Active,
@ -468,7 +469,7 @@ func MatrixHooksNewPost(ctx *context.Context) {
w := &webhook.Webhook{ w := &webhook.Webhook{
RepoID: orCtx.RepoID, 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, ContentType: webhook.ContentTypeJSON,
HTTPMethod: "PUT", HTTPMethod: "PUT",
HookEvent: ParseHookEvent(form.WebhookForm), HookEvent: ParseHookEvent(form.WebhookForm),
@ -976,7 +977,7 @@ func TelegramHooksEditPost(ctx *context.Context) {
return return
} }
w.Meta = string(meta) 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.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active w.IsActive = form.Active
if err := w.UpdateEvent(); err != nil { if err := w.UpdateEvent(); err != nil {
@ -1020,7 +1021,7 @@ func MatrixHooksEditPost(ctx *context.Context) {
return return
} }
w.Meta = string(meta) 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.HookEvent = ParseHookEvent(form.WebhookForm)
w.IsActive = form.Active w.IsActive = form.Active
@ -1162,7 +1163,7 @@ func TestWebhook(ctx *context.Context) {
apiCommit := &api.PayloadCommit{ apiCommit := &api.PayloadCommit{
ID: commit.ID.String(), ID: commit.ID.String(),
Message: commit.Message(), 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{ Author: &api.PayloadUser{
Name: commit.Author.Name, Name: commit.Author.Name,
Email: commit.Author.Email, Email: commit.Author.Email,

@ -180,7 +180,7 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) {
ctx.Data["Pages"] = pages ctx.Data["Pages"] = pages
// get requested pagename // get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) pageName := wiki_service.NormalizeWikiName(ctx.Params("*"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" 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 //lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry { if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
} }
if entry == nil || ctx.Written() { if entry == nil || ctx.Written() {
if wikiRepo != nil { if wikiRepo != nil {
@ -276,7 +276,7 @@ func renderRevisionPage(ctx *context.Context) (*git.Repository, *git.TreeEntry)
} }
// get requested pagename // get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) pageName := wiki_service.NormalizeWikiName(ctx.Params("*"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" 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 //lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName) data, entry, pageFilename, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry { if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
} }
if entry == nil || ctx.Written() { if entry == nil || ctx.Written() {
if wikiRepo != nil { if wikiRepo != nil {
@ -352,7 +352,7 @@ func renderEditPage(ctx *context.Context) {
}() }()
// get requested pagename // get requested pagename
pageName := wiki_service.NormalizeWikiName(ctx.Params(":page")) pageName := wiki_service.NormalizeWikiName(ctx.Params("*"))
if len(pageName) == 0 { if len(pageName) == 0 {
pageName = "Home" pageName = "Home"
} }
@ -365,7 +365,7 @@ func renderEditPage(ctx *context.Context) {
//lookup filename in wiki - get filecontent, gitTree entry , real filename //lookup filename in wiki - get filecontent, gitTree entry , real filename
data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName) data, entry, _, noEntry := wikiContentsByName(ctx, commit, pageName)
if noEntry { if noEntry {
ctx.Redirect(ctx.Repo.RepoLink + "/wiki/_pages") ctx.Redirect(ctx.Repo.RepoLink + "/wiki/?action=_pages")
} }
if entry == nil || ctx.Written() { if entry == nil || ctx.Written() {
return return
@ -378,6 +378,32 @@ func renderEditPage(ctx *context.Context) {
ctx.Data["footerContent"] = "" 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 // Wiki renders single wiki page
func Wiki(ctx *context.Context) { func Wiki(ctx *context.Context) {
ctx.Data["PageIsWiki"] = true ctx.Data["PageIsWiki"] = true
@ -389,6 +415,29 @@ func Wiki(ctx *context.Context) {
return 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) wikiRepo, entry := renderViewPage(ctx)
defer func() { defer func() {
if wikiRepo != nil { if wikiRepo != nil {
@ -652,7 +701,7 @@ func EditWikiPost(ctx *context.Context) {
return return
} }
oldWikiName := wiki_service.NormalizeWikiName(ctx.Params(":page")) oldWikiName := wiki_service.NormalizeWikiName(ctx.Params("*"))
newWikiName := wiki_service.NormalizeWikiName(form.Title) newWikiName := wiki_service.NormalizeWikiName(form.Title)
if len(form.Message) == 0 { if len(form.Message) == 0 {
@ -669,7 +718,7 @@ func EditWikiPost(ctx *context.Context) {
// DeleteWikiPagePost delete wiki page // DeleteWikiPagePost delete wiki page
func DeleteWikiPagePost(ctx *context.Context) { func DeleteWikiPagePost(ctx *context.Context) {
wikiName := wiki_service.NormalizeWikiName(ctx.Params(":page")) wikiName := wiki_service.NormalizeWikiName(ctx.Params("*"))
if len(wikiName) == 0 { if len(wikiName) == 0 {
wikiName = "Home" wikiName = "Home"
} }

@ -76,8 +76,8 @@ func assertPagesMetas(t *testing.T, expectedNames []string, metas interface{}) {
func TestWiki(t *testing.T) { func TestWiki(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1/wiki/_pages") ctx := test.MockContext(t, "user2/repo1/wiki/?action=_pages")
ctx.SetParams(":page", "Home") ctx.SetParams("*", "Home")
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
Wiki(ctx) Wiki(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
@ -88,7 +88,7 @@ func TestWiki(t *testing.T) {
func TestWikiPages(t *testing.T) { func TestWikiPages(t *testing.T) {
unittest.PrepareTestEnv(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) test.LoadRepo(t, ctx, 1)
WikiPages(ctx) WikiPages(ctx)
assert.EqualValues(t, http.StatusOK, ctx.Resp.Status()) assert.EqualValues(t, http.StatusOK, ctx.Resp.Status())
@ -98,7 +98,7 @@ func TestWikiPages(t *testing.T) {
func TestNewWiki(t *testing.T) { func TestNewWiki(t *testing.T) {
unittest.PrepareTestEnv(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.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
NewWiki(ctx) NewWiki(ctx)
@ -113,7 +113,7 @@ func TestNewWikiPost(t *testing.T) {
} { } {
unittest.PrepareTestEnv(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.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
web.SetForm(ctx, &forms.NewWikiForm{ web.SetForm(ctx, &forms.NewWikiForm{
@ -131,7 +131,7 @@ func TestNewWikiPost(t *testing.T) {
func TestNewWikiPost_ReservedName(t *testing.T) { func TestNewWikiPost_ReservedName(t *testing.T) {
unittest.PrepareTestEnv(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.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
web.SetForm(ctx, &forms.NewWikiForm{ web.SetForm(ctx, &forms.NewWikiForm{
@ -148,8 +148,8 @@ func TestNewWikiPost_ReservedName(t *testing.T) {
func TestEditWiki(t *testing.T) { func TestEditWiki(t *testing.T) {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1/wiki/_edit/Home") ctx := test.MockContext(t, "user2/repo1/wiki/Home?action=_edit")
ctx.SetParams(":page", "Home") ctx.SetParams("*", "Home")
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
EditWiki(ctx) EditWiki(ctx)
@ -164,8 +164,8 @@ func TestEditWikiPost(t *testing.T) {
"New/<page>", "New/<page>",
} { } {
unittest.PrepareTestEnv(t) unittest.PrepareTestEnv(t)
ctx := test.MockContext(t, "user2/repo1/wiki/_new/Home") ctx := test.MockContext(t, "user2/repo1/wiki/Home?action=_new")
ctx.SetParams(":page", "Home") ctx.SetParams("*", "Home")
test.LoadUser(t, ctx, 2) test.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
web.SetForm(ctx, &forms.NewWikiForm{ web.SetForm(ctx, &forms.NewWikiForm{
@ -186,7 +186,7 @@ func TestEditWikiPost(t *testing.T) {
func TestDeleteWikiPagePost(t *testing.T) { func TestDeleteWikiPagePost(t *testing.T) {
unittest.PrepareTestEnv(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.LoadUser(t, ctx, 2)
test.LoadRepo(t, ctx, 1) test.LoadRepo(t, ctx, 1)
DeleteWikiPagePost(ctx) DeleteWikiPagePost(ctx)

@ -887,5 +887,5 @@ func Email2User(ctx *context.Context) {
} }
return return
} }
ctx.Redirect(setting.AppSubURL + "/user/" + u.Name) ctx.Redirect(u.HomeLink())
} }

@ -167,7 +167,7 @@ func NotificationStatusPost(c *context.Context) {
} }
if !c.FormBool("noredirect") { 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) c.Redirect(url, http.StatusSeeOther)
} }
@ -189,6 +189,5 @@ func NotificationPurgePost(c *context.Context) {
return return
} }
url := fmt.Sprintf("%s/notifications", setting.AppSubURL) c.Redirect(setting.AppSubURL+"/notifications", http.StatusSeeOther)
c.Redirect(url, http.StatusSeeOther)
} }

@ -454,7 +454,7 @@ func AuthorizeOAuth(ctx *context.Context) {
ctx.Data["State"] = form.State ctx.Data["State"] = form.State
ctx.Data["Scope"] = form.Scope ctx.Data["Scope"] = form.Scope
ctx.Data["Nonce"] = form.Nonce ctx.Data["Nonce"] = form.Nonce
ctx.Data["ApplicationUserLink"] = "<a href=\"" + html.EscapeString(setting.AppURL) + html.EscapeString(url.PathEscape(user.LowerName)) + "\">@" + html.EscapeString(user.Name) + "</a>" ctx.Data["ApplicationUserLink"] = "<a href=\"" + html.EscapeString(user.HTMLURL()) + "\">@" + html.EscapeString(user.Name) + "</a>"
ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + html.EscapeString(form.RedirectURI) + "</strong>" ctx.Data["ApplicationRedirectDomainHTML"] = "<strong>" + html.EscapeString(form.RedirectURI) + "</strong>"
// TODO document SESSION <=> FORM // TODO document SESSION <=> FORM
err = ctx.Session.Set("client_id", app.ClientID) err = ctx.Session.Set("client_id", app.ClientID)

@ -364,6 +364,6 @@ func Action(ctx *context.Context) {
ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err) ctx.ServerError(fmt.Sprintf("Action (%s)", ctx.Params(":action")), err)
return 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()) ctx.RedirectToFirst(ctx.FormString("redirect_to"), u.HomeLink())
} }

@ -895,21 +895,23 @@ func RegisterRoutes(m *web.Route) {
}, reqRepoProjectsReader, repo.MustEnableProjects) }, reqRepoProjectsReader, repo.MustEnableProjects)
m.Group("/wiki", func() { m.Group("/wiki", func() {
m.Get("/", repo.Wiki) m.Combo("/").
m.Get("/{page}", repo.Wiki) Get(repo.Wiki).
m.Get("/_pages", repo.WikiPages) Post(context.RepoMustNotBeArchived(),
m.Get("/{page}/_revision", repo.WikiRevision) 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}}", repo.SetEditorconfigIfExists, repo.SetDiffViewStyle, repo.SetWhitespaceBehavior, repo.Diff)
m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff) m.Get("/commit/{sha:[a-f0-9]{7,40}}.{ext:patch|diff}", repo.RawDiff)
}, repo.MustEnableWiki, func(ctx *context.Context) {
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) {
ctx.Data["PageIsWiki"] = true ctx.Data["PageIsWiki"] = true
}) })

@ -12,6 +12,7 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/url"
"path" "path"
"regexp" "regexp"
"strconv" "strconv"
@ -46,17 +47,17 @@ type Claims struct {
// DownloadLink builds a URL to download the object. // DownloadLink builds a URL to download the object.
func (rc *requestContext) DownloadLink(p lfs_module.Pointer) string { 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. // UploadLink builds a URL to upload the object.
func (rc *requestContext) UploadLink(p lfs_module.Pointer) string { 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. // VerifyLink builds a URL for verifying the object.
func (rc *requestContext) VerifyLink(p lfs_module.Pointer) string { 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. // CheckAcceptMediaType checks if the client accepts the LFS media type.

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
dingtalk "github.com/lunny/dingtalk_webhook" 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) refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) 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 // Delete implements PayloadConvertor Delete method
@ -50,7 +51,7 @@ func (d *DingtalkPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
refName := git.RefEndName(p.Ref) refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) 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 // 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]) linkText = fmt.Sprintf("view commit %s...%s", p.Commits[0].ID[:7], p.Commits[len(p.Commits)-1].ID[:7])
} }
if titleLink == "" { 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) title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
) )
type ( type (
@ -115,7 +116,7 @@ func (d *DiscordPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
refName := git.RefEndName(p.Ref) refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s created", p.Repo.FullName, p.RefType, refName) 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 // Delete implements PayloadConvertor Delete method
@ -124,7 +125,7 @@ func (d *DiscordPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
refName := git.RefEndName(p.Ref) refName := git.RefEndName(p.Ref)
title := fmt.Sprintf("[%s] %s %s deleted", p.Repo.FullName, p.RefType, refName) 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 // Fork implements PayloadConvertor Fork method
@ -150,7 +151,7 @@ func (d *DiscordPayload) Push(p *api.PushPayload) (api.Payloader, error) {
titleLink = p.CompareURL titleLink = p.CompareURL
} }
if titleLink == "" { 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) title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)

@ -7,10 +7,12 @@ package webhook
import ( import (
"fmt" "fmt"
"html" "html"
"net/url"
"strings" "strings"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
) )
type linkFormatter = func(string, string) string type linkFormatter = func(string, string) string
@ -22,7 +24,7 @@ func noneLinkFormatter(url string, text string) string {
// htmlLinkFormatter creates a HTML link // htmlLinkFormatter creates a HTML link
func htmlLinkFormatter(url string, text string) string { func htmlLinkFormatter(url string, text string) string {
return fmt.Sprintf(`<a href="%s">%s</a>`, url, html.EscapeString(text)) return fmt.Sprintf(`<a href="%s">%s</a>`, html.EscapeString(url), html.EscapeString(text))
} }
func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, withSender bool) (string, string, string, int) { 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: case api.HookIssueAssigned:
list := make([]string, len(p.Issue.Assignees)) list := make([]string, len(p.Issue.Assignees))
for i, user := range 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) text = fmt.Sprintf("[%s] Issue assigned to %s: %s", repoLink, strings.Join(list, ", "), titleLink)
color = greenColor color = greenColor
@ -66,7 +68,7 @@ func getIssuesPayloadInfo(p *api.IssuePayload, linkFormatter linkFormatter, with
text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink) text = fmt.Sprintf("[%s] Issue milestone cleared: %s", repoLink, titleLink)
} }
if withSender { 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 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) { func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, withSender bool) (text string, color int) {
repoLink := linkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 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 { switch p.Action {
case api.HookReleasePublished: case api.HookReleasePublished:
@ -153,7 +155,7 @@ func getReleasePayloadInfo(p *api.ReleasePayload, linkFormatter linkFormatter, w
color = redColor color = redColor
} }
if withSender { 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 return text, color
@ -189,7 +191,7 @@ func getIssueCommentPayloadInfo(p *api.IssueCommentPayload, linkFormatter linkFo
color = redColor color = redColor
} }
if withSender { 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 return text, issueTitle, color

@ -10,6 +10,7 @@ import (
"fmt" "fmt"
"html" "html"
"net/http" "net/http"
"net/url"
"regexp" "regexp"
"strings" "strings"
@ -19,6 +20,7 @@ import (
"code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/setting"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
) )
const matrixPayloadSizeLimit = 1024 * 64 const matrixPayloadSizeLimit = 1024 * 64
@ -94,11 +96,11 @@ func MatrixLinkToRef(repoURL, ref string) string {
refName := git.RefEndName(ref) refName := git.RefEndName(ref)
switch { switch {
case strings.HasPrefix(ref, git.BranchPrefix): 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): case strings.HasPrefix(ref, git.TagPrefix):
return MatrixLinkFormatter(repoURL+"/src/tag/"+refName, refName) return MatrixLinkFormatter(repoURL+"/src/tag/"+util.PathEscapeSegments(refName), refName)
default: 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 // Review implements PayloadConvertor Review method
func (m *MatrixPayloadUnsafe) Review(p *api.PullRequestPayload, event webhook_model.HookEventType) (api.Payloader, error) { 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) title := fmt.Sprintf("#%d %s", p.Index, p.PullRequest.Title)
titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index) titleLink := fmt.Sprintf("%s/pulls/%d", p.Repository.HTMLURL, p.Index)
repoLink := MatrixLinkFormatter(p.Repository.HTMLURL, p.Repository.FullName) 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) 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))) req, err := http.NewRequest(w.HTTPMethod, url, strings.NewReader(string(payload)))
if err != nil { if err != nil {

@ -12,6 +12,7 @@ import (
"code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/json" "code.gitea.io/gitea/modules/json"
api "code.gitea.io/gitea/modules/structs" api "code.gitea.io/gitea/modules/structs"
"code.gitea.io/gitea/modules/util"
) )
type ( type (
@ -79,7 +80,7 @@ func (m *MSTeamsPayload) Create(p *api.CreatePayload) (api.Payloader, error) {
p.Sender, p.Sender,
title, title,
"", "",
p.Repo.HTMLURL+"/src/"+refName, p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName),
greenColor, greenColor,
&MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName},
), nil ), nil
@ -96,7 +97,7 @@ func (m *MSTeamsPayload) Delete(p *api.DeletePayload) (api.Payloader, error) {
p.Sender, p.Sender,
title, title,
"", "",
p.Repo.HTMLURL+"/src/"+refName, p.Repo.HTMLURL+"/src/"+util.PathEscapeSegments(refName),
yellowColor, yellowColor,
&MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName}, &MSTeamsFact{fmt.Sprintf("%s:", p.RefType), refName},
), nil ), nil
@ -133,7 +134,7 @@ func (m *MSTeamsPayload) Push(p *api.PushPayload) (api.Payloader, error) {
titleLink = p.CompareURL titleLink = p.CompareURL
} }
if titleLink == "" { 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) title := fmt.Sprintf("[%s:%s] %s", p.Repo.FullName, branchName, commitDesc)

@ -36,7 +36,7 @@ func nameAllowed(name string) error {
// NameToSubURL converts a wiki name to its corresponding sub-URL. // NameToSubURL converts a wiki name to its corresponding sub-URL.
func NameToSubURL(name string) string { func NameToSubURL(name string) string {
return url.QueryEscape(strings.ReplaceAll(name, " ", "-")) return url.PathEscape(strings.ReplaceAll(name, " ", "-"))
} }
// NormalizeWikiName normalizes a wiki name // NormalizeWikiName normalizes a wiki name

@ -49,7 +49,7 @@
<tbody> <tbody>
{{range .Emails}} {{range .Emails}}
<tr> <tr>
<td><a href="{{AppSubUrl}}/{{.Name}}">{{.Name}}</a></td> <td><a href="{{AppSubUrl}}/{{.Name | PathEscape}}">{{.Name}}</a></td>
<td><span class="text truncate">{{.FullName}}</span></td> <td><span class="text truncate">{{.FullName}}</span></td>
<td><span class="text email">{{.Email}}</span></td> <td><span class="text email">{{.Email}}</span></td>
<td>{{if .IsPrimary}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsPrimary}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>

@ -45,13 +45,13 @@
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td> <td>
<a href="{{AppSubUrl}}/{{.Owner.Name}}">{{.Owner.Name}}</a> <a href="{{.Owner.HomeLink}}">{{.Owner.Name}}</a>
{{if .Owner.Visibility.IsPrivate}} {{if .Owner.Visibility.IsPrivate}}
<span class="text gold">{{svg "octicon-lock"}}</span> <span class="text gold">{{svg "octicon-lock"}}</span>
{{end}} {{end}}
</td> </td>
<td> <td>
<a href="{{AppSubUrl}}/{{.Owner.Name}}/{{.Name}}">{{.Name}}</a> <a href="{{.Link}}">{{.Name}}</a>
{{if .IsArchived}} {{if .IsArchived}}
<span class="ui basic mini label">{{$.i18n.Tr "repo.desc.archived"}}</span> <span class="ui basic mini label">{{$.i18n.Tr "repo.desc.archived"}}</span>
{{end}} {{end}}

@ -87,7 +87,7 @@
{{range .Users}} {{range .Users}}
<tr> <tr>
<td>{{.ID}}</td> <td>{{.ID}}</td>
<td><a href="{{AppSubUrl}}/{{.Name}}">{{.Name}}</a></td> <td><a href="{{.HomeLink}}">{{.Name}}</a></td>
<td><span class="text truncate email">{{.Email}}</span></td> <td><span class="text truncate email">{{.Email}}</span></td>
<td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsActive}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>
<td>{{if .IsAdmin}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td> <td>{{if .IsAdmin}}{{svg "octicon-check"}}{{else}}{{svg "octicon-x"}}{{end}}</td>

@ -102,10 +102,10 @@
<meta property="og:site_name" content="{{AppName}}" /> <meta property="og:site_name" content="{{AppName}}" />
{{if .IsSigned }} {{if .IsSigned }}
{{ if ne .SignedUser.Theme "gitea" }} {{ if ne .SignedUser.Theme "gitea" }}
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{.SignedUser.Theme}}.css?v={{MD5 AppVer}}"> <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{.SignedUser.Theme | PathEscape}}.css?v={{MD5 AppVer}}">
{{end}} {{end}}
{{else if ne DefaultTheme "gitea"}} {{else if ne DefaultTheme "gitea"}}
<link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{DefaultTheme}}.css?v={{MD5 AppVer}}"> <link rel="stylesheet" href="{{AssetUrlPrefix}}/css/theme-{{DefaultTheme | PathEscape}}.css?v={{MD5 AppVer}}">
{{end}} {{end}}
{{template "custom/header" .}} {{template "custom/header" .}}
</head> </head>

@ -63,8 +63,7 @@
</div> </div>
{{else if .IsSigned}} {{else if .IsSigned}}
<div class="right stackable menu"> <div class="right stackable menu">
{{$issueURL := Printf "%s/%s/issues/%d" AppSubUrl .ActiveStopwatch.RepoSlug .ActiveStopwatch.IssueIndex}} <a class="active-stopwatch-trigger item ui label {{if not .ActiveStopwatch}}hidden{{end}}" href="{{.ActiveStopwatch.IssueLink}}">
<a class="active-stopwatch-trigger item ui label {{if not .ActiveStopwatch}}hidden{{end}}" href="{{$issueURL}}">
<span class="text"> <span class="text">
<span class="fitted item"> <span class="fitted item">
{{svg "octicon-stopwatch"}} {{svg "octicon-stopwatch"}}
@ -75,14 +74,14 @@
</a> </a>
<div class="ui popup very wide"> <div class="ui popup very wide">
<div class="df ac"> <div class="df ac">
<a class="stopwatch-link df ac" href="{{$issueURL}}"> <a class="stopwatch-link df ac" href="{{.ActiveStopwatch.IssueLink}}">
{{svg "octicon-issue-opened"}} {{svg "octicon-issue-opened"}}
<span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span> <span class="stopwatch-issue">{{.ActiveStopwatch.RepoSlug}}#{{.ActiveStopwatch.IssueIndex}}</span>
<span class="ui label blue stopwatch-time my-0 mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}"> <span class="ui label blue stopwatch-time my-0 mx-4" data-seconds="{{.ActiveStopwatch.Seconds}}">
{{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}} {{if .ActiveStopwatch}}{{Sec2Time .ActiveStopwatch.Seconds}}{{end}}
</span> </span>
</a> </a>
<form class="stopwatch-commit" method="POST" action="{{$issueURL}}/times/stopwatch/toggle"> <form class="stopwatch-commit" method="POST" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/toggle">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<button <button
class="ui button mini compact basic icon fitted poping up" class="ui button mini compact basic icon fitted poping up"
@ -90,7 +89,7 @@
data-position="top right" data-variation="small inverted" data-position="top right" data-variation="small inverted"
>{{svg "octicon-square-fill"}}</button> >{{svg "octicon-square-fill"}}</button>
</form> </form>
<form class="stopwatch-cancel" method="POST" action="{{$issueURL}}/times/stopwatch/cancel"> <form class="stopwatch-cancel" method="POST" action="{{.ActiveStopwatch.IssueLink}}/times/stopwatch/cancel">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<button <button
class="ui button mini compact basic icon fitted poping up" class="ui button mini compact basic icon fitted poping up"
@ -149,12 +148,12 @@
</div> </div>
<div class="divider"></div> <div class="divider"></div>
<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}"> <a class="item" href="{{.SignedUser.HomeLink}}">
{{svg "octicon-person"}} {{svg "octicon-person"}}
{{.i18n.Tr "your_profile"}}<!-- Your profile --> {{.i18n.Tr "your_profile"}}<!-- Your profile -->
</a> </a>
{{if not .DisableStars}} {{if not .DisableStars}}
<a class="item" href="{{AppSubUrl}}/{{.SignedUser.Name}}?tab=stars"> <a class="item" href="{{.SignedUser.HomeLink}}?tab=stars">
{{svg "octicon-star"}} {{svg "octicon-star"}}
{{.i18n.Tr "your_starred"}} {{.i18n.Tr "your_starred"}}
</a> </a>

@ -37,8 +37,8 @@
{{$repo := (index $.RepoMaps .RepoID)}} {{$repo := (index $.RepoMaps .RepoID)}}
<div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result"> <div class="diff-file-box diff-box file-content non-diff-file-content repo-search-result">
<h4 class="ui top attached normal header"> <h4 class="ui top attached normal header">
<span class="file"><a rel="nofollow" href="{{EscapePound $repo.HTMLURL}}">{{$repo.FullName}}</a> - {{.Filename}}</span> <span class="file"><a rel="nofollow" href="{{$repo.HTMLURL}}">{{$repo.FullName}}</a> - {{.Filename}}</span>
<a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $repo.HTMLURL}}/src/commit/{{$result.CommitID}}/{{EscapePound .Filename}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> <a class="ui basic tiny button" rel="nofollow" href="{{$repo.HTMLURL}}/src/commit/{{$result.CommitID | PathEscape}}/{{.Filename | PathEscapeSegments}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
</h4> </h4>
<div class="ui attached table segment"> <div class="ui attached table segment">
<div class="file-body file-code code-view"> <div class="file-body file-code code-view">
@ -47,7 +47,7 @@
<tr> <tr>
<td class="lines-num"> <td class="lines-num">
{{range .LineNumbers}} {{range .LineNumbers}}
<a href="{{EscapePound $repo.HTMLURL}}/src/commit/{{$result.CommitID}}/{{EscapePound $result.Filename}}#L{{.}}"><span>{{.}}</span></a> <a href="{{$repo.HTMLURL}}/src/commit/{{$result.CommitID | PathEscape}}/{{$result.Filename | PathEscapeSegments}}#L{{.}}"><span>{{.}}</span></a>
{{end}} {{end}}
</td> </td>
<td class="lines-code chroma"><code class="code-inner">{{.FormattedLines | Safe}}</code></td> <td class="lines-code chroma"><code class="code-inner">{{.FormattedLines | Safe}}</code></td>

@ -5,7 +5,7 @@
<title>{{.i18n.Tr "mail.activate_account.title" .DisplayName}}</title> <title>{{.i18n.Tr "mail.activate_account.title" .DisplayName}}</title>
</head> </head>
{{ $activate_url := printf "%suser/activate?code=%s" AppUrl .Code}} {{ $activate_url := printf "%suser/activate?code=%s" AppUrl (QueryEscape .Code)}}
<body> <body>
<p>{{.i18n.Tr "mail.activate_account.text_1" .DisplayName AppName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.activate_account.text_1" .DisplayName AppName | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.activate_account.text_2" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> <p>{{.i18n.Tr "mail.activate_account.text_2" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br>

@ -5,7 +5,7 @@
<title>{{.i18n.Tr "mail.activate_email.title" .DisplayName}}</title> <title>{{.i18n.Tr "mail.activate_email.title" .DisplayName}}</title>
</head> </head>
{{ $activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl .Code (QueryEscape .Email)}} {{ $activate_url := printf "%suser/activate_email?code=%s&email=%s" AppUrl (QueryEscape .Code) (QueryEscape .Email)}}
<body> <body>
<p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.activate_email.text" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br> <p>{{.i18n.Tr "mail.activate_email.text" .ActiveCodeLives | Str2html}}</p><p><a href="{{$activate_url}}">{{$activate_url}}</a></p><br>

@ -10,7 +10,7 @@
<p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.register_notify.text_1" AppName}}</p><br> <p>{{.i18n.Tr "mail.register_notify.text_1" AppName}}</p><br>
<p>{{.i18n.Tr "mail.register_notify.text_2" .Username}}</p><p><a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a></p><br> <p>{{.i18n.Tr "mail.register_notify.text_2" .Username}}</p><p><a href="{{AppUrl}}user/login">{{AppUrl}}user/login</a></p><br>
<p>{{.i18n.Tr "mail.register_notify.text_3" $set_pwd_url | Str2html}}</p><br> <p>{{.i18n.Tr "mail.register_notify.text_3" ($set_pwd_url | Escape) | Str2html}}</p><br>
<p>© <a target="_blank" rel="noopener noreferrer" href="{{AppUrl}}">{{AppName}}</a></p> <p>© <a target="_blank" rel="noopener noreferrer" href="{{AppUrl}}">{{AppName}}</a></p>
</body> </body>

@ -5,7 +5,7 @@
<title>{{.i18n.Tr "mail.reset_password.title" .DisplayName}}</title> <title>{{.i18n.Tr "mail.reset_password.title" .DisplayName}}</title>
</head> </head>
{{ $recover_url := printf "%suser/recover_account?code=%s" AppUrl .Code}} {{ $recover_url := printf "%suser/recover_account?code=%s" AppUrl (QueryEscape .Code)}}
<body> <body>
<p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br> <p>{{.i18n.Tr "mail.hi_user_x" .DisplayName | Str2html}}</p><br>
<p>{{.i18n.Tr "mail.reset_password.text" .ResetPwdCodeLives | Str2html}}</p><p><a href="{{$recover_url}}">{{$recover_url}}</a></p><br> <p>{{.i18n.Tr "mail.reset_password.text" .ResetPwdCodeLives | Str2html}}</p><p><a href="{{$recover_url}}">{{$recover_url}}</a></p><br>

@ -8,8 +8,8 @@
<title>{{.Subject}}</title> <title>{{.Subject}}</title>
</head> </head>
{{$repo_url := printf "<a href='%s'>%s</a>" .Issue.Repo.HTMLURL .Issue.Repo.FullName}} {{$repo_url := printf "<a href='%s'>%s</a>" (Escape .Issue.Repo.HTMLURL) (Escape .Issue.Repo.FullName)}}
{{$link := printf "<a href='%s'>#%d</a>" .Link .Issue.Index}} {{$link := printf "<a href='%s'>#%d</a>" (Escape .Link) (Escape .Issue.Index)}}
<body> <body>
<p> <p>
{{if .IsPull}} {{if .IsPull}}

@ -20,13 +20,13 @@
{{if eq .ActionName "push"}} {{if eq .ActionName "push"}}
<p> <p>
{{if .Comment.IsForcePush}} {{if .Comment.IsForcePush}}
{{$oldCommitUrl := printf "%s%s/%s/commit/%s" AppUrl .Comment.Issue.PullRequest.BaseRepo.OwnerName .Comment.Issue.PullRequest.BaseRepo.Name .Comment.OldCommit}} {{$oldCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.OldCommit}}
{{$oldShortSha := ShortSha .Comment.OldCommit}} {{$oldShortSha := ShortSha .Comment.OldCommit}}
{{$oldCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" $oldCommitUrl $oldShortSha}} {{$oldCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $oldCommitUrl) (Escape $oldShortSha)}}
{{$newCommitUrl := printf "%s%s/%s/commit/%s" AppUrl .Comment.Issue.PullRequest.BaseRepo.OwnerName .Comment.Issue.PullRequest.BaseRepo.Name .Comment.NewCommit}} {{$newCommitUrl := printf "%s/commit/%s" .Comment.Issue.PullRequest.BaseRepo.HTMLURL .Comment.NewCommit}}
{{$newShortSha := ShortSha .Comment.NewCommit}} {{$newShortSha := ShortSha .Comment.NewCommit}}
{{$newCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" $newCommitUrl $newShortSha}} {{$newCommitLink := printf "<a href='%[1]s'><b>%[2]s</b></a>" (Escape $newCommitUrl) (Escape $newShortSha)}}
{{.i18n.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink | Str2html}} {{.i18n.Tr "mail.issue.action.force_push" .Doer.Name .Comment.Issue.PullRequest.HeadBranch $oldCommitLink $newCommitLink | Str2html}}
{{else}} {{else}}
@ -36,26 +36,26 @@
{{end}} {{end}}
<p> <p>
{{if eq .ActionName "close"}} {{if eq .ActionName "close"}}
{{.i18n.Tr "mail.issue.action.close" .Doer.Name .Issue.Index | Str2html}} {{.i18n.Tr "mail.issue.action.close" (Escape .Doer.Name) .Issue.Index | Str2html}}
{{else if eq .ActionName "reopen"}} {{else if eq .ActionName "reopen"}}
{{.i18n.Tr "mail.issue.action.reopen" .Doer.Name .Issue.Index | Str2html}} {{.i18n.Tr "mail.issue.action.reopen" (Escape .Doer.Name) .Issue.Index | Str2html}}
{{else if eq .ActionName "merge"}} {{else if eq .ActionName "merge"}}
{{.i18n.Tr "mail.issue.action.merge" .Doer.Name .Issue.Index .Issue.PullRequest.BaseBranch | Str2html}} {{.i18n.Tr "mail.issue.action.merge" (Escape .Doer.Name) .Issue.Index (Escape .Issue.PullRequest.BaseBranch) | Str2html}}
{{else if eq .ActionName "approve"}} {{else if eq .ActionName "approve"}}
{{.i18n.Tr "mail.issue.action.approve" .Doer.Name | Str2html}} {{.i18n.Tr "mail.issue.action.approve" (Escape .Doer.Name) | Str2html}}
{{else if eq .ActionName "reject"}} {{else if eq .ActionName "reject"}}
{{.i18n.Tr "mail.issue.action.reject" .Doer.Name | Str2html}} {{.i18n.Tr "mail.issue.action.reject" (Escape .Doer.Name) | Str2html}}
{{else if eq .ActionName "review"}} {{else if eq .ActionName "review"}}
{{.i18n.Tr "mail.issue.action.review" .Doer.Name | Str2html}} {{.i18n.Tr "mail.issue.action.review" (Escape .Doer.Name) | Str2html}}
{{else if eq .ActionName "review_dismissed"}} {{else if eq .ActionName "review_dismissed"}}
{{.i18n.Tr "mail.issue.action.review_dismissed" .Doer.Name .Comment.Review.Reviewer.Name | Str2html}} {{.i18n.Tr "mail.issue.action.review_dismissed" (Escape .Doer.Name) (Escape .Comment.Review.Reviewer.Name) | Str2html}}
{{else if eq .ActionName "ready_for_review"}} {{else if eq .ActionName "ready_for_review"}}
{{.i18n.Tr "mail.issue.action.ready_for_review" .Doer.Name | Str2html}} {{.i18n.Tr "mail.issue.action.ready_for_review" (Escape .Doer.Name) | Str2html}}
{{end}} {{end}}
{{- if eq .Body ""}} {{- if eq .Body ""}}
{{if eq .ActionName "new"}} {{if eq .ActionName "new"}}
{{.i18n.Tr "mail.issue.action.new" .Doer.Name .Issue.Index | Str2html}} {{.i18n.Tr "mail.issue.action.new" (Escape .Doer.Name) .Issue.Index | Str2html}}
{{end}} {{end}}
{{else}} {{else}}
{{.Body | Str2html}} {{.Body | Str2html}}
@ -72,7 +72,7 @@
<ul> <ul>
{{range .Comment.Commits}} {{range .Comment.Commits}}
<li> <li>
<a href="{{AppUrl}}{{$.Comment.Issue.PullRequest.BaseRepo.OwnerName}}/{{$.Comment.Issue.PullRequest.BaseRepo.Name}}/commit/{{.ID}}"> <a href="{{$.Comment.Issue.PullRequest.BaseRepo.HTMLURL}}/commit/{{.ID}}">
{{ShortSha .ID.String}} {{ShortSha .ID.String}}
</a> - {{.Summary}} </a> - {{.Summary}}
</li> </li>

@ -5,7 +5,7 @@
<title>{{.Subject}}</title> <title>{{.Subject}}</title>
</head> </head>
{{$url := printf "<a href='%[1]s'>%[2]s</a>" .Link .Repo}} {{$url := printf "<a href='%[1]s'>%[2]s</a>" (Escape .Link) (Escape .Repo)}}
<body> <body>
<p>{{.Subject}}. <p>{{.Subject}}.
{{.i18n.Tr "mail.repo.transfer.body" $url | Str2html}} {{.i18n.Tr "mail.repo.transfer.body" $url | Str2html}}

@ -11,8 +11,8 @@
</head> </head>
{{$release_url := printf "<a href='%s'>%s</a>" .Release.HTMLURL .Release.TagName}} {{$release_url := printf "<a href='%s'>%s</a>" (.Release.HTMLURL | Escape) (.Release.TagName | Escape) }}
{{$repo_url := printf "<a href='%s'>%s</a>" .Release.Repo.HTMLURL .Release.Repo.FullName}} {{$repo_url := printf "<a href='%s'>%s</a>" (.Release.Repo.HTMLURL | Escape) (.Release.Repo.FullName | Escape)}}
<body> <body>
<p> <p>
{{.i18n.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url | Str2html}} {{.i18n.Tr "mail.release.new.text" .Release.Publisher.Name $release_url $repo_url | Str2html}}
@ -31,13 +31,11 @@
<br> <br>
{{.i18n.Tr "mail.release.downloads"}} {{.i18n.Tr "mail.release.downloads"}}
<ul> <ul>
{{$tagname := .Release.TagName | EscapePound}}
{{$archive_url := printf "%s%s/%s/archive" AppUrl .Release.Repo.OwnerName .Release.Repo.Name}}
<li> <li>
<a href="{{$archive_url}}/{{$tagname}}.zip" rel="nofollow"><strong>{{.i18n.Tr "mail.release.download.zip"}}</strong></a> <a href="{{.Release.Repo.Link}}/archive/{{.Release.TagName | PathEscapeSegments}}.zip" rel="nofollow"><strong>{{.i18n.Tr "mail.release.download.zip"}}</strong></a>
</li> </li>
<li> <li>
<a href="{{$archive_url}}/{{$tagname}}.tar.gz" rel="nofollow"><strong>{{.i18n.Tr "mail.release.download.targz"}}</strong></a> <a href="{{.Release.Repo.Link}}/archive/{{.Release.TagName | PathEscapeSegments}}.tar.gz" rel="nofollow"><strong>{{.i18n.Tr "mail.release.download.targz"}}</strong></a>
</li> </li>
{{if .Release.Attachments}} {{if .Release.Attachments}}
{{range .Release.Attachments}} {{range .Release.Attachments}}

@ -68,10 +68,10 @@
<div class="ui attached table segment teams"> <div class="ui attached table segment teams">
{{range .Teams}} {{range .Teams}}
<div class="item"> <div class="item">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong class="team-name">{{.Name}}</strong></a> <a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong class="team-name">{{.Name}}</strong></a>
<p class="text grey"> <p class="text grey">
<a href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> · <a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.NumMembers}}</strong> {{$.i18n.Tr "org.lower_members"}}</a> ·
<a href="{{$.OrgLink}}/teams/{{.LowerName}}/repositories"><strong>{{.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a> <a href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/repositories"><strong>{{.NumRepos}}</strong> {{$.i18n.Tr "org.lower_repositories"}}</a>
</p> </p>
</div> </div>
{{end}} {{end}}

@ -9,7 +9,7 @@
{{template "org/team/navbar" .}} {{template "org/team/navbar" .}}
{{if .IsOrganizationOwner}} {{if .IsOrganizationOwner}}
<div class="ui attached segment"> <div class="ui attached segment">
<form class="ui form" id="add-member-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/add" method="post"> <form class="ui form" id="add-member-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/add" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<input type="hidden" name="uid" value="{{.SignedUser.ID}}"> <input type="hidden" name="uid" value="{{.SignedUser.ID}}">
<div class="inline field ui left"> <div class="inline field ui left">
@ -29,7 +29,7 @@
{{if and $.IsOrganizationOwner (not (eq $.SignedUser.ID .ID))}} {{if and $.IsOrganizationOwner (not (eq $.SignedUser.ID .ID))}}
<form> <form>
<button class="ui red button delete-button right" data-modal-id="remove-team-member" <button class="ui red button delete-button right" data-modal-id="remove-team-member"
data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/remove" data-datauid="{{.ID}}" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/remove" data-datauid="{{.ID}}"
data-name="{{.DisplayName}}" data-name="{{.DisplayName}}"
data-data-team-name="{{$.Team.Name}}">{{$.i18n.Tr "org.members.remove"}}</button> data-data-team-name="{{$.Team.Name}}">{{$.i18n.Tr "org.members.remove"}}</button>
</form> </form>

@ -1,4 +1,4 @@
<div class="ui top attached tabular menu"> <div class="ui top attached tabular menu">
<a class="item{{if .PageIsOrgTeamMembers}} active{{end}}" href="{{.OrgLink}}/teams/{{.Team.LowerName}}">{{svg "octicon-person"}} <strong>{{.Team.NumMembers}}</strong>&nbsp; {{$.i18n.Tr "org.lower_members"}}</a> <a class="item{{if .PageIsOrgTeamMembers}} active{{end}}" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}">{{svg "octicon-person"}} <strong>{{.Team.NumMembers}}</strong>&nbsp; {{$.i18n.Tr "org.lower_members"}}</a>
<a class="item{{if .PageIsOrgTeamRepos}} active{{end}}" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/repositories">{{svg "octicon-repo"}} <strong>{{.Team.NumRepos}}</strong>&nbsp; {{$.i18n.Tr "org.lower_repositories"}}</a> <a class="item{{if .PageIsOrgTeamRepos}} active{{end}}" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/repositories">{{svg "octicon-repo"}} <strong>{{.Team.NumRepos}}</strong>&nbsp; {{$.i18n.Tr "org.lower_repositories"}}</a>
</div> </div>

@ -3,7 +3,7 @@
{{template "org/header" .}} {{template "org/header" .}}
<div class="ui middle very relaxed page grid"> <div class="ui middle very relaxed page grid">
<div class="column"> <div class="column">
<form class="ui form" action="{{if .PageIsOrgTeamsNew}}{{.OrgLink}}/teams/new{{else}}{{.OrgLink}}/teams/{{.Team.LowerName}}/edit{{end}}" data-delete-url="{{.OrgLink}}/teams/{{.Team.LowerName}}/delete" method="post"> <form class="ui form" action="{{if .PageIsOrgTeamsNew}}{{.OrgLink}}/teams/new{{else}}{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit{{end}}" data-delete-url="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/delete" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<h3 class="ui top attached header"> <h3 class="ui top attached header">
{{if .PageIsOrgTeamsNew}}{{.i18n.Tr "org.create_new_team"}}{{else}}{{.i18n.Tr "org.teams.settings"}}{{end}} {{if .PageIsOrgTeamsNew}}{{.i18n.Tr "org.create_new_team"}}{{else}}{{.i18n.Tr "org.teams.settings"}}{{end}}
@ -104,7 +104,7 @@
{{else}} {{else}}
<button class="ui green button">{{.i18n.Tr "org.teams.update_settings"}}</button> <button class="ui green button">{{.i18n.Tr "org.teams.update_settings"}}</button>
{{if not (eq .Team.LowerName "owners")}} {{if not (eq .Team.LowerName "owners")}}
<button class="ui red button delete-button" data-url="{{.OrgLink}}/teams/{{.team_name}}/delete">{{.i18n.Tr "org.teams.delete_team"}}</button> <button class="ui red button delete-button" data-url="{{.OrgLink}}/teams/{{.team_name | PathEscape}}/delete">{{.i18n.Tr "org.teams.delete_team"}}</button>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>

@ -11,7 +11,7 @@
{{if $canAddRemove}} {{if $canAddRemove}}
<div class="ui attached segment" id="repo-top-segment"> <div class="ui attached segment" id="repo-top-segment">
<div class="inline ui field left"> <div class="inline ui field left">
<form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/add" method="post"> <form class="ui form" id="add-repo-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/add" method="post">
{{.CsrfTokenHtml}} {{.CsrfTokenHtml}}
<div class="inline field ui left"> <div class="inline field ui left">
<div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search"> <div id="search-repo-box" data-uid="{{.Org.ID}}" class="ui search">
@ -24,9 +24,9 @@
</form> </form>
</div> </div>
<div class="inline ui field right"> <div class="inline ui field right">
<form class="ui form" id="repo-multiple-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/repositories" method="post"> <form class="ui form" id="repo-multiple-form" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/repositories" method="post">
<button class="ui red button delete-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/removeall">{{.i18n.Tr "remove_all"}}</button> <button class="ui red button delete-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/removeall">{{.i18n.Tr "remove_all"}}</button>
<button class="ui green button add-all-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/addall">{{.i18n.Tr "add_all"}}</button> <button class="ui green button add-all-button right" data-url="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/addall">{{.i18n.Tr "add_all"}}</button>
</form> </form>
</div> </div>
</div> </div>
@ -35,12 +35,12 @@
{{range .Team.Repos}} {{range .Team.Repos}}
<div class="item"> <div class="item">
{{if $canAddRemove}} {{if $canAddRemove}}
<form method="post" action="{{$.OrgLink}}/teams/{{$.Team.LowerName}}/action/repo/remove"> <form method="post" action="{{$.OrgLink}}/teams/{{$.Team.LowerName | PathEscape}}/action/repo/remove">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button type="submit" class="ui red small button right" name="repoid" value="{{.ID}}">{{$.i18n.Tr "remove"}}</button> <button type="submit" class="ui red small button right" name="repoid" value="{{.ID}}">{{$.i18n.Tr "remove"}}</button>
</form> </form>
{{end}} {{end}}
<a class="member" href="{{AppSubUrl}}/{{$.Org.Name}}/{{.Name}}"> <a class="member" href="{{$.Org.HomeLink}}/{{.Name | PathEscape}}">
{{if .IsPrivate}} {{if .IsPrivate}}
{{svg "octicon-lock"}} {{svg "octicon-lock"}}
{{else if .IsFork}} {{else if .IsFork}}

@ -5,11 +5,11 @@
{{if .Team.IsMember $.SignedUser.ID}} {{if .Team.IsMember $.SignedUser.ID}}
<form> <form>
<button class="ui red tiny button delete-button" data-modal-id="leave-team-sidebar" <button class="ui red tiny button delete-button" data-modal-id="leave-team-sidebar"
data-url="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/leave" data-datauid="{{$.SignedUser.ID}}" data-url="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Team.Name}}">{{$.i18n.Tr "org.teams.leave"}}</button> data-name="{{.Team.Name}}">{{$.i18n.Tr "org.teams.leave"}}</button>
</form> </form>
{{else if .IsOrganizationOwner}} {{else if .IsOrganizationOwner}}
<form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName}}/action/join"> <form method="post" action="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<input type="hidden" name="page" value="team"/> <input type="hidden" name="page" value="team"/>
<button type="submit" class="ui blue tiny button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.join"}}</button> <button type="submit" class="ui blue tiny button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.join"}}</button>
@ -55,7 +55,7 @@
</div> </div>
{{if .IsOrganizationOwner}} {{if .IsOrganizationOwner}}
<div class="ui bottom attached segment"> <div class="ui bottom attached segment">
<a class="ui teal small button" href="{{.OrgLink}}/teams/{{.Team.LowerName}}/edit">{{svg "octicon-gear"}} {{$.i18n.Tr "org.teams.settings"}}</a> <a class="ui teal small button" href="{{.OrgLink}}/teams/{{.Team.LowerName | PathEscape}}/edit">{{svg "octicon-gear"}} {{$.i18n.Tr "org.teams.settings"}}</a>
</div> </div>
{{end}} {{end}}
</div> </div>

@ -14,16 +14,16 @@
{{range .Teams}} {{range .Teams}}
<div class="column"> <div class="column">
<div class="ui top attached header"> <div class="ui top attached header">
<a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName}}"><strong>{{.Name}}</strong></a> <a class="text black" href="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}"><strong>{{.Name}}</strong></a>
<div class="ui right"> <div class="ui right">
{{if .IsMember $.SignedUser.ID}} {{if .IsMember $.SignedUser.ID}}
<form> <form>
<button class="ui red tiny button delete-button" data-modal-id="leave-team" <button class="ui red tiny button delete-button" data-modal-id="leave-team"
data-url="{{$.OrgLink}}/teams/{{.LowerName}}/action/leave" data-datauid="{{$.SignedUser.ID}}" data-url="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/leave" data-datauid="{{$.SignedUser.ID}}"
data-name="{{.Name}}">{{$.i18n.Tr "org.teams.leave"}}</button> data-name="{{.Name}}">{{$.i18n.Tr "org.teams.leave"}}</button>
</form> </form>
{{else if $.IsOrganizationOwner}} {{else if $.IsOrganizationOwner}}
<form method="post" action="{{$.OrgLink}}/teams/{{.LowerName}}/action/join"> <form method="post" action="{{$.OrgLink}}/teams/{{.LowerName | PathEscape}}/action/join">
{{$.CsrfTokenHtml}} {{$.CsrfTokenHtml}}
<button type="submit" class="ui blue small button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.join"}}</button> <button type="submit" class="ui blue small button" name="uid" value="{{$.SignedUser.ID}}">{{$.i18n.Tr "org.teams.join"}}</button>
</form> </form>

@ -126,7 +126,7 @@
<span class="ui green label">{{$.i18n.Tr "repo.activity.published_release_label"}}</span> <span class="ui green label">{{$.i18n.Tr "repo.activity.published_release_label"}}</span>
{{.TagName}} {{.TagName}}
{{if not .IsTag}} {{if not .IsTag}}
<a class="title" href="{{$.RepoLink}}/src/{{.TagName | EscapePound}}">{{.Title | RenderEmoji}}</a> <a class="title" href="{{$.RepoLink}}/src/{{.TagName | PathEscapeSegments}}">{{.Title | RenderEmoji}}</a>
{{end}} {{end}}
{{TimeSinceUnix .CreatedUnix $.Lang}} {{TimeSinceUnix .CreatedUnix $.Lang}}
</p> </p>

@ -10,12 +10,12 @@
</div> </div>
<div class="file-header-right file-actions df ac"> <div class="file-header-right file-actions df ac">
<div class="ui buttons"> <div class="ui buttons">
<a class="ui tiny button" href="{{EscapePound $.RawFileLink}}">{{.i18n.Tr "repo.file_raw"}}</a> <a class="ui tiny button" href="{{$.RawFileLink}}">{{.i18n.Tr "repo.file_raw"}}</a>
{{if not .IsViewCommit}} {{if not .IsViewCommit}}
<a class="ui tiny button" href="{{.RepoLink}}/src/commit/{{.CommitID}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.file_permalink"}}</a> <a class="ui tiny button" href="{{.RepoLink}}/src/commit/{{.CommitID | PathEscape}}/{{.TreePath | PathEscapeSegments}}">{{.i18n.Tr "repo.file_permalink"}}</a>
{{end}} {{end}}
<a class="ui tiny button" href="{{.RepoLink}}/src/{{EscapePound .BranchNameSubURL}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.normal_view"}}</a> <a class="ui tiny button" href="{{.RepoLink}}/src/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">{{.i18n.Tr "repo.normal_view"}}</a>
<a class="ui tiny button" href="{{.RepoLink}}/commits/{{EscapePound .BranchNameSubURL}}/{{EscapePound .TreePath}}">{{.i18n.Tr "repo.file_history"}}</a> <a class="ui tiny button" href="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/{{.TreePath | PathEscapeSegments}}">{{.i18n.Tr "repo.file_history"}}</a>
</div> </div>
</div> </div>
</h4> </h4>

@ -18,22 +18,22 @@
{{if .IsProtected}} {{if .IsProtected}}
{{svg "octicon-shield-lock"}} {{svg "octicon-shield-lock"}}
{{end}} {{end}}
<a href="{{$.RepoLink}}/src/branch/{{$.DefaultBranch | EscapePound}}">{{$.DefaultBranch}}</a> <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments $.DefaultBranch}}">{{$.DefaultBranch}}</a>
<p class="info df ac my-2">{{svg "octicon-git-commit" 16 "mr-2"}}<a href="{{$.RepoLink}}/commit/{{.Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p> <p class="info df ac my-2">{{svg "octicon-git-commit" 16 "mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p>
{{end}} {{end}}
{{end}} {{end}}
</td> </td>
<td class="right aligned overflow-visible"> <td class="right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}} {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
<div class="ui basic jump button icon poping up show-create-branch-modal" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}" data-variation="tiny inverted" data-branch-from="{{EscapePound $.DefaultBranch}}" data-modal="#create-branch-modal" data-position="top right"> <div class="ui basic jump button icon poping up show-create-branch-modal" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" ($.DefaultBranch)}}" data-variation="tiny inverted" data-branch-from="{{$.DefaultBranch}}" data-branch-from-urlcomponent="{{PathEscapeSegments $.DefaultBranch}}" data-modal="#create-branch-modal" data-position="top right">
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}
</div> </div>
{{end}} {{end}}
<div class="ui basic jump dropdown icon button poping up" data-content="{{$.i18n.Tr "repo.branch.download" ($.DefaultBranch)}}" data-variation="tiny inverted" data-position="top right"> <div class="ui basic jump dropdown icon button poping up" data-content="{{$.i18n.Tr "repo.branch.download" ($.DefaultBranch)}}" data-variation="tiny inverted" data-position="top right">
{{svg "octicon-download"}} {{svg "octicon-download"}}
<div class="menu"> <div class="menu">
<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound $.DefaultBranch}}.zip">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> <a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.zip">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound $.DefaultBranch}}.tar.gz">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> <a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{PathEscapeSegments $.DefaultBranch}}.tar.gz">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div> </div>
</div> </div>
</td> </td>
@ -54,14 +54,14 @@
<tr> <tr>
<td class="six wide"> <td class="six wide">
{{if .IsDeleted}} {{if .IsDeleted}}
<s><a href="{{$.RepoLink}}/src/branch/{{.Name | EscapePound}}">{{.Name}}</a></s> <s><a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a></s>
<p class="info">{{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.i18n.Lang}}</p> <p class="info">{{$.i18n.Tr "repo.branch.deleted_by" .DeletedBranch.DeletedBy.Name}} {{TimeSinceUnix .DeletedBranch.DeletedUnix $.i18n.Lang}}</p>
{{else}} {{else}}
{{if .IsProtected}} {{if .IsProtected}}
{{svg "octicon-shield-lock"}} {{svg "octicon-shield-lock"}}
{{end}} {{end}}
<a href="{{$.RepoLink}}/src/branch/{{.Name | EscapePound}}">{{.Name}}</a> <a href="{{$.RepoLink}}/src/branch/{{PathEscapeSegments .Name}}">{{.Name}}</a>
<p class="info df ac my-2">{{svg "octicon-git-commit" 16 "mr-2"}}<a href="{{$.RepoLink}}/commit/{{.Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p> <p class="info df ac my-2">{{svg "octicon-git-commit" 16 "mr-2"}}<a href="{{$.RepoLink}}/commit/{{PathEscape .Commit.ID.String}}">{{ShortSha .Commit.ID.String}}</a> · <span class="commit-message">{{RenderCommitMessage .Commit.CommitMessage $.RepoLink $.Repository.ComposeMetas}}</span> · {{$.i18n.Tr "org.repo_updated"}} {{TimeSince .Commit.Committer.When $.i18n.Lang}}</p>
{{end}} {{end}}
</td> </td>
<td class="three wide ui"> <td class="three wide ui">
@ -85,13 +85,13 @@
{{svg "octicon-git-pull-request"}} {{$.i18n.Tr "repo.branch.included"}} {{svg "octicon-git-pull-request"}} {{$.i18n.Tr "repo.branch.included"}}
</a> </a>
{{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} {{else if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}"> <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{PathEscape $.Owner.Name}}:{{end}}{{PathEscapeSegments .Name}}">
<button id="new-pull-request" class="ui compact basic button mr-0">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button> <button id="new-pull-request" class="ui compact basic button mr-0">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a> </a>
{{end}} {{end}}
{{else if and .LatestPullRequest.HasMerged .MergeMovedOn}} {{else if and .LatestPullRequest.HasMerged .MergeMovedOn}}
{{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}} {{if and (not .IsDeleted) $.AllowsPulls (gt .CommitsAhead 0)}}
<a href="{{$.RepoLink}}/compare/{{$.DefaultBranch | EscapePound}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | EscapePound}}"> <a href="{{$.RepoLink}}/compare/{{PathEscapeSegments $.DefaultBranch}}...{{if ne $.Repository.Owner.Name $.Owner.Name}}{{$.Owner.Name}}:{{end}}{{.Name | PathEscapeSegments}}">
<button id="new-pull-request" class="ui compact basic button mr-0">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button> <button id="new-pull-request" class="ui compact basic button mr-0">{{if $.CanPull}}{{$.i18n.Tr "repo.pulls.compare_changes"}}{{else}}{{$.i18n.Tr "action.compare_branch"}}{{end}}</button>
</a> </a>
{{end}} {{end}}
@ -108,7 +108,7 @@
</td> </td>
<td class="two wide right aligned overflow-visible"> <td class="two wide right aligned overflow-visible">
{{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}} {{if and $.IsWriter (not $.Repository.IsArchived) (not .IsDeleted)}}
<div class="ui basic jump button icon poping up show-create-branch-modal" data-branch-from="{{EscapePound .Name}}" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" .Name}}" data-variation="tiny inverted" data-position="top right" data-modal="#create-branch-modal" data-name="{{.Name}}"> <div class="ui basic jump button icon poping up show-create-branch-modal" data-branch-from="{{.Name}}" data-branch-from-urlcomponent="{{PathEscapeSegments .Name}}" data-content="{{$.i18n.Tr "repo.branch.new_branch_from" .Name}}" data-variation="tiny inverted" data-position="top right" data-modal="#create-branch-modal" data-name="{{.Name}}">
{{svg "octicon-git-branch"}} {{svg "octicon-git-branch"}}
</div> </div>
{{end}} {{end}}
@ -116,16 +116,16 @@
<div class="ui basic jump dropdown icon button poping up" data-content="{{$.i18n.Tr "repo.branch.download" (.Name)}}" data-variation="tiny inverted" data-position="top right"> <div class="ui basic jump dropdown icon button poping up" data-content="{{$.i18n.Tr "repo.branch.download" (.Name)}}" data-variation="tiny inverted" data-position="top right">
{{svg "octicon-download"}} {{svg "octicon-download"}}
<div class="menu"> <div class="menu">
<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound .Name}}.zip">{{svg "octicon-file-zip"}}&nbsp;ZIP</a> <a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.zip">{{svg "octicon-file-zip"}}&nbsp;ZIP</a>
<a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{EscapePound .Name}}.tar.gz">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a> <a class="item archive-link" data-url="{{$.RepoLink}}/archive/{{PathEscapeSegments .Name}}.tar.gz">{{svg "octicon-file-zip"}}&nbsp;TAR.GZ</a>
</div> </div>
</div> </div>
{{end}} {{end}}
{{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}} {{if and $.IsWriter (not $.IsMirror) (not $.Repository.IsArchived) (not .IsProtected)}}
{{if .IsDeleted}} {{if .IsDeleted}}
<a class="ui basic jump button icon poping up undo-button" href data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID | urlquery}}&name={{.DeletedBranch.Name | urlquery}}" data-content="{{$.i18n.Tr "repo.branch.restore" (.Name)}}" data-variation="tiny inverted" data-position="top right"><span class="text blue">{{svg "octicon-reply"}}</span></a> <a class="ui basic jump button icon poping up undo-button" href data-url="{{$.Link}}/restore?branch_id={{.DeletedBranch.ID}}&name={{PathEscapeSegments .DeletedBranch.Name}}" data-content="{{$.i18n.Tr "repo.branch.restore" (.Name)}}" data-variation="tiny inverted" data-position="top right"><span class="text blue">{{svg "octicon-reply"}}</span></a>
{{else}} {{else}}
<a class="ui basic jump button icon poping up delete-button delete-branch-button" href data-url="{{$.Link}}/delete?name={{.Name | urlquery}}" data-content="{{$.i18n.Tr "repo.branch.delete" (.Name)}}" data-variation="tiny inverted" data-position="top right" data-name="{{.Name}}"> <a class="ui basic jump button icon poping up delete-button delete-branch-button" href data-url="{{$.Link}}/delete?name={{PathEscapeSegments .Name}}" data-content="{{$.i18n.Tr "repo.branch.delete" (.Name)}}" data-variation="tiny inverted" data-position="top right" data-name="{{.Name}}">
{{svg "octicon-trash"}} {{svg "octicon-trash"}}
</a> </a>
{{end}} {{end}}

@ -17,14 +17,14 @@
<div class="data" style="display: none" data-mode="{{if .root.IsViewTag}}tags{{else}}branches{{end}}"> <div class="data" style="display: none" data-mode="{{if .root.IsViewTag}}tags{{else}}branches{{end}}">
{{if $showBranchesInDropdown}} {{if $showBranchesInDropdown}}
{{range .root.Branches}} {{range .root.Branches}}
<div class="item branch {{if eq $.root.BranchName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{EscapePound .}}{{if $.root.TreePath}}/{{EscapePound $.root.TreePath}}{{end}}">{{.}}</div> <div class="item branch {{if eq $.root.BranchName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/branch/{{PathEscapeSegments .}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}">{{.}}</div>
{{end}} {{end}}
{{end}} {{end}}
{{range .root.Tags}} {{range .root.Tags}}
{{if $release}} {{if $release}}
<div class="item tag {{if eq $release.TagName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/compare/{{EscapePound .}}...{{if $release.IsDraft}}{{EscapePound $release.Target}}{{else}}{{if $release.TagName}}{{EscapePound $release.TagName}}{{else}}{{EscapePound $release.Sha1}}{{end}}{{end}}">{{.}}</div> <div class="item tag {{if eq $release.TagName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/compare/{{PathEscapeSegments .}}...{{if $release.IsDraft}}{{PathEscapeSegments $release.Target}}{{else}}{{if $release.TagName}}{{PathEscapeSegments $release.TagName}}{{else}}{{PathEscapeSegments $release.Sha1}}{{end}}{{end}}">{{.}}</div>
{{else}} {{else}}
<div class="item tag {{if eq $.root.BranchName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{EscapePound .}}{{if $.root.TreePath}}/{{EscapePound $.root.TreePath}}{{end}}">{{.}}</div> <div class="item tag {{if eq $.root.BranchName .}}selected{{end}}" data-url="{{$.root.RepoLink}}/{{if $.root.PageIsCommits}}commits{{else}}src{{end}}/tag/{{PathEscapeSegments .}}{{if $.root.TreePath}}/{{PathEscapeSegments $.root.TreePath}}{{end}}">{{.}}</div>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>
@ -71,7 +71,7 @@
{{end}} {{end}}
</div> </div>
</a> </a>
<form ref="newBranchForm" action="{{.root.RepoLink}}/branches/_new/{{EscapePound .root.BranchNameSubURL}}" method="post"> <form ref="newBranchForm" action="{{.root.RepoLink}}/branches/_new/{{.root.BranchNameSubURL}}" method="post">
{{.root.CsrfTokenHtml}} {{.root.CsrfTokenHtml}}
<input type="hidden" name="new_branch_name" v-model="searchTerm"> <input type="hidden" name="new_branch_name" v-model="searchTerm">
<input type="hidden" name="create_tag" v-model="createTag"> <input type="hidden" name="create_tag" v-model="createTag">

@ -19,7 +19,7 @@
{{end}} {{end}}
<div class="ui top attached info clearing segment {{$class}}"> <div class="ui top attached info clearing segment {{$class}}">
{{if not $.PageIsWiki}} {{if not $.PageIsWiki}}
<a class="ui floated right blue tiny button" href="{{EscapePound .SourcePath}}"> <a class="ui floated right blue tiny button" href="{{.SourcePath}}">
{{.i18n.Tr "repo.diff.browse_source"}} {{.i18n.Tr "repo.diff.browse_source"}}
</a> </a>
{{end}} {{end}}
@ -72,9 +72,9 @@
<div class="item"> <div class="item">
{{range .Parents}} {{range .Parents}}
{{if $.PageIsWiki}} {{if $.PageIsWiki}}
<a class="ui blue sha label" href="{{$.RepoLink}}/wiki/commit/{{.}}">{{ShortSha .}}</a> <a class="ui blue sha label" href="{{$.RepoLink}}/wiki/commit/{{PathEscape .}}">{{ShortSha .}}</a>
{{else}} {{else}}
<a class="ui blue sha label" href="{{$.RepoLink}}/commit/{{.}}">{{ShortSha .}}</a> <a class="ui blue sha label" href="{{$.RepoLink}}/commit/{{PathEscape .}}">{{ShortSha .}}</a>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>

@ -17,7 +17,7 @@
{{if .User.FullName}} {{if .User.FullName}}
{{$userName = .User.FullName}} {{$userName = .User.FullName}}
{{end}} {{end}}
{{avatar .User 28 "mr-2"}}<a href="{{AppSubUrl}}/{{.User.Name}}">{{$userName}}</a> {{avatar .User 28 "mr-2"}}<a href="{{.User.HomeLink}}">{{$userName}}</a>
{{else}} {{else}}
{{avatarByEmail .Author.Email .Author.Name 28 "mr-2"}} {{avatarByEmail .Author.Email .Author.Name 28 "mr-2"}}
{{$userName}} {{$userName}}
@ -40,9 +40,9 @@
{{end}} {{end}}
{{end}} {{end}}
{{if $.PageIsWiki}} {{if $.PageIsWiki}}
<a href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/wiki/commit/{{.ID}}" rel="nofollow" class="{{$class}}"> <a href="{{$.RepoLink}}/wiki/commit/{{.ID}}" rel="nofollow" class="{{$class}}">
{{else if $.Reponame}} {{else if $.Reponame}}
<a href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.ID}}" rel="nofollow" class="{{$class}}"> <a href="{{$.RepoLink}}/commit/{{.ID}}" rel="nofollow" class="{{$class}}">
{{else}} {{else}}
<span class="{{$class}}"> <span class="{{$class}}">
{{end}} {{end}}
@ -61,7 +61,7 @@
{{if $.PageIsWiki}} {{if $.PageIsWiki}}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | RenderEmoji}}</span> <span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{.Summary | RenderEmoji}}</span>
{{else }} {{else }}
{{ $commitLink:= printf "%s/%s/%s/commit/%s" AppSubUrl $.Username $.Reponame .ID }} {{ $commitLink:= printf "%s/commit/%s" $.RepoLink (PathEscape .ID.String) }}
<span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject .Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span> <span class="commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject .Message $.RepoLink $commitLink $.Repository.ComposeMetas}}</span>
{{end}} {{end}}
</span> </span>

@ -6,7 +6,7 @@
<div class="singular-commit" id="{{$tag}}"> <div class="singular-commit" id="{{$tag}}">
<span class="badge badge-commit">{{svg "octicon-git-commit"}}</span> <span class="badge badge-commit">{{svg "octicon-git-commit"}}</span>
{{if .User}} {{if .User}}
<a href="{{AppSubUrl}}/{{.User.Name}}"> <a href="{.User.HomeLink}}">
{{avatar .User}} {{avatar .User}}
</a> </a>
{{else}} {{else}}
@ -31,7 +31,7 @@
{{end}} {{end}}
{{end}} {{end}}
{{if $.comment.Issue.PullRequest.BaseRepo.Name}} {{if $.comment.Issue.PullRequest.BaseRepo.Name}}
<a href="{{AppSubUrl}}/{{$.comment.Issue.PullRequest.BaseRepo.OwnerName}}/{{$.comment.Issue.PullRequest.BaseRepo.Name}}/commit/{{.ID}}" rel="nofollow" class="{{$class}}"> <a href="{{$.comment.Issue.PullRequest.BaseRepo.Link}}/commit/{{PathEscape .ID.String}}" rel="nofollow" class="{{$class}}">
{{else}} {{else}}
<span class="{{$class}}"> <span class="{{$class}}">
{{end}} {{end}}
@ -46,7 +46,7 @@
{{end}} {{end}}
</span> </span>
{{ $commitLink:= printf "%s/%s/%s/commit/%s" AppSubUrl $.comment.Issue.PullRequest.BaseRepo.OwnerName $.comment.Issue.PullRequest.BaseRepo.Name .ID }} {{ $commitLink:= printf "%s/commit/%s" $.comment.Issue.PullRequest.BaseRepo.Link (PathEscape .ID.String) }}
<span class="mono commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $commitLink $.comment.Issue.PullRequest.BaseRepo.ComposeMetas}}</span> <span class="mono commit-summary {{if gt .ParentCount 1}} grey text{{end}}" title="{{.Summary}}">{{RenderCommitMessageLinkSubject .Message ($.comment.Issue.PullRequest.BaseRepo.Link|Escape) $commitLink $.comment.Issue.PullRequest.BaseRepo.ComposeMetas}}</span>
{{if IsMultilineCommitMessage .Message}} {{if IsMultilineCommitMessage .Message}}
<button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button> <button class="basic compact mini ui icon button commit-button"><i class="ellipsis horizontal icon"></i></button>

@ -11,7 +11,7 @@
</div> </div>
<div class="commits-table-right df ac"> <div class="commits-table-right df ac">
{{if .PageIsCommits}} {{if .PageIsCommits}}
<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL | EscapePound}}/search"> <form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL}}/search">
<div class="ui tiny search input"> <div class="ui tiny search input">
<input name="q" placeholder="{{.i18n.Tr "repo.commits.search"}}" value="{{.Keyword}}" autofocus> <input name="q" placeholder="{{.i18n.Tr "repo.commits.search"}}" value="{{.Keyword}}" autofocus>
</div> </div>
@ -23,9 +23,9 @@
<button class="ui primary tiny button mr-0 poping up" data-panel="#add-deploy-key-panel" data-content={{.i18n.Tr "repo.commits.search.tooltip"}}>{{.i18n.Tr "repo.commits.find"}}</button> <button class="ui primary tiny button mr-0 poping up" data-panel="#add-deploy-key-panel" data-content={{.i18n.Tr "repo.commits.search.tooltip"}}>{{.i18n.Tr "repo.commits.find"}}</button>
</form> </form>
{{else if .IsDiffCompare}} {{else if .IsDiffCompare}}
<a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID}}" class="ui green sha label">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a> <a href="{{$.CommitRepoLink}}/commit/{{.BeforeCommitID | PathEscape}}" class="ui green sha label">{{if not .BaseIsCommit}}{{if .BaseIsBranch}}{{svg "octicon-git-branch"}}{{else if .BaseIsTag}}{{svg "octicon-tag"}}{{end}}{{.BaseBranch}}{{else}}{{ShortSha .BaseBranch}}{{end}}</a>
... ...
<a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID}}" class="ui green sha label">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}{{svg "octicon-git-branch"}}{{else if .HeadIsTag}}{{svg "octicon-tag"}}{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</a> <a href="{{$.CommitRepoLink}}/commit/{{.AfterCommitID | PathEscape}}" class="ui green sha label">{{if not .HeadIsCommit}}{{if .HeadIsBranch}}{{svg "octicon-git-branch"}}{{else if .HeadIsTag}}{{svg "octicon-tag"}}{{end}}{{.HeadBranch}}{{else}}{{ShortSha .HeadBranch}}{{end}}</a>
{{end}} {{end}}
</div> </div>
</h4> </h4>

@ -11,7 +11,7 @@
{{template "base/alert" .}} {{template "base/alert" .}}
{{if not $.DisableMigrations}} {{if not $.DisableMigrations}}
<p class="ui center">{{.i18n.Tr "repo.new_repo_helper" (printf "%s%s" AppSubUrl "/repo/migrate") | Safe}}</p> <p class="ui center">{{.i18n.Tr "repo.new_repo_helper" ((printf "%s%s" AppSubUrl "/repo/migrate")|Escape) | Safe}}</p>
{{end}} {{end}}
{{if not .CanCreateRepo}} {{if not .CanCreateRepo}}

@ -4,17 +4,17 @@
{{if eq .GetType 4}} {{if eq .GetType 4}}
<td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}"> <td class="lines-num lines-num-old" data-line-num="{{if $line.LeftIdx}}{{$line.LeftIdx}}{{end}}">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{$.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=down" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</a> </a>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{$.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=up" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</a> </a>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{$.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=split&direction=" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</a> </a>
{{end}} {{end}}
@ -36,17 +36,17 @@
{{if eq .GetType 4}} {{if eq .GetType 4}}
<td colspan="2" class="lines-num"> <td colspan="2" class="lines-num">
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 5) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{$.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=down" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-down"}} {{svg "octicon-fold-down"}}
</a> </a>
{{end}} {{end}}
{{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }} {{if or (eq $line.GetExpandDirection 3) (eq $line.GetExpandDirection 4) }}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{$.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=up" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold-up"}} {{svg "octicon-fold-up"}}
</a> </a>
{{end}} {{end}}
{{if eq $line.GetExpandDirection 2}} {{if eq $line.GetExpandDirection 2}}
<a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{$.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=" data-anchor="{{$.Anchor}}"> <a role="button" class="blob-excerpt" data-url="{{$.RepoLink}}/blob_excerpt/{{PathEscape $.AfterCommitID}}" data-query="{{$line.GetBlobExcerptQuery}}&style=unified&direction=" data-anchor="{{$.Anchor}}">
{{svg "octicon-fold"}} {{svg "octicon-fold"}}
</a> </a>
{{end}} {{end}}

@ -96,9 +96,9 @@
{{end}} {{end}}
{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}} {{if and (not $file.IsSubmodule) (not $.PageIsWiki)}}
{{if $file.IsDeleted}} {{if $file.IsDeleted}}
<a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.BeforeSourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> <a class="ui basic tiny button" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
{{else}} {{else}}
<a class="ui basic tiny button" rel="nofollow" href="{{EscapePound $.SourcePath}}/{{EscapePound .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a> <a class="ui basic tiny button" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{$.i18n.Tr "repo.diff.view_file"}}</a>
{{end}} {{end}}
{{end}} {{end}}
</div> </div>

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save