Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 67 additions & 17 deletions modules/markup/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,35 +126,82 @@ func URLJoin(base string, elems ...string) string {
return u.String()
}

// RenderIssueIndexPatternOptions options for RenderIssueIndexPattern function
type RenderIssueIndexPatternOptions struct {
// url to which non-special formatting should be linked. If empty,
// no such links will be added
DefaultURL string
URLPrefix string
Metas map[string]string
}

// addText add text to the given buffer, adding a link to the default url
// if appropriate
func (opts RenderIssueIndexPatternOptions) addText(text []byte, buf *bytes.Buffer) {
if len(text) == 0 {
return
} else if len(opts.DefaultURL) == 0 {
buf.Write(text)
return
}
buf.WriteString(`<a rel="nofollow" href="`)
buf.WriteString(opts.DefaultURL)
buf.WriteString(`">`)
buf.Write(text)
buf.WriteString(`</a>`)
}

// RenderIssueIndexPattern renders issue indexes to corresponding links.
func RenderIssueIndexPattern(rawBytes []byte, urlPrefix string, metas map[string]string) []byte {
urlPrefix = cutoutVerbosePrefix(urlPrefix)
func RenderIssueIndexPattern(rawBytes []byte, opts RenderIssueIndexPatternOptions) []byte {
opts.URLPrefix = cutoutVerbosePrefix(opts.URLPrefix)

pattern := IssueNumericPattern
if metas["style"] == IssueNameStyleAlphanumeric {
if opts.Metas["style"] == IssueNameStyleAlphanumeric {
pattern = IssueAlphanumericPattern
}

ms := pattern.FindAll(rawBytes, -1)
for _, m := range ms {
if m[0] == ' ' || m[0] == '(' {
m = m[1:] // ignore leading space or opening parentheses
var buf bytes.Buffer
remainder := rawBytes
for {
indices := pattern.FindIndex(remainder)
if indices == nil || len(indices) < 2 {
opts.addText(remainder, &buf)
return buf.Bytes()
}
var link string
if metas == nil {
link = fmt.Sprintf(`<a href="%s">%s</a>`, URLJoin(urlPrefix, "issues", string(m[1:])), m)
startIndex := indices[0]
endIndex := indices[1]
opts.addText(remainder[:startIndex], &buf)
if remainder[startIndex] == '(' || remainder[startIndex] == ' ' {
buf.WriteByte(remainder[startIndex])
startIndex++
}
if opts.Metas == nil {
buf.WriteString(`<a href="`)
buf.WriteString(URLJoin(
opts.URLPrefix, "issues", string(remainder[startIndex+1:endIndex])))
buf.WriteString(`">`)
buf.Write(remainder[startIndex:endIndex])
buf.WriteString(`</a>`)
} else {
// Support for external issue tracker
if metas["style"] == IssueNameStyleAlphanumeric {
metas["index"] = string(m)
buf.WriteString(`<a href="`)
if opts.Metas["style"] == IssueNameStyleAlphanumeric {
opts.Metas["index"] = string(remainder[startIndex:endIndex])
} else {
metas["index"] = string(m[1:])
opts.Metas["index"] = string(remainder[startIndex+1 : endIndex])
}
link = fmt.Sprintf(`<a href="%s">%s</a>`, com.Expand(metas["format"], metas), m)
buf.WriteString(com.Expand(opts.Metas["format"], opts.Metas))
buf.WriteString(`">`)
buf.Write(remainder[startIndex:endIndex])
buf.WriteString(`</a>`)
}
rawBytes = bytes.Replace(rawBytes, m, []byte(link), 1)
if endIndex < len(remainder) &&
(remainder[endIndex] == ')' || remainder[endIndex] == ' ') {
buf.WriteByte(remainder[endIndex])
endIndex++
}
remainder = remainder[endIndex:]
}
return rawBytes
}

// IsSameDomain checks if given url string has the same hostname as current Gitea instance
Expand Down Expand Up @@ -432,7 +479,10 @@ func RenderSpecialLink(rawBytes []byte, urlPrefix string, metas map[string]strin

rawBytes = RenderFullIssuePattern(rawBytes)
rawBytes = RenderShortLinks(rawBytes, urlPrefix, false, isWikiMarkdown)
rawBytes = RenderIssueIndexPattern(rawBytes, urlPrefix, metas)
rawBytes = RenderIssueIndexPattern(rawBytes, RenderIssueIndexPatternOptions{
URLPrefix: urlPrefix,
Metas: metas,
})
rawBytes = RenderCrossReferenceIssueIndexPattern(rawBytes, urlPrefix, metas)
rawBytes = renderFullSha1Pattern(rawBytes, urlPrefix)
rawBytes = renderSha1CurrentPattern(rawBytes, urlPrefix)
Expand Down
40 changes: 31 additions & 9 deletions modules/markup/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,12 @@ func link(href, contents string) string {
return fmt.Sprintf("<a href=\"%s\">%s</a>", href, contents)
}

func testRenderIssueIndexPattern(t *testing.T, input, expected string, metas map[string]string) {
assert.Equal(t, expected,
string(RenderIssueIndexPattern([]byte(input), AppSubURL, metas)))
func testRenderIssueIndexPattern(t *testing.T, input, expected string, opts RenderIssueIndexPatternOptions) {
if len(opts.URLPrefix) == 0 {
opts.URLPrefix = AppSubURL
}
actual := string(RenderIssueIndexPattern([]byte(input), opts))
assert.Equal(t, expected, actual)
}

func TestURLJoin(t *testing.T) {
Expand Down Expand Up @@ -88,8 +91,8 @@ func TestURLJoin(t *testing.T) {
func TestRender_IssueIndexPattern(t *testing.T) {
// numeric: render inputs without valid mentions
test := func(s string) {
testRenderIssueIndexPattern(t, s, s, nil)
testRenderIssueIndexPattern(t, s, s, numericMetas)
testRenderIssueIndexPattern(t, s, s, RenderIssueIndexPatternOptions{})
testRenderIssueIndexPattern(t, s, s, RenderIssueIndexPatternOptions{Metas: numericMetas})
}

// should not render anything when there are no mentions
Expand Down Expand Up @@ -123,13 +126,13 @@ func TestRender_IssueIndexPattern2(t *testing.T) {
links[i] = numericIssueLink(URLJoin(setting.AppSubURL, "issues"), index)
}
expectedNil := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNil, nil)
testRenderIssueIndexPattern(t, s, expectedNil, RenderIssueIndexPatternOptions{})

for i, index := range indices {
links[i] = numericIssueLink("https://someurl.com/someUser/someRepo/", index)
}
expectedNum := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expectedNum, numericMetas)
testRenderIssueIndexPattern(t, s, expectedNum, RenderIssueIndexPatternOptions{Metas: numericMetas})
}

// should render freestanding mentions
Expand All @@ -155,7 +158,7 @@ func TestRender_IssueIndexPattern3(t *testing.T) {

// alphanumeric: render inputs without valid mentions
test := func(s string) {
testRenderIssueIndexPattern(t, s, s, alphanumericMetas)
testRenderIssueIndexPattern(t, s, s, RenderIssueIndexPatternOptions{Metas: alphanumericMetas})
}
test("")
test("this is a test")
Expand Down Expand Up @@ -187,13 +190,32 @@ func TestRender_IssueIndexPattern4(t *testing.T) {
links[i] = alphanumIssueLink("https://someurl.com/someUser/someRepo/", name)
}
expected := fmt.Sprintf(expectedFmt, links...)
testRenderIssueIndexPattern(t, s, expected, alphanumericMetas)
testRenderIssueIndexPattern(t, s, expected, RenderIssueIndexPatternOptions{Metas: alphanumericMetas})
}
test("OTT-1234 test", "%s test", "OTT-1234")
test("test T-12 issue", "test %s issue", "T-12")
test("test issue ABCDEFGHIJ-1234567890", "test issue %s", "ABCDEFGHIJ-1234567890")
}

func TestRenderIssueIndexPatternWithDefaultURL(t *testing.T) {
setting.AppURL = AppURL
setting.AppSubURL = AppSubURL

test := func(input string, expected string) {
testRenderIssueIndexPattern(t, input, expected, RenderIssueIndexPatternOptions{
DefaultURL: AppURL,
})
}
test("hello #123 world",
fmt.Sprintf(`<a rel="nofollow" href="%s">hello</a> `, AppURL)+
fmt.Sprintf(`<a href="%sissues/123">#123</a> `, AppSubURL)+
fmt.Sprintf(`<a rel="nofollow" href="%s">world</a>`, AppURL))
test("hello (#123) world",
fmt.Sprintf(`<a rel="nofollow" href="%s">hello </a>`, AppURL)+
fmt.Sprintf(`(<a href="%sissues/123">#123</a>)`, AppSubURL)+
fmt.Sprintf(`<a rel="nofollow" href="%s"> world</a>`, AppURL))
}

func TestRender_AutoLink(t *testing.T) {
setting.AppURL = AppURL
setting.AppSubURL = AppSubURL
Expand Down
42 changes: 23 additions & 19 deletions modules/templates/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,8 @@ func NewFuncMap() []template.FuncMap {
"EscapePound": func(str string) string {
return strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(str)
},
"RenderCommitMessage": RenderCommitMessage,
"RenderCommitMessage": RenderCommitMessage,
"RenderCommitMessageLink": RenderCommitMessageLink,
"ThemeColorMetaTag": func() string {
return setting.UI.ThemeColorMetaTag
},
Expand Down Expand Up @@ -252,28 +253,31 @@ func ReplaceLeft(s, old, new string) string {
}

// RenderCommitMessage renders commit message with XSS-safe and special links.
func RenderCommitMessage(full bool, msg, urlPrefix string, metas map[string]string) template.HTML {
func RenderCommitMessage(msg, urlPrefix string, metas map[string]string) template.HTML {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed the full parameter, because it was always set to false.

return renderCommitMessage(msg, markup.RenderIssueIndexPatternOptions{
URLPrefix: urlPrefix,
Metas: metas,
})
}

// RenderCommitMessageLink renders commit message as a XXS-safe link to the provided
// default url, handling for special links.
func RenderCommitMessageLink(msg, urlPrefix string, urlDefault string, metas map[string]string) template.HTML {
return renderCommitMessage(msg, markup.RenderIssueIndexPatternOptions{
DefaultURL: urlDefault,
URLPrefix: urlPrefix,
Metas: metas,
})
}

func renderCommitMessage(msg string, opts markup.RenderIssueIndexPatternOptions) template.HTML {
cleanMsg := template.HTMLEscapeString(msg)
fullMessage := string(markup.RenderIssueIndexPattern([]byte(cleanMsg), urlPrefix, metas))
fullMessage := string(markup.RenderIssueIndexPattern([]byte(cleanMsg), opts))
msgLines := strings.Split(strings.TrimSpace(fullMessage), "\n")
numLines := len(msgLines)
if numLines == 0 {
if len(msgLines) == 0 {
return template.HTML("")
} else if !full {
return template.HTML(msgLines[0])
} else if numLines == 1 || (numLines >= 2 && len(msgLines[1]) == 0) {
// First line is a header, standalone or followed by empty line
header := fmt.Sprintf("<h3>%s</h3>", msgLines[0])
if numLines >= 2 {
fullMessage = header + fmt.Sprintf("\n<pre>%s</pre>", strings.Join(msgLines[2:], "\n"))
} else {
fullMessage = header
}
} else {
// Non-standard git message, there is no header line
fullMessage = fmt.Sprintf("<h4>%s</h4>", strings.Join(msgLines, "<br>"))
}
return template.HTML(fullMessage)
return template.HTML(msgLines[0])
}

// Actioner describes an action
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/commits_table.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
</a>
</td>
<td class="message collapsing">
<span class="has-emoji{{if gt .ParentCount 1}} grey text{{end}}">{{RenderCommitMessage false .Summary $.RepoLink $.Repository.ComposeMetas}}</span>
<span class="has-emoji{{if gt .ParentCount 1}} grey text{{end}}">{{RenderCommitMessage .Summary $.RepoLink $.Repository.ComposeMetas}}</span>
{{template "repo/commit_status" .Status}}
</td>
<td class="grey text right aligned">{{TimeSince .Author.When $.Lang}}</td>
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/diff/page.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
<a class="ui floated right blue tiny button" href="{{EscapePound .SourcePath}}">
{{.i18n.Tr "repo.diff.browse_source"}}
</a>
<h3>{{RenderCommitMessage false .Commit.Message $.RepoLink $.Repository.ComposeMetas}}{{template "repo/commit_status" .CommitStatus}}</h3>
<h3>{{RenderCommitMessage .Commit.Message $.RepoLink $.Repository.ComposeMetas}}{{template "repo/commit_status" .CommitStatus}}</h3>
</div>
<div class="ui attached info segment {{if .Commit.Signature}} isSigned {{if .Verification.Verified }} isVerified {{end}}{{end}}">
{{if .Author}}
Expand Down
2 changes: 1 addition & 1 deletion templates/repo/graph.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<a href="{{AppSubUrl}}/{{$.Username}}/{{$.Reponame}}/commit/{{.Rev}}">{{ .ShortRev}}</a>
</code>
<strong> {{.Branch}}</strong>
<em>{{RenderCommitMessage false .Subject $.RepoLink $.Repository.ComposeMetas}}</em> by
<em>{{RenderCommitMessage .Subject $.RepoLink $.Repository.ComposeMetas}}</em> by
<span class="author">
{{.Author}}
</span>
Expand Down
6 changes: 2 additions & 4 deletions templates/repo/view_list.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
</div>
{{end}}
</a>
<span class="grey has-emoji">{{RenderCommitMessage false .LatestCommit.Summary .RepoLink $.Repository.ComposeMetas}}
<span class="grey has-emoji">{{RenderCommitMessage .LatestCommit.Summary .RepoLink $.Repository.ComposeMetas}}
{{template "repo/commit_status" .LatestCommitStatus}}</span>
</th>
<th class="nine wide">
Expand Down Expand Up @@ -75,9 +75,7 @@
</td>
{{end}}
<td class="message collapsing has-emoji">
<a rel="nofollow" href="{{$.RepoLink}}/commit/{{$commit.ID}}">
{{RenderCommitMessage false $commit.Summary $.RepoLink $.Repository.ComposeMetas}}
</a>
{{RenderCommitMessageLink $commit.Summary $.RepoLink (print $.RepoLink "/commit/" $commit.ID) $.Repository.ComposeMetas}}
</td>
<td class="text grey right age">{{TimeSince $commit.Committer.When $.Lang}}</td>
</tr>
Expand Down