code.gitea.io/gitea@v1.19.3/modules/markup/html_test.go (about)

     1  // Copyright 2017 The Gitea Authors. All rights reserved.
     2  // SPDX-License-Identifier: MIT
     3  
     4  package markup_test
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  	"os"
    10  	"strings"
    11  	"testing"
    12  
    13  	"code.gitea.io/gitea/modules/emoji"
    14  	"code.gitea.io/gitea/modules/git"
    15  	"code.gitea.io/gitea/modules/log"
    16  	. "code.gitea.io/gitea/modules/markup"
    17  	"code.gitea.io/gitea/modules/markup/markdown"
    18  	"code.gitea.io/gitea/modules/setting"
    19  	"code.gitea.io/gitea/modules/util"
    20  
    21  	"github.com/stretchr/testify/assert"
    22  )
    23  
    24  var localMetas = map[string]string{
    25  	"user":     "gogits",
    26  	"repo":     "gogs",
    27  	"repoPath": "../../tests/gitea-repositories-meta/user13/repo11.git/",
    28  }
    29  
    30  func TestMain(m *testing.M) {
    31  	setting.InitProviderAllowEmpty()
    32  	setting.LoadCommonSettings()
    33  	if err := git.InitSimple(context.Background()); err != nil {
    34  		log.Fatal("git init failed, err: %v", err)
    35  	}
    36  	os.Exit(m.Run())
    37  }
    38  
    39  func TestRender_Commits(t *testing.T) {
    40  	setting.AppURL = TestAppURL
    41  	test := func(input, expected string) {
    42  		buffer, err := RenderString(&RenderContext{
    43  			Ctx:          git.DefaultContext,
    44  			RelativePath: ".md",
    45  			URLPrefix:    TestRepoURL,
    46  			Metas:        localMetas,
    47  		}, input)
    48  		assert.NoError(t, err)
    49  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
    50  	}
    51  
    52  	sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
    53  	repo := TestRepoURL
    54  	commit := util.URLJoin(repo, "commit", sha)
    55  	tree := util.URLJoin(repo, "tree", sha, "src")
    56  
    57  	file := util.URLJoin(repo, "commit", sha, "example.txt")
    58  	fileWithExtra := file + ":"
    59  	fileWithHash := file + "#L2"
    60  	fileWithHasExtra := file + "#L2:"
    61  	commitCompare := util.URLJoin(repo, "compare", sha+"..."+sha)
    62  	commitCompareWithHash := commitCompare + "#L2"
    63  
    64  	test(sha, `<p><a href="`+commit+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    65  	test(sha[:7], `<p><a href="`+commit[:len(commit)-(40-7)]+`" rel="nofollow"><code>65f1bf2</code></a></p>`)
    66  	test(sha[:39], `<p><a href="`+commit[:len(commit)-(40-39)]+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    67  	test(commit, `<p><a href="`+commit+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    68  	test(tree, `<p><a href="`+tree+`" rel="nofollow"><code>65f1bf27bc/src</code></a></p>`)
    69  
    70  	test(file, `<p><a href="`+file+`" rel="nofollow"><code>65f1bf27bc/example.txt</code></a></p>`)
    71  	test(fileWithExtra, `<p><a href="`+file+`" rel="nofollow"><code>65f1bf27bc/example.txt</code></a>:</p>`)
    72  	test(fileWithHash, `<p><a href="`+fileWithHash+`" rel="nofollow"><code>65f1bf27bc/example.txt (L2)</code></a></p>`)
    73  	test(fileWithHasExtra, `<p><a href="`+fileWithHash+`" rel="nofollow"><code>65f1bf27bc/example.txt (L2)</code></a>:</p>`)
    74  	test(commitCompare, `<p><a href="`+commitCompare+`" rel="nofollow"><code>65f1bf27bc...65f1bf27bc</code></a></p>`)
    75  	test(commitCompareWithHash, `<p><a href="`+commitCompareWithHash+`" rel="nofollow"><code>65f1bf27bc...65f1bf27bc (L2)</code></a></p>`)
    76  
    77  	test("commit "+sha, `<p>commit <a href="`+commit+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    78  	test("/home/gitea/"+sha, "<p>/home/gitea/"+sha+"</p>")
    79  	test("deadbeef", `<p>deadbeef</p>`)
    80  	test("d27ace93", `<p>d27ace93</p>`)
    81  	test(sha[:14]+".x", `<p>`+sha[:14]+`.x</p>`)
    82  
    83  	expected14 := `<a href="` + commit[:len(commit)-(40-14)] + `" rel="nofollow"><code>` + sha[:10] + `</code></a>`
    84  	test(sha[:14]+".", `<p>`+expected14+`.</p>`)
    85  	test(sha[:14]+",", `<p>`+expected14+`,</p>`)
    86  	test("["+sha[:14]+"]", `<p>[`+expected14+`]</p>`)
    87  }
    88  
    89  func TestRender_CrossReferences(t *testing.T) {
    90  	setting.AppURL = TestAppURL
    91  
    92  	test := func(input, expected string) {
    93  		buffer, err := RenderString(&RenderContext{
    94  			Ctx:          git.DefaultContext,
    95  			RelativePath: "a.md",
    96  			URLPrefix:    setting.AppSubURL,
    97  			Metas:        localMetas,
    98  		}, input)
    99  		assert.NoError(t, err)
   100  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   101  	}
   102  
   103  	test(
   104  		"gogits/gogs#12345",
   105  		`<p><a href="`+util.URLJoin(TestAppURL, "gogits", "gogs", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogits/gogs#12345</a></p>`)
   106  	test(
   107  		"go-gitea/gitea#12345",
   108  		`<p><a href="`+util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
   109  	test(
   110  		"/home/gitea/go-gitea/gitea#12345",
   111  		`<p>/home/gitea/go-gitea/gitea#12345</p>`)
   112  	test(
   113  		util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345"),
   114  		`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/gitea#12345</a></p>`)
   115  	test(
   116  		util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345"),
   117  		`<p><a href="`+util.URLJoin(TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
   118  	test(
   119  		util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
   120  		`<p><a href="`+util.URLJoin(TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
   121  }
   122  
   123  func TestMisc_IsSameDomain(t *testing.T) {
   124  	setting.AppURL = TestAppURL
   125  
   126  	sha := "b6dd6210eaebc915fd5be5579c58cce4da2e2579"
   127  	commit := util.URLJoin(TestRepoURL, "commit", sha)
   128  
   129  	assert.True(t, IsSameDomain(commit))
   130  	assert.False(t, IsSameDomain("http://google.com/ncr"))
   131  	assert.False(t, IsSameDomain("favicon.ico"))
   132  }
   133  
   134  func TestRender_links(t *testing.T) {
   135  	setting.AppURL = TestAppURL
   136  
   137  	test := func(input, expected string) {
   138  		buffer, err := RenderString(&RenderContext{
   139  			Ctx:          git.DefaultContext,
   140  			RelativePath: "a.md",
   141  			URLPrefix:    TestRepoURL,
   142  		}, input)
   143  		assert.NoError(t, err)
   144  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   145  	}
   146  	// Text that should be turned into URL
   147  
   148  	defaultCustom := setting.Markdown.CustomURLSchemes
   149  	setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"}
   150  	InitializeSanitizer()
   151  	CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
   152  
   153  	test(
   154  		"https://www.example.com",
   155  		`<p><a href="https://www.example.com" rel="nofollow">https://www.example.com</a></p>`)
   156  	test(
   157  		"http://www.example.com",
   158  		`<p><a href="http://www.example.com" rel="nofollow">http://www.example.com</a></p>`)
   159  	test(
   160  		"https://example.com",
   161  		`<p><a href="https://example.com" rel="nofollow">https://example.com</a></p>`)
   162  	test(
   163  		"http://example.com",
   164  		`<p><a href="http://example.com" rel="nofollow">http://example.com</a></p>`)
   165  	test(
   166  		"http://foo.com/blah_blah",
   167  		`<p><a href="http://foo.com/blah_blah" rel="nofollow">http://foo.com/blah_blah</a></p>`)
   168  	test(
   169  		"http://foo.com/blah_blah/",
   170  		`<p><a href="http://foo.com/blah_blah/" rel="nofollow">http://foo.com/blah_blah/</a></p>`)
   171  	test(
   172  		"http://www.example.com/wpstyle/?p=364",
   173  		`<p><a href="http://www.example.com/wpstyle/?p=364" rel="nofollow">http://www.example.com/wpstyle/?p=364</a></p>`)
   174  	test(
   175  		"https://www.example.com/foo/?bar=baz&inga=42&quux",
   176  		`<p><a href="https://www.example.com/foo/?bar=baz&amp;inga=42&amp;quux" rel="nofollow">https://www.example.com/foo/?bar=baz&amp;inga=42&amp;quux</a></p>`)
   177  	test(
   178  		"http://142.42.1.1/",
   179  		`<p><a href="http://142.42.1.1/" rel="nofollow">http://142.42.1.1/</a></p>`)
   180  	test(
   181  		"https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd",
   182  		`<p><a href="https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd" rel="nofollow">https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd</a></p>`)
   183  	test(
   184  		"https://en.wikipedia.org/wiki/URL_(disambiguation)",
   185  		`<p><a href="https://en.wikipedia.org/wiki/URL_(disambiguation)" rel="nofollow">https://en.wikipedia.org/wiki/URL_(disambiguation)</a></p>`)
   186  	test(
   187  		"https://foo_bar.example.com/",
   188  		`<p><a href="https://foo_bar.example.com/" rel="nofollow">https://foo_bar.example.com/</a></p>`)
   189  	test(
   190  		"https://stackoverflow.com/questions/2896191/what-is-go-used-fore",
   191  		`<p><a href="https://stackoverflow.com/questions/2896191/what-is-go-used-fore" rel="nofollow">https://stackoverflow.com/questions/2896191/what-is-go-used-fore</a></p>`)
   192  	test(
   193  		"https://username:password@gitea.com",
   194  		`<p><a href="https://username:password@gitea.com" rel="nofollow">https://username:password@gitea.com</a></p>`)
   195  	test(
   196  		"ftp://gitea.com/file.txt",
   197  		`<p><a href="ftp://gitea.com/file.txt" rel="nofollow">ftp://gitea.com/file.txt</a></p>`)
   198  	test(
   199  		"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download",
   200  		`<p><a href="magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download</a></p>`)
   201  
   202  	// Test that should *not* be turned into URL
   203  	test(
   204  		"www.example.com",
   205  		`<p>www.example.com</p>`)
   206  	test(
   207  		"example.com",
   208  		`<p>example.com</p>`)
   209  	test(
   210  		"test.example.com",
   211  		`<p>test.example.com</p>`)
   212  	test(
   213  		"http://",
   214  		`<p>http://</p>`)
   215  	test(
   216  		"https://",
   217  		`<p>https://</p>`)
   218  	test(
   219  		"://",
   220  		`<p>://</p>`)
   221  	test(
   222  		"www",
   223  		`<p>www</p>`)
   224  	test(
   225  		"ftps://gitea.com",
   226  		`<p>ftps://gitea.com</p>`)
   227  
   228  	// Restore previous settings
   229  	setting.Markdown.CustomURLSchemes = defaultCustom
   230  	InitializeSanitizer()
   231  	CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
   232  }
   233  
   234  func TestRender_email(t *testing.T) {
   235  	setting.AppURL = TestAppURL
   236  
   237  	test := func(input, expected string) {
   238  		res, err := RenderString(&RenderContext{
   239  			Ctx:          git.DefaultContext,
   240  			RelativePath: "a.md",
   241  			URLPrefix:    TestRepoURL,
   242  		}, input)
   243  		assert.NoError(t, err)
   244  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
   245  	}
   246  	// Text that should be turned into email link
   247  
   248  	test(
   249  		"info@gitea.com",
   250  		`<p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a></p>`)
   251  	test(
   252  		"(info@gitea.com)",
   253  		`<p>(<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>)</p>`)
   254  	test(
   255  		"[info@gitea.com]",
   256  		`<p>[<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>]</p>`)
   257  	test(
   258  		"info@gitea.com.",
   259  		`<p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>.</p>`)
   260  	test(
   261  		"firstname+lastname@gitea.com",
   262  		`<p><a href="mailto:firstname+lastname@gitea.com" rel="nofollow">firstname+lastname@gitea.com</a></p>`)
   263  	test(
   264  		"send email to info@gitea.co.uk.",
   265  		`<p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p>`)
   266  
   267  	// Test that should *not* be turned into email links
   268  	test(
   269  		"\"info@gitea.com\"",
   270  		`<p>&#34;info@gitea.com&#34;</p>`)
   271  	test(
   272  		"/home/gitea/mailstore/info@gitea/com",
   273  		`<p>/home/gitea/mailstore/info@gitea/com</p>`)
   274  	test(
   275  		"git@try.gitea.io:go-gitea/gitea.git",
   276  		`<p>git@try.gitea.io:go-gitea/gitea.git</p>`)
   277  	test(
   278  		"gitea@3",
   279  		`<p>gitea@3</p>`)
   280  	test(
   281  		"gitea@gmail.c",
   282  		`<p>gitea@gmail.c</p>`)
   283  	test(
   284  		"email@domain@domain.com",
   285  		`<p>email@domain@domain.com</p>`)
   286  	test(
   287  		"email@domain..com",
   288  		`<p>email@domain..com</p>`)
   289  }
   290  
   291  func TestRender_emoji(t *testing.T) {
   292  	setting.AppURL = TestAppURL
   293  	setting.StaticURLPrefix = TestAppURL
   294  
   295  	test := func(input, expected string) {
   296  		expected = strings.ReplaceAll(expected, "&", "&amp;")
   297  		buffer, err := RenderString(&RenderContext{
   298  			Ctx:          git.DefaultContext,
   299  			RelativePath: "a.md",
   300  			URLPrefix:    TestRepoURL,
   301  		}, input)
   302  		assert.NoError(t, err)
   303  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   304  	}
   305  
   306  	// Make sure we can successfully match every emoji in our dataset with regex
   307  	for i := range emoji.GemojiData {
   308  		test(
   309  			emoji.GemojiData[i].Emoji,
   310  			`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
   311  	}
   312  	for i := range emoji.GemojiData {
   313  		test(
   314  			":"+emoji.GemojiData[i].Aliases[0]+":",
   315  			`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
   316  	}
   317  
   318  	// Text that should be turned into or recognized as emoji
   319  	test(
   320  		":gitea:",
   321  		`<p><span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
   322  	test(
   323  		":custom-emoji:",
   324  		`<p>:custom-emoji:</p>`)
   325  	setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:"
   326  	test(
   327  		":custom-emoji:",
   328  		`<p><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`)
   329  	test(
   330  		"θΏ™ζ˜―ε­—η¬¦:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:",
   331  		`<p>θΏ™ζ˜―ε­—η¬¦:1:<span class="emoji" aria-label="thumbs up">πŸ‘</span> some<span class="emoji" aria-label="crocodile">🐊</span> `+
   332  			`<span class="emoji" aria-label="thumbs up">πŸ‘</span><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span> `+
   333  			`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
   334  	test(
   335  		"Some text with πŸ˜„ in the middle",
   336  		`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span> in the middle</p>`)
   337  	test(
   338  		"Some text with :smile: in the middle",
   339  		`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span> in the middle</p>`)
   340  	test(
   341  		"Some text with πŸ˜„πŸ˜„ 2 emoji next to each other",
   342  		`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span><span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span> 2 emoji next to each other</p>`)
   343  	test(
   344  		"😎πŸ€ͺπŸ”πŸ€‘β“",
   345  		`<p><span class="emoji" aria-label="smiling face with sunglasses">😎</span><span class="emoji" aria-label="zany face">πŸ€ͺ</span><span class="emoji" aria-label="locked with key">πŸ”</span><span class="emoji" aria-label="money-mouth face">πŸ€‘</span><span class="emoji" aria-label="red question mark">❓</span></p>`)
   346  
   347  	// should match nothing
   348  	test(
   349  		"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
   350  		`<p>2001:0db8:85a3:0000:0000:8a2e:0370:7334</p>`)
   351  	test(
   352  		":not exist:",
   353  		`<p>:not exist:</p>`)
   354  }
   355  
   356  func TestRender_ShortLinks(t *testing.T) {
   357  	setting.AppURL = TestAppURL
   358  	tree := util.URLJoin(TestRepoURL, "src", "master")
   359  
   360  	test := func(input, expected, expectedWiki string) {
   361  		buffer, err := markdown.RenderString(&RenderContext{
   362  			Ctx:       git.DefaultContext,
   363  			URLPrefix: tree,
   364  		}, input)
   365  		assert.NoError(t, err)
   366  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   367  		buffer, err = markdown.RenderString(&RenderContext{
   368  			Ctx:       git.DefaultContext,
   369  			URLPrefix: TestRepoURL,
   370  			Metas:     localMetas,
   371  			IsWiki:    true,
   372  		}, input)
   373  		assert.NoError(t, err)
   374  		assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
   375  	}
   376  
   377  	rawtree := util.URLJoin(TestRepoURL, "raw", "master")
   378  	url := util.URLJoin(tree, "Link")
   379  	otherURL := util.URLJoin(tree, "Other-Link")
   380  	encodedURL := util.URLJoin(tree, "Link%3F")
   381  	imgurl := util.URLJoin(rawtree, "Link.jpg")
   382  	otherImgurl := util.URLJoin(rawtree, "Link+Other.jpg")
   383  	encodedImgurl := util.URLJoin(rawtree, "Link+%23.jpg")
   384  	notencodedImgurl := util.URLJoin(rawtree, "some", "path", "Link+#.jpg")
   385  	urlWiki := util.URLJoin(TestRepoURL, "wiki", "Link")
   386  	otherURLWiki := util.URLJoin(TestRepoURL, "wiki", "Other-Link")
   387  	encodedURLWiki := util.URLJoin(TestRepoURL, "wiki", "Link%3F")
   388  	imgurlWiki := util.URLJoin(TestRepoURL, "wiki", "raw", "Link.jpg")
   389  	otherImgurlWiki := util.URLJoin(TestRepoURL, "wiki", "raw", "Link+Other.jpg")
   390  	encodedImgurlWiki := util.URLJoin(TestRepoURL, "wiki", "raw", "Link+%23.jpg")
   391  	notencodedImgurlWiki := util.URLJoin(TestRepoURL, "wiki", "raw", "some", "path", "Link+#.jpg")
   392  	favicon := "http://google.com/favicon.ico"
   393  
   394  	test(
   395  		"[[Link]]",
   396  		`<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
   397  		`<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
   398  	test(
   399  		"[[Link.jpg]]",
   400  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Link.jpg" alt="Link.jpg"/></a></p>`,
   401  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Link.jpg" alt="Link.jpg"/></a></p>`)
   402  	test(
   403  		"[["+favicon+"]]",
   404  		`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`,
   405  		`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`)
   406  	test(
   407  		"[[Name|Link]]",
   408  		`<p><a href="`+url+`" rel="nofollow">Name</a></p>`,
   409  		`<p><a href="`+urlWiki+`" rel="nofollow">Name</a></p>`)
   410  	test(
   411  		"[[Name|Link.jpg]]",
   412  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Name" alt="Name"/></a></p>`,
   413  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Name" alt="Name"/></a></p>`)
   414  	test(
   415  		"[[Name|Link.jpg|alt=AltName]]",
   416  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="AltName" alt="AltName"/></a></p>`,
   417  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="AltName" alt="AltName"/></a></p>`)
   418  	test(
   419  		"[[Name|Link.jpg|title=Title]]",
   420  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="Title"/></a></p>`,
   421  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="Title"/></a></p>`)
   422  	test(
   423  		"[[Name|Link.jpg|alt=AltName|title=Title]]",
   424  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
   425  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   426  	test(
   427  		"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
   428  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
   429  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   430  	test(
   431  		"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]",
   432  		`<p><a href="`+otherImgurl+`" rel="nofollow"><img src="`+otherImgurl+`" title="Title" alt="AltName"/></a></p>`,
   433  		`<p><a href="`+otherImgurlWiki+`" rel="nofollow"><img src="`+otherImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   434  	test(
   435  		"[[Link]] [[Other Link]]",
   436  		`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
   437  		`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a></p>`)
   438  	test(
   439  		"[[Link?]]",
   440  		`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
   441  		`<p><a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
   442  	test(
   443  		"[[Link]] [[Other Link]] [[Link?]]",
   444  		`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
   445  		`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
   446  	test(
   447  		"[[Link #.jpg]]",
   448  		`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`,
   449  		`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`)
   450  	test(
   451  		"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
   452  		`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Title" alt="AltName"/></a></p>`,
   453  		`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   454  	test(
   455  		"[[some/path/Link #.jpg]]",
   456  		`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`,
   457  		`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`)
   458  	test(
   459  		"<p><a href=\"https://example.org\">[[foobar]]</a></p>",
   460  		`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
   461  		`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
   462  }
   463  
   464  func TestRender_RelativeImages(t *testing.T) {
   465  	setting.AppURL = TestAppURL
   466  	tree := util.URLJoin(TestRepoURL, "src", "master")
   467  
   468  	test := func(input, expected, expectedWiki string) {
   469  		buffer, err := markdown.RenderString(&RenderContext{
   470  			Ctx:       git.DefaultContext,
   471  			URLPrefix: tree,
   472  			Metas:     localMetas,
   473  		}, input)
   474  		assert.NoError(t, err)
   475  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   476  		buffer, err = markdown.RenderString(&RenderContext{
   477  			Ctx:       git.DefaultContext,
   478  			URLPrefix: TestRepoURL,
   479  			Metas:     localMetas,
   480  			IsWiki:    true,
   481  		}, input)
   482  		assert.NoError(t, err)
   483  		assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(buffer))
   484  	}
   485  
   486  	rawwiki := util.URLJoin(TestRepoURL, "wiki", "raw")
   487  	mediatree := util.URLJoin(TestRepoURL, "media", "master")
   488  
   489  	test(
   490  		`<img src="Link">`,
   491  		`<img src="`+util.URLJoin(mediatree, "Link")+`"/>`,
   492  		`<img src="`+util.URLJoin(rawwiki, "Link")+`"/>`)
   493  
   494  	test(
   495  		`<img src="./icon.png">`,
   496  		`<img src="`+util.URLJoin(mediatree, "icon.png")+`"/>`,
   497  		`<img src="`+util.URLJoin(rawwiki, "icon.png")+`"/>`)
   498  }
   499  
   500  func Test_ParseClusterFuzz(t *testing.T) {
   501  	setting.AppURL = TestAppURL
   502  
   503  	localMetas := map[string]string{
   504  		"user": "go-gitea",
   505  		"repo": "gitea",
   506  	}
   507  
   508  	data := "<A><maTH><tr><MN><bodY ΓΏ><temPlate></template><tH><tr></A><tH><d<bodY "
   509  
   510  	var res strings.Builder
   511  	err := PostProcess(&RenderContext{
   512  		Ctx:       git.DefaultContext,
   513  		URLPrefix: "https://example.com",
   514  		Metas:     localMetas,
   515  	}, strings.NewReader(data), &res)
   516  	assert.NoError(t, err)
   517  	assert.NotContains(t, res.String(), "<html")
   518  
   519  	data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ΓΏ><temPlate></template><tH><tr></A><tH><d<bodY "
   520  
   521  	res.Reset()
   522  	err = PostProcess(&RenderContext{
   523  		Ctx:       git.DefaultContext,
   524  		URLPrefix: "https://example.com",
   525  		Metas:     localMetas,
   526  	}, strings.NewReader(data), &res)
   527  
   528  	assert.NoError(t, err)
   529  	assert.NotContains(t, res.String(), "<html")
   530  }
   531  
   532  func TestIssue16020(t *testing.T) {
   533  	setting.AppURL = TestAppURL
   534  
   535  	localMetas := map[string]string{
   536  		"user": "go-gitea",
   537  		"repo": "gitea",
   538  	}
   539  
   540  	data := `<img src="data:image/png;base64,i//V"/>`
   541  
   542  	var res strings.Builder
   543  	err := PostProcess(&RenderContext{
   544  		Ctx:       git.DefaultContext,
   545  		URLPrefix: "https://example.com",
   546  		Metas:     localMetas,
   547  	}, strings.NewReader(data), &res)
   548  	assert.NoError(t, err)
   549  	assert.Equal(t, data, res.String())
   550  }
   551  
   552  func BenchmarkEmojiPostprocess(b *testing.B) {
   553  	data := "πŸ₯° "
   554  	for len(data) < 1<<16 {
   555  		data += data
   556  	}
   557  	b.ResetTimer()
   558  	for i := 0; i < b.N; i++ {
   559  		var res strings.Builder
   560  		err := PostProcess(&RenderContext{
   561  			Ctx:       git.DefaultContext,
   562  			URLPrefix: "https://example.com",
   563  			Metas:     localMetas,
   564  		}, strings.NewReader(data), &res)
   565  		assert.NoError(b, err)
   566  	}
   567  }
   568  
   569  func TestFuzz(t *testing.T) {
   570  	s := "t/l/issues/8#/../../a"
   571  	renderContext := RenderContext{
   572  		Ctx:       git.DefaultContext,
   573  		URLPrefix: "https://example.com/go-gitea/gitea",
   574  		Metas: map[string]string{
   575  			"user": "go-gitea",
   576  			"repo": "gitea",
   577  		},
   578  	}
   579  
   580  	err := PostProcess(&renderContext, strings.NewReader(s), io.Discard)
   581  
   582  	assert.NoError(t, err)
   583  }
   584  
   585  func TestIssue18471(t *testing.T) {
   586  	data := `http://domain/org/repo/compare/783b039...da951ce`
   587  
   588  	var res strings.Builder
   589  	err := PostProcess(&RenderContext{
   590  		Ctx:       git.DefaultContext,
   591  		URLPrefix: "https://example.com",
   592  		Metas:     localMetas,
   593  	}, strings.NewReader(data), &res)
   594  
   595  	assert.NoError(t, err)
   596  	assert.Equal(t, res.String(), "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>")
   597  }