code.gitea.io/gitea@v1.22.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/models/unittest"
    14  	"code.gitea.io/gitea/modules/emoji"
    15  	"code.gitea.io/gitea/modules/git"
    16  	"code.gitea.io/gitea/modules/log"
    17  	"code.gitea.io/gitea/modules/markup"
    18  	"code.gitea.io/gitea/modules/markup/markdown"
    19  	"code.gitea.io/gitea/modules/setting"
    20  	"code.gitea.io/gitea/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": "../../tests/gitea-repositories-meta/user13/repo11.git/",
    29  }
    30  
    31  func TestMain(m *testing.M) {
    32  	unittest.InitSettings()
    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 = markup.TestAppURL
    41  	test := func(input, expected string) {
    42  		buffer, err := markup.RenderString(&markup.RenderContext{
    43  			Ctx:          git.DefaultContext,
    44  			RelativePath: ".md",
    45  			Links: markup.Links{
    46  				AbsolutePrefix: true,
    47  				Base:           markup.TestRepoURL,
    48  			},
    49  			Metas: localMetas,
    50  		}, input)
    51  		assert.NoError(t, err)
    52  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
    53  	}
    54  
    55  	sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
    56  	repo := "http://localhost:3000/gogits/gogs"
    57  	commit := util.URLJoin(repo, "commit", sha)
    58  	tree := util.URLJoin(repo, "tree", sha, "src")
    59  
    60  	file := util.URLJoin(repo, "commit", sha, "example.txt")
    61  	fileWithExtra := file + ":"
    62  	fileWithHash := file + "#L2"
    63  	fileWithHasExtra := file + "#L2:"
    64  	commitCompare := util.URLJoin(repo, "compare", sha+"..."+sha)
    65  	commitCompareWithHash := commitCompare + "#L2"
    66  
    67  	test(sha, `<p><a href="`+commit+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    68  	test(sha[:7], `<p><a href="`+commit[:len(commit)-(40-7)]+`" rel="nofollow"><code>65f1bf2</code></a></p>`)
    69  	test(sha[:39], `<p><a href="`+commit[:len(commit)-(40-39)]+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    70  	test(commit, `<p><a href="`+commit+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    71  	test(tree, `<p><a href="`+tree+`" rel="nofollow"><code>65f1bf27bc/src</code></a></p>`)
    72  
    73  	test(file, `<p><a href="`+file+`" rel="nofollow"><code>65f1bf27bc/example.txt</code></a></p>`)
    74  	test(fileWithExtra, `<p><a href="`+file+`" rel="nofollow"><code>65f1bf27bc/example.txt</code></a>:</p>`)
    75  	test(fileWithHash, `<p><a href="`+fileWithHash+`" rel="nofollow"><code>65f1bf27bc/example.txt (L2)</code></a></p>`)
    76  	test(fileWithHasExtra, `<p><a href="`+fileWithHash+`" rel="nofollow"><code>65f1bf27bc/example.txt (L2)</code></a>:</p>`)
    77  	test(commitCompare, `<p><a href="`+commitCompare+`" rel="nofollow"><code>65f1bf27bc...65f1bf27bc</code></a></p>`)
    78  	test(commitCompareWithHash, `<p><a href="`+commitCompareWithHash+`" rel="nofollow"><code>65f1bf27bc...65f1bf27bc (L2)</code></a></p>`)
    79  
    80  	test("commit "+sha, `<p>commit <a href="`+commit+`" rel="nofollow"><code>65f1bf27bc</code></a></p>`)
    81  	test("/home/gitea/"+sha, "<p>/home/gitea/"+sha+"</p>")
    82  	test("deadbeef", `<p>deadbeef</p>`)
    83  	test("d27ace93", `<p>d27ace93</p>`)
    84  	test(sha[:14]+".x", `<p>`+sha[:14]+`.x</p>`)
    85  
    86  	expected14 := `<a href="` + commit[:len(commit)-(40-14)] + `" rel="nofollow"><code>` + sha[:10] + `</code></a>`
    87  	test(sha[:14]+".", `<p>`+expected14+`.</p>`)
    88  	test(sha[:14]+",", `<p>`+expected14+`,</p>`)
    89  	test("["+sha[:14]+"]", `<p>[`+expected14+`]</p>`)
    90  }
    91  
    92  func TestRender_CrossReferences(t *testing.T) {
    93  	setting.AppURL = markup.TestAppURL
    94  
    95  	test := func(input, expected string) {
    96  		buffer, err := markup.RenderString(&markup.RenderContext{
    97  			Ctx:          git.DefaultContext,
    98  			RelativePath: "a.md",
    99  			Links: markup.Links{
   100  				AbsolutePrefix: true,
   101  				Base:           setting.AppSubURL,
   102  			},
   103  			Metas: localMetas,
   104  		}, input)
   105  		assert.NoError(t, err)
   106  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   107  	}
   108  
   109  	test(
   110  		"test-owner/test-repo#12345",
   111  		`<p><a href="`+util.URLJoin(markup.TestAppURL, "test-owner", "test-repo", "issues", "12345")+`" class="ref-issue" rel="nofollow">test-owner/test-repo#12345</a></p>`)
   112  	test(
   113  		"go-gitea/gitea#12345",
   114  		`<p><a href="`+util.URLJoin(markup.TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
   115  	test(
   116  		"/home/gitea/go-gitea/gitea#12345",
   117  		`<p>/home/gitea/go-gitea/gitea#12345</p>`)
   118  	test(
   119  		util.URLJoin(markup.TestAppURL, "gogitea", "gitea", "issues", "12345"),
   120  		`<p><a href="`+util.URLJoin(markup.TestAppURL, "gogitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/gitea#12345</a></p>`)
   121  	test(
   122  		util.URLJoin(markup.TestAppURL, "go-gitea", "gitea", "issues", "12345"),
   123  		`<p><a href="`+util.URLJoin(markup.TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p>`)
   124  	test(
   125  		util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345"),
   126  		`<p><a href="`+util.URLJoin(markup.TestAppURL, "gogitea", "some-repo-name", "issues", "12345")+`" class="ref-issue" rel="nofollow">gogitea/some-repo-name#12345</a></p>`)
   127  
   128  	inputURL := "https://host/a/b/commit/0123456789012345678901234567890123456789/foo.txt?a=b#L2-L3"
   129  	test(
   130  		inputURL,
   131  		`<p><a href="`+inputURL+`" rel="nofollow"><code>0123456789/foo.txt (L2-L3)</code></a></p>`)
   132  }
   133  
   134  func TestMisc_IsSameDomain(t *testing.T) {
   135  	setting.AppURL = markup.TestAppURL
   136  
   137  	sha := "b6dd6210eaebc915fd5be5579c58cce4da2e2579"
   138  	commit := util.URLJoin(markup.TestRepoURL, "commit", sha)
   139  
   140  	assert.True(t, markup.IsSameDomain(commit))
   141  	assert.False(t, markup.IsSameDomain("http://google.com/ncr"))
   142  	assert.False(t, markup.IsSameDomain("favicon.ico"))
   143  }
   144  
   145  func TestRender_links(t *testing.T) {
   146  	setting.AppURL = markup.TestAppURL
   147  
   148  	test := func(input, expected string) {
   149  		buffer, err := markup.RenderString(&markup.RenderContext{
   150  			Ctx:          git.DefaultContext,
   151  			RelativePath: "a.md",
   152  			Links: markup.Links{
   153  				Base: markup.TestRepoURL,
   154  			},
   155  		}, input)
   156  		assert.NoError(t, err)
   157  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   158  	}
   159  
   160  	oldCustomURLSchemes := setting.Markdown.CustomURLSchemes
   161  	markup.ResetDefaultSanitizerForTesting()
   162  	defer func() {
   163  		setting.Markdown.CustomURLSchemes = oldCustomURLSchemes
   164  		markup.ResetDefaultSanitizerForTesting()
   165  		markup.CustomLinkURLSchemes(oldCustomURLSchemes)
   166  	}()
   167  	setting.Markdown.CustomURLSchemes = []string{"ftp", "magnet"}
   168  	markup.CustomLinkURLSchemes(setting.Markdown.CustomURLSchemes)
   169  
   170  	// Text that should be turned into URL
   171  	test(
   172  		"https://www.example.com",
   173  		`<p><a href="https://www.example.com" rel="nofollow">https://www.example.com</a></p>`)
   174  	test(
   175  		"http://www.example.com",
   176  		`<p><a href="http://www.example.com" rel="nofollow">http://www.example.com</a></p>`)
   177  	test(
   178  		"https://example.com",
   179  		`<p><a href="https://example.com" rel="nofollow">https://example.com</a></p>`)
   180  	test(
   181  		"http://example.com",
   182  		`<p><a href="http://example.com" rel="nofollow">http://example.com</a></p>`)
   183  	test(
   184  		"http://foo.com/blah_blah",
   185  		`<p><a href="http://foo.com/blah_blah" rel="nofollow">http://foo.com/blah_blah</a></p>`)
   186  	test(
   187  		"http://foo.com/blah_blah/",
   188  		`<p><a href="http://foo.com/blah_blah/" rel="nofollow">http://foo.com/blah_blah/</a></p>`)
   189  	test(
   190  		"http://www.example.com/wpstyle/?p=364",
   191  		`<p><a href="http://www.example.com/wpstyle/?p=364" rel="nofollow">http://www.example.com/wpstyle/?p=364</a></p>`)
   192  	test(
   193  		"https://www.example.com/foo/?bar=baz&inga=42&quux",
   194  		`<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>`)
   195  	test(
   196  		"http://142.42.1.1/",
   197  		`<p><a href="http://142.42.1.1/" rel="nofollow">http://142.42.1.1/</a></p>`)
   198  	test(
   199  		"https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd",
   200  		`<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>`)
   201  	test(
   202  		"https://en.wikipedia.org/wiki/URL_(disambiguation)",
   203  		`<p><a href="https://en.wikipedia.org/wiki/URL_(disambiguation)" rel="nofollow">https://en.wikipedia.org/wiki/URL_(disambiguation)</a></p>`)
   204  	test(
   205  		"https://foo_bar.example.com/",
   206  		`<p><a href="https://foo_bar.example.com/" rel="nofollow">https://foo_bar.example.com/</a></p>`)
   207  	test(
   208  		"https://stackoverflow.com/questions/2896191/what-is-go-used-fore",
   209  		`<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>`)
   210  	test(
   211  		"https://username:password@gitea.com",
   212  		`<p><a href="https://username:password@gitea.com" rel="nofollow">https://username:password@gitea.com</a></p>`)
   213  	test(
   214  		"ftp://gitea.com/file.txt",
   215  		`<p><a href="ftp://gitea.com/file.txt" rel="nofollow">ftp://gitea.com/file.txt</a></p>`)
   216  	test(
   217  		"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download",
   218  		`<p><a href="magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&amp;dn=download</a></p>`)
   219  	test(
   220  		`[link](https://example.com)`,
   221  		`<p><a href="https://example.com" rel="nofollow">link</a></p>`)
   222  	test(
   223  		`[link](mailto:test@example.com)`,
   224  		`<p><a href="mailto:test@example.com" rel="nofollow">link</a></p>`)
   225  	test(
   226  		`[link](javascript:xss)`,
   227  		`<p>link</p>`)
   228  
   229  	// Test that should *not* be turned into URL
   230  	test(
   231  		"www.example.com",
   232  		`<p>www.example.com</p>`)
   233  	test(
   234  		"example.com",
   235  		`<p>example.com</p>`)
   236  	test(
   237  		"test.example.com",
   238  		`<p>test.example.com</p>`)
   239  	test(
   240  		"http://",
   241  		`<p>http://</p>`)
   242  	test(
   243  		"https://",
   244  		`<p>https://</p>`)
   245  	test(
   246  		"://",
   247  		`<p>://</p>`)
   248  	test(
   249  		"www",
   250  		`<p>www</p>`)
   251  	test(
   252  		"ftps://gitea.com",
   253  		`<p>ftps://gitea.com</p>`)
   254  }
   255  
   256  func TestRender_email(t *testing.T) {
   257  	setting.AppURL = markup.TestAppURL
   258  
   259  	test := func(input, expected string) {
   260  		res, err := markup.RenderString(&markup.RenderContext{
   261  			Ctx:          git.DefaultContext,
   262  			RelativePath: "a.md",
   263  			Links: markup.Links{
   264  				Base: markup.TestRepoURL,
   265  			},
   266  		}, input)
   267  		assert.NoError(t, err)
   268  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res))
   269  	}
   270  	// Text that should be turned into email link
   271  
   272  	test(
   273  		"info@gitea.com",
   274  		`<p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a></p>`)
   275  	test(
   276  		"(info@gitea.com)",
   277  		`<p>(<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>)</p>`)
   278  	test(
   279  		"[info@gitea.com]",
   280  		`<p>[<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>]</p>`)
   281  	test(
   282  		"info@gitea.com.",
   283  		`<p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>.</p>`)
   284  	test(
   285  		"firstname+lastname@gitea.com",
   286  		`<p><a href="mailto:firstname+lastname@gitea.com" rel="nofollow">firstname+lastname@gitea.com</a></p>`)
   287  	test(
   288  		"send email to info@gitea.co.uk.",
   289  		`<p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p>`)
   290  
   291  	test(
   292  		`j.doe@example.com,
   293  	j.doe@example.com.
   294  	j.doe@example.com;
   295  	j.doe@example.com?
   296  	j.doe@example.com!`,
   297  		`<p><a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>,<br/>
   298  <a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>.<br/>
   299  <a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>;<br/>
   300  <a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>?<br/>
   301  <a href="mailto:j.doe@example.com" rel="nofollow">j.doe@example.com</a>!</p>`)
   302  
   303  	// Test that should *not* be turned into email links
   304  	test(
   305  		"\"info@gitea.com\"",
   306  		`<p>&#34;info@gitea.com&#34;</p>`)
   307  	test(
   308  		"/home/gitea/mailstore/info@gitea/com",
   309  		`<p>/home/gitea/mailstore/info@gitea/com</p>`)
   310  	test(
   311  		"git@try.gitea.io:go-gitea/gitea.git",
   312  		`<p>git@try.gitea.io:go-gitea/gitea.git</p>`)
   313  	test(
   314  		"gitea@3",
   315  		`<p>gitea@3</p>`)
   316  	test(
   317  		"gitea@gmail.c",
   318  		`<p>gitea@gmail.c</p>`)
   319  	test(
   320  		"email@domain@domain.com",
   321  		`<p>email@domain@domain.com</p>`)
   322  	test(
   323  		"email@domain..com",
   324  		`<p>email@domain..com</p>`)
   325  }
   326  
   327  func TestRender_emoji(t *testing.T) {
   328  	setting.AppURL = markup.TestAppURL
   329  	setting.StaticURLPrefix = markup.TestAppURL
   330  
   331  	test := func(input, expected string) {
   332  		expected = strings.ReplaceAll(expected, "&", "&amp;")
   333  		buffer, err := markup.RenderString(&markup.RenderContext{
   334  			Ctx:          git.DefaultContext,
   335  			RelativePath: "a.md",
   336  			Links: markup.Links{
   337  				Base: markup.TestRepoURL,
   338  			},
   339  		}, input)
   340  		assert.NoError(t, err)
   341  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(buffer))
   342  	}
   343  
   344  	// Make sure we can successfully match every emoji in our dataset with regex
   345  	for i := range emoji.GemojiData {
   346  		test(
   347  			emoji.GemojiData[i].Emoji,
   348  			`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
   349  	}
   350  	for i := range emoji.GemojiData {
   351  		test(
   352  			":"+emoji.GemojiData[i].Aliases[0]+":",
   353  			`<p><span class="emoji" aria-label="`+emoji.GemojiData[i].Description+`">`+emoji.GemojiData[i].Emoji+`</span></p>`)
   354  	}
   355  
   356  	// Text that should be turned into or recognized as emoji
   357  	test(
   358  		":gitea:",
   359  		`<p><span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
   360  	test(
   361  		":custom-emoji:",
   362  		`<p>:custom-emoji:</p>`)
   363  	setting.UI.CustomEmojisMap["custom-emoji"] = ":custom-emoji:"
   364  	test(
   365  		":custom-emoji:",
   366  		`<p><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/custom-emoji.png"/></span></p>`)
   367  	test(
   368  		"θΏ™ζ˜―ε­—η¬¦:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:",
   369  		`<p>θΏ™ζ˜―ε­—η¬¦:1:<span class="emoji" aria-label="thumbs up">πŸ‘</span> some<span class="emoji" aria-label="crocodile">🐊</span> `+
   370  			`<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> `+
   371  			`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span></p>`)
   372  	test(
   373  		"Some text with πŸ˜„ in the middle",
   374  		`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span> in the middle</p>`)
   375  	test(
   376  		"Some text with :smile: in the middle",
   377  		`<p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span> in the middle</p>`)
   378  	test(
   379  		"Some text with πŸ˜„πŸ˜„ 2 emoji next to each other",
   380  		`<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>`)
   381  	test(
   382  		"😎πŸ€ͺπŸ”πŸ€‘β“",
   383  		`<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>`)
   384  
   385  	// should match nothing
   386  	test(
   387  		"2001:0db8:85a3:0000:0000:8a2e:0370:7334",
   388  		`<p>2001:0db8:85a3:0000:0000:8a2e:0370:7334</p>`)
   389  	test(
   390  		":not exist:",
   391  		`<p>:not exist:</p>`)
   392  }
   393  
   394  func TestRender_ShortLinks(t *testing.T) {
   395  	setting.AppURL = markup.TestAppURL
   396  	tree := util.URLJoin(markup.TestRepoURL, "src", "master")
   397  
   398  	test := func(input, expected, expectedWiki string) {
   399  		buffer, err := markdown.RenderString(&markup.RenderContext{
   400  			Ctx: git.DefaultContext,
   401  			Links: markup.Links{
   402  				Base:       markup.TestRepoURL,
   403  				BranchPath: "master",
   404  			},
   405  		}, input)
   406  		assert.NoError(t, err)
   407  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(string(buffer)))
   408  		buffer, err = markdown.RenderString(&markup.RenderContext{
   409  			Ctx: git.DefaultContext,
   410  			Links: markup.Links{
   411  				Base: markup.TestRepoURL,
   412  			},
   413  			Metas:  localMetas,
   414  			IsWiki: true,
   415  		}, input)
   416  		assert.NoError(t, err)
   417  		assert.Equal(t, strings.TrimSpace(expectedWiki), strings.TrimSpace(string(buffer)))
   418  	}
   419  
   420  	mediatree := util.URLJoin(markup.TestRepoURL, "media", "master")
   421  	url := util.URLJoin(tree, "Link")
   422  	otherURL := util.URLJoin(tree, "Other-Link")
   423  	encodedURL := util.URLJoin(tree, "Link%3F")
   424  	imgurl := util.URLJoin(mediatree, "Link.jpg")
   425  	otherImgurl := util.URLJoin(mediatree, "Link+Other.jpg")
   426  	encodedImgurl := util.URLJoin(mediatree, "Link+%23.jpg")
   427  	notencodedImgurl := util.URLJoin(mediatree, "some", "path", "Link+#.jpg")
   428  	urlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link")
   429  	otherURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Other-Link")
   430  	encodedURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "Link%3F")
   431  	imgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link.jpg")
   432  	otherImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+Other.jpg")
   433  	encodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "Link+%23.jpg")
   434  	notencodedImgurlWiki := util.URLJoin(markup.TestRepoURL, "wiki", "raw", "some", "path", "Link+#.jpg")
   435  	renderableFileURL := util.URLJoin(tree, "markdown_file.md")
   436  	renderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "markdown_file.md")
   437  	unrenderableFileURL := util.URLJoin(tree, "file.zip")
   438  	unrenderableFileURLWiki := util.URLJoin(markup.TestRepoURL, "wiki", "file.zip")
   439  	favicon := "http://google.com/favicon.ico"
   440  
   441  	test(
   442  		"[[Link]]",
   443  		`<p><a href="`+url+`" rel="nofollow">Link</a></p>`,
   444  		`<p><a href="`+urlWiki+`" rel="nofollow">Link</a></p>`)
   445  	test(
   446  		"[[Link.-]]",
   447  		`<p><a href="http://localhost:3000/test-owner/test-repo/src/master/Link.-" rel="nofollow">Link.-</a></p>`,
   448  		`<p><a href="http://localhost:3000/test-owner/test-repo/wiki/Link.-" rel="nofollow">Link.-</a></p>`)
   449  	test(
   450  		"[[Link.jpg]]",
   451  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Link.jpg" alt="Link.jpg"/></a></p>`,
   452  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Link.jpg" alt="Link.jpg"/></a></p>`)
   453  	test(
   454  		"[["+favicon+"]]",
   455  		`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`,
   456  		`<p><a href="`+favicon+`" rel="nofollow"><img src="`+favicon+`" title="favicon.ico" alt="`+favicon+`"/></a></p>`)
   457  	test(
   458  		"[[Name|Link]]",
   459  		`<p><a href="`+url+`" rel="nofollow">Name</a></p>`,
   460  		`<p><a href="`+urlWiki+`" rel="nofollow">Name</a></p>`)
   461  	test(
   462  		"[[Name|Link.jpg]]",
   463  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Name" alt="Name"/></a></p>`,
   464  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Name" alt="Name"/></a></p>`)
   465  	test(
   466  		"[[Name|Link.jpg|alt=AltName]]",
   467  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="AltName" alt="AltName"/></a></p>`,
   468  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="AltName" alt="AltName"/></a></p>`)
   469  	test(
   470  		"[[Name|Link.jpg|title=Title]]",
   471  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="Title"/></a></p>`,
   472  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="Title"/></a></p>`)
   473  	test(
   474  		"[[Name|Link.jpg|alt=AltName|title=Title]]",
   475  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
   476  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   477  	test(
   478  		"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]",
   479  		`<p><a href="`+imgurl+`" rel="nofollow"><img src="`+imgurl+`" title="Title" alt="AltName"/></a></p>`,
   480  		`<p><a href="`+imgurlWiki+`" rel="nofollow"><img src="`+imgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   481  	test(
   482  		"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]",
   483  		`<p><a href="`+otherImgurl+`" rel="nofollow"><img src="`+otherImgurl+`" title="Title" alt="AltName"/></a></p>`,
   484  		`<p><a href="`+otherImgurlWiki+`" rel="nofollow"><img src="`+otherImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   485  	test(
   486  		"[[Link]] [[Other Link]]",
   487  		`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a></p>`,
   488  		`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a></p>`)
   489  	test(
   490  		"[[Link?]]",
   491  		`<p><a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
   492  		`<p><a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
   493  	test(
   494  		"[[Link]] [[Other Link]] [[Link?]]",
   495  		`<p><a href="`+url+`" rel="nofollow">Link</a> <a href="`+otherURL+`" rel="nofollow">Other Link</a> <a href="`+encodedURL+`" rel="nofollow">Link?</a></p>`,
   496  		`<p><a href="`+urlWiki+`" rel="nofollow">Link</a> <a href="`+otherURLWiki+`" rel="nofollow">Other Link</a> <a href="`+encodedURLWiki+`" rel="nofollow">Link?</a></p>`)
   497  	test(
   498  		"[[markdown_file.md]]",
   499  		`<p><a href="`+renderableFileURL+`" rel="nofollow">markdown_file.md</a></p>`,
   500  		`<p><a href="`+renderableFileURLWiki+`" rel="nofollow">markdown_file.md</a></p>`)
   501  	test(
   502  		"[[file.zip]]",
   503  		`<p><a href="`+unrenderableFileURL+`" rel="nofollow">file.zip</a></p>`,
   504  		`<p><a href="`+unrenderableFileURLWiki+`" rel="nofollow">file.zip</a></p>`)
   505  	test(
   506  		"[[Link #.jpg]]",
   507  		`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`,
   508  		`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Link #.jpg" alt="Link #.jpg"/></a></p>`)
   509  	test(
   510  		"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]",
   511  		`<p><a href="`+encodedImgurl+`" rel="nofollow"><img src="`+encodedImgurl+`" title="Title" alt="AltName"/></a></p>`,
   512  		`<p><a href="`+encodedImgurlWiki+`" rel="nofollow"><img src="`+encodedImgurlWiki+`" title="Title" alt="AltName"/></a></p>`)
   513  	test(
   514  		"[[some/path/Link #.jpg]]",
   515  		`<p><a href="`+notencodedImgurl+`" rel="nofollow"><img src="`+notencodedImgurl+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`,
   516  		`<p><a href="`+notencodedImgurlWiki+`" rel="nofollow"><img src="`+notencodedImgurlWiki+`" title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p>`)
   517  	test(
   518  		"<p><a href=\"https://example.org\">[[foobar]]</a></p>",
   519  		`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`,
   520  		`<p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p>`)
   521  }
   522  
   523  func TestRender_RelativeMedias(t *testing.T) {
   524  	render := func(input string, isWiki bool, links markup.Links) string {
   525  		buffer, err := markdown.RenderString(&markup.RenderContext{
   526  			Ctx:    git.DefaultContext,
   527  			Links:  links,
   528  			Metas:  localMetas,
   529  			IsWiki: isWiki,
   530  		}, input)
   531  		assert.NoError(t, err)
   532  		return strings.TrimSpace(string(buffer))
   533  	}
   534  
   535  	out := render(`<img src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
   536  	assert.Equal(t, `<a href="/test-owner/test-repo/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/LINK"/></a>`, out)
   537  
   538  	out = render(`<img src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo"})
   539  	assert.Equal(t, `<a href="/test-owner/test-repo/wiki/raw/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/wiki/raw/LINK"/></a>`, out)
   540  
   541  	out = render(`<img src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
   542  	assert.Equal(t, `<a href="/test-owner/test-repo/media/test-branch/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/media/test-branch/LINK"/></a>`, out)
   543  
   544  	out = render(`<img src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
   545  	assert.Equal(t, `<a href="/test-owner/test-repo/wiki/raw/LINK" target="_blank" rel="nofollow noopener"><img src="/test-owner/test-repo/wiki/raw/LINK"/></a>`, out)
   546  
   547  	out = render(`<img src="/LINK">`, true, markup.Links{Base: "/test-owner/test-repo", BranchPath: "test-branch"})
   548  	assert.Equal(t, `<img src="/LINK"/>`, out)
   549  
   550  	out = render(`<video src="LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
   551  	assert.Equal(t, `<video src="/test-owner/test-repo/LINK"></video>`, out)
   552  
   553  	out = render(`<video src="LINK">`, true, markup.Links{Base: "/test-owner/test-repo"})
   554  	assert.Equal(t, `<video src="/test-owner/test-repo/wiki/raw/LINK"></video>`, out)
   555  
   556  	out = render(`<video src="/LINK">`, false, markup.Links{Base: "/test-owner/test-repo"})
   557  	assert.Equal(t, `<video src="/LINK"></video>`, out)
   558  }
   559  
   560  func Test_ParseClusterFuzz(t *testing.T) {
   561  	setting.AppURL = markup.TestAppURL
   562  
   563  	localMetas := map[string]string{
   564  		"user": "go-gitea",
   565  		"repo": "gitea",
   566  	}
   567  
   568  	data := "<A><maTH><tr><MN><bodY ΓΏ><temPlate></template><tH><tr></A><tH><d<bodY "
   569  
   570  	var res strings.Builder
   571  	err := markup.PostProcess(&markup.RenderContext{
   572  		Ctx: git.DefaultContext,
   573  		Links: markup.Links{
   574  			Base: "https://example.com",
   575  		},
   576  		Metas: localMetas,
   577  	}, strings.NewReader(data), &res)
   578  	assert.NoError(t, err)
   579  	assert.NotContains(t, res.String(), "<html")
   580  
   581  	data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ΓΏ><temPlate></template><tH><tr></A><tH><d<bodY "
   582  
   583  	res.Reset()
   584  	err = markup.PostProcess(&markup.RenderContext{
   585  		Ctx: git.DefaultContext,
   586  		Links: markup.Links{
   587  			Base: "https://example.com",
   588  		},
   589  		Metas: localMetas,
   590  	}, strings.NewReader(data), &res)
   591  
   592  	assert.NoError(t, err)
   593  	assert.NotContains(t, res.String(), "<html")
   594  }
   595  
   596  func TestPostProcess_RenderDocument(t *testing.T) {
   597  	setting.AppURL = markup.TestAppURL
   598  	setting.StaticURLPrefix = markup.TestAppURL // can't run standalone
   599  
   600  	localMetas := map[string]string{
   601  		"user": "go-gitea",
   602  		"repo": "gitea",
   603  		"mode": "document",
   604  	}
   605  
   606  	test := func(input, expected string) {
   607  		var res strings.Builder
   608  		err := markup.PostProcess(&markup.RenderContext{
   609  			Ctx: git.DefaultContext,
   610  			Links: markup.Links{
   611  				AbsolutePrefix: true,
   612  				Base:           "https://example.com",
   613  			},
   614  			Metas: localMetas,
   615  		}, strings.NewReader(input), &res)
   616  		assert.NoError(t, err)
   617  		assert.Equal(t, strings.TrimSpace(expected), strings.TrimSpace(res.String()))
   618  	}
   619  
   620  	// Issue index shouldn't be post processing in a document.
   621  	test(
   622  		"#1",
   623  		"#1")
   624  
   625  	// But cross-referenced issue index should work.
   626  	test(
   627  		"go-gitea/gitea#12345",
   628  		`<a href="`+util.URLJoin(markup.TestAppURL, "go-gitea", "gitea", "issues", "12345")+`" class="ref-issue">go-gitea/gitea#12345</a>`)
   629  
   630  	// Test that other post processing still works.
   631  	test(
   632  		":gitea:",
   633  		`<span class="emoji" aria-label="gitea"><img alt=":gitea:" src="`+setting.StaticURLPrefix+`/assets/img/emoji/gitea.png"/></span>`)
   634  	test(
   635  		"Some text with πŸ˜„ in the middle",
   636  		`Some text with <span class="emoji" aria-label="grinning face with smiling eyes">πŸ˜„</span> in the middle`)
   637  	test("http://localhost:3000/person/repo/issues/4#issuecomment-1234",
   638  		`<a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a>`)
   639  }
   640  
   641  func TestIssue16020(t *testing.T) {
   642  	setting.AppURL = markup.TestAppURL
   643  
   644  	localMetas := map[string]string{
   645  		"user": "go-gitea",
   646  		"repo": "gitea",
   647  	}
   648  
   649  	data := `<img src="data:image/png;base64,i//V"/>`
   650  
   651  	var res strings.Builder
   652  	err := markup.PostProcess(&markup.RenderContext{
   653  		Ctx:   git.DefaultContext,
   654  		Metas: localMetas,
   655  	}, strings.NewReader(data), &res)
   656  	assert.NoError(t, err)
   657  	assert.Equal(t, data, res.String())
   658  }
   659  
   660  func BenchmarkEmojiPostprocess(b *testing.B) {
   661  	data := "πŸ₯° "
   662  	for len(data) < 1<<16 {
   663  		data += data
   664  	}
   665  	b.ResetTimer()
   666  	for i := 0; i < b.N; i++ {
   667  		var res strings.Builder
   668  		err := markup.PostProcess(&markup.RenderContext{
   669  			Ctx:   git.DefaultContext,
   670  			Metas: localMetas,
   671  		}, strings.NewReader(data), &res)
   672  		assert.NoError(b, err)
   673  	}
   674  }
   675  
   676  func TestFuzz(t *testing.T) {
   677  	s := "t/l/issues/8#/../../a"
   678  	renderContext := markup.RenderContext{
   679  		Ctx: git.DefaultContext,
   680  		Links: markup.Links{
   681  			Base: "https://example.com/go-gitea/gitea",
   682  		},
   683  		Metas: map[string]string{
   684  			"user": "go-gitea",
   685  			"repo": "gitea",
   686  		},
   687  	}
   688  
   689  	err := markup.PostProcess(&renderContext, strings.NewReader(s), io.Discard)
   690  
   691  	assert.NoError(t, err)
   692  }
   693  
   694  func TestIssue18471(t *testing.T) {
   695  	data := `http://domain/org/repo/compare/783b039...da951ce`
   696  
   697  	var res strings.Builder
   698  	err := markup.PostProcess(&markup.RenderContext{
   699  		Ctx:   git.DefaultContext,
   700  		Metas: localMetas,
   701  	}, strings.NewReader(data), &res)
   702  
   703  	assert.NoError(t, err)
   704  	assert.Equal(t, `<a href="http://domain/org/repo/compare/783b039...da951ce" class="compare"><code class="nohighlight">783b039...da951ce</code></a>`, res.String())
   705  }
   706  
   707  func TestIsFullURL(t *testing.T) {
   708  	assert.True(t, markup.IsFullURLString("https://example.com"))
   709  	assert.True(t, markup.IsFullURLString("mailto:test@example.com"))
   710  	assert.True(t, markup.IsFullURLString("data:image/11111"))
   711  	assert.False(t, markup.IsFullURLString("/foo:bar"))
   712  }