github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/markup/html_test.go (about)

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