2017-09-16 11:17:57 -06:00
// Copyright 2017 The Gitea Authors. All rights reserved.
2022-11-27 11:20:29 -07:00
// SPDX-License-Identifier: MIT
2017-09-16 11:17:57 -06:00
package markup_test
import (
2022-06-16 09:47:44 -06:00
"context"
2021-08-12 16:22:05 -06:00
"io"
2022-10-05 12:55:36 -06:00
"os"
2017-09-16 11:17:57 -06:00
"strings"
"testing"
Refactor path & config system (#25330)
# The problem
There were many "path tricks":
* By default, Gitea uses its program directory as its work path
* Gitea tries to use the "work path" to guess its "custom path" and
"custom conf (app.ini)"
* Users might want to use other directories as work path
* The non-default work path should be passed to Gitea by GITEA_WORK_DIR
or "--work-path"
* But some Gitea processes are started without these values
* The "serv" process started by OpenSSH server
* The CLI sub-commands started by site admin
* The paths are guessed by SetCustomPathAndConf again and again
* The default values of "work path / custom path / custom conf" can be
changed when compiling
# The solution
* Use `InitWorkPathAndCommonConfig` to handle these path tricks, and use
test code to cover its behaviors.
* When Gitea's web server runs, write the WORK_PATH to "app.ini", this
value must be the most correct one, because if this value is not right,
users would find that the web UI doesn't work and then they should be
able to fix it.
* Then all other sub-commands can use the WORK_PATH in app.ini to
initialize their paths.
* By the way, when Gitea starts for git protocol, it shouldn't output
any log, otherwise the git protocol gets broken and client blocks
forever.
The "work path" priority is: WORK_PATH in app.ini > cmd arg --work-path
> env var GITEA_WORK_DIR > builtin default
The "app.ini" searching order is: cmd arg --config > cmd arg "work path
/ custom path" > env var "work path / custom path" > builtin default
## ⚠️ BREAKING
If your instance's "work path / custom path / custom conf" doesn't meet
the requirements (eg: work path must be absolute), Gitea will report a
fatal error and exit. You need to set these values according to the
error log.
----
Close #24818
Close #24222
Close #21606
Close #21498
Close #25107
Close #24981
Maybe close #24503
Replace #23301
Replace #22754
And maybe more
2023-06-20 23:50:26 -06:00
"code.gitea.io/gitea/models/unittest"
2020-04-28 12:05:39 -06:00
"code.gitea.io/gitea/modules/emoji"
2022-01-19 16:26:57 -07:00
"code.gitea.io/gitea/modules/git"
2022-06-16 09:47:44 -06:00
"code.gitea.io/gitea/modules/log"
2023-10-23 20:54:59 -06:00
"code.gitea.io/gitea/modules/markup"
2018-02-27 00:09:18 -07:00
"code.gitea.io/gitea/modules/markup/markdown"
2017-09-16 11:17:57 -06:00
"code.gitea.io/gitea/modules/setting"
2018-02-20 05:50:42 -07:00
"code.gitea.io/gitea/modules/util"
2017-09-16 11:17:57 -06:00
"github.com/stretchr/testify/assert"
)
2019-04-16 01:53:57 -06:00
var localMetas = map [ string ] string {
2019-08-14 02:04:55 -06:00
"user" : "gogits" ,
"repo" : "gogs" ,
2022-09-02 13:18:23 -06:00
"repoPath" : "../../tests/gitea-repositories-meta/user13/repo11.git/" ,
2019-04-16 01:53:57 -06:00
}
2022-06-16 09:47:44 -06:00
func TestMain ( m * testing . M ) {
Refactor path & config system (#25330)
# The problem
There were many "path tricks":
* By default, Gitea uses its program directory as its work path
* Gitea tries to use the "work path" to guess its "custom path" and
"custom conf (app.ini)"
* Users might want to use other directories as work path
* The non-default work path should be passed to Gitea by GITEA_WORK_DIR
or "--work-path"
* But some Gitea processes are started without these values
* The "serv" process started by OpenSSH server
* The CLI sub-commands started by site admin
* The paths are guessed by SetCustomPathAndConf again and again
* The default values of "work path / custom path / custom conf" can be
changed when compiling
# The solution
* Use `InitWorkPathAndCommonConfig` to handle these path tricks, and use
test code to cover its behaviors.
* When Gitea's web server runs, write the WORK_PATH to "app.ini", this
value must be the most correct one, because if this value is not right,
users would find that the web UI doesn't work and then they should be
able to fix it.
* Then all other sub-commands can use the WORK_PATH in app.ini to
initialize their paths.
* By the way, when Gitea starts for git protocol, it shouldn't output
any log, otherwise the git protocol gets broken and client blocks
forever.
The "work path" priority is: WORK_PATH in app.ini > cmd arg --work-path
> env var GITEA_WORK_DIR > builtin default
The "app.ini" searching order is: cmd arg --config > cmd arg "work path
/ custom path" > env var "work path / custom path" > builtin default
## ⚠️ BREAKING
If your instance's "work path / custom path / custom conf" doesn't meet
the requirements (eg: work path must be absolute), Gitea will report a
fatal error and exit. You need to set these values according to the
error log.
----
Close #24818
Close #24222
Close #21606
Close #21498
Close #25107
Close #24981
Maybe close #24503
Replace #23301
Replace #22754
And maybe more
2023-06-20 23:50:26 -06:00
unittest . InitSettings ( )
2022-06-16 09:47:44 -06:00
if err := git . InitSimple ( context . Background ( ) ) ; err != nil {
log . Fatal ( "git init failed, err: %v" , err )
}
2022-10-05 12:55:36 -06:00
os . Exit ( m . Run ( ) )
2022-06-16 09:47:44 -06:00
}
2017-09-16 11:17:57 -06:00
func TestRender_Commits ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2017-09-16 11:17:57 -06:00
test := func ( input , expected string ) {
2023-10-23 20:54:59 -06:00
buffer , err := markup . RenderString ( & markup . RenderContext {
2022-06-15 21:33:23 -06:00
Ctx : git . DefaultContext ,
RelativePath : ".md" ,
2024-01-15 01:49:24 -07:00
Links : markup . Links {
2024-03-13 04:34:58 -06:00
AbsolutePrefix : true ,
Base : markup . TestRepoURL ,
2024-01-15 01:49:24 -07:00
} ,
Metas : localMetas ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
2019-07-23 12:50:39 -06:00
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( buffer ) )
2017-09-16 11:17:57 -06:00
}
2022-01-20 10:46:10 -07:00
sha := "65f1bf27bc3bf70f64657658635e66094edbcb4d"
2023-10-23 20:54:59 -06:00
repo := markup . TestRepoURL
2022-01-20 10:46:10 -07:00
commit := util . URLJoin ( repo , "commit" , sha )
tree := util . URLJoin ( repo , "tree" , sha , "src" )
file := util . URLJoin ( repo , "commit" , sha , "example.txt" )
fileWithExtra := file + ":"
fileWithHash := file + "#L2"
fileWithHasExtra := file + "#L2:"
commitCompare := util . URLJoin ( repo , "compare" , sha + "..." + sha )
commitCompareWithHash := commitCompare + "#L2"
2017-09-16 11:17:57 -06:00
2019-08-14 02:04:55 -06:00
test ( sha , ` <p><a href=" ` + commit + ` " rel="nofollow"><code>65f1bf27bc</code></a></p> ` )
test ( sha [ : 7 ] , ` <p><a href=" ` + commit [ : len ( commit ) - ( 40 - 7 ) ] + ` " rel="nofollow"><code>65f1bf2</code></a></p> ` )
test ( sha [ : 39 ] , ` <p><a href=" ` + commit [ : len ( commit ) - ( 40 - 39 ) ] + ` " rel="nofollow"><code>65f1bf27bc</code></a></p> ` )
test ( commit , ` <p><a href=" ` + commit + ` " rel="nofollow"><code>65f1bf27bc</code></a></p> ` )
test ( tree , ` <p><a href=" ` + tree + ` " rel="nofollow"><code>65f1bf27bc/src</code></a></p> ` )
2021-12-11 10:21:36 -07:00
test ( file , ` <p><a href=" ` + file + ` " rel="nofollow"><code>65f1bf27bc/example.txt</code></a></p> ` )
test ( fileWithExtra , ` <p><a href=" ` + file + ` " rel="nofollow"><code>65f1bf27bc/example.txt</code></a>:</p> ` )
test ( fileWithHash , ` <p><a href=" ` + fileWithHash + ` " rel="nofollow"><code>65f1bf27bc/example.txt (L2)</code></a></p> ` )
test ( fileWithHasExtra , ` <p><a href=" ` + fileWithHash + ` " rel="nofollow"><code>65f1bf27bc/example.txt (L2)</code></a>:</p> ` )
test ( commitCompare , ` <p><a href=" ` + commitCompare + ` " rel="nofollow"><code>65f1bf27bc...65f1bf27bc</code></a></p> ` )
test ( commitCompareWithHash , ` <p><a href=" ` + commitCompareWithHash + ` " rel="nofollow"><code>65f1bf27bc...65f1bf27bc (L2)</code></a></p> ` )
2019-08-14 02:04:55 -06:00
test ( "commit " + sha , ` <p>commit <a href=" ` + commit + ` " rel="nofollow"><code>65f1bf27bc</code></a></p> ` )
2019-04-07 05:18:16 -06:00
test ( "/home/gitea/" + sha , "<p>/home/gitea/" + sha + "</p>" )
2019-08-14 02:04:55 -06:00
test ( "deadbeef" , ` <p>deadbeef</p> ` )
test ( "d27ace93" , ` <p>d27ace93</p> ` )
2021-01-03 08:11:10 -07:00
test ( sha [ : 14 ] + ".x" , ` <p> ` + sha [ : 14 ] + ` .x</p> ` )
expected14 := ` <a href=" ` + commit [ : len ( commit ) - ( 40 - 14 ) ] + ` " rel="nofollow"><code> ` + sha [ : 10 ] + ` </code></a> `
test ( sha [ : 14 ] + "." , ` <p> ` + expected14 + ` .</p> ` )
test ( sha [ : 14 ] + "," , ` <p> ` + expected14 + ` ,</p> ` )
test ( "[" + sha [ : 14 ] + "]" , ` <p>[ ` + expected14 + ` ]</p> ` )
2017-09-16 11:17:57 -06:00
}
func TestRender_CrossReferences ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2017-09-16 11:17:57 -06:00
test := func ( input , expected string ) {
2023-10-23 20:54:59 -06:00
buffer , err := markup . RenderString ( & markup . RenderContext {
2023-03-05 14:59:05 -07:00
Ctx : git . DefaultContext ,
2022-06-15 21:33:23 -06:00
RelativePath : "a.md" ,
2024-01-15 01:49:24 -07:00
Links : markup . Links {
2024-03-13 04:34:58 -06:00
AbsolutePrefix : true ,
Base : setting . AppSubURL ,
2024-01-15 01:49:24 -07:00
} ,
Metas : localMetas ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
2019-07-23 12:50:39 -06:00
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( buffer ) )
2017-09-16 11:17:57 -06:00
}
test (
"gogits/gogs#12345" ,
2023-10-23 20:54:59 -06:00
` <p><a href=" ` + util . URLJoin ( markup . TestAppURL , "gogits" , "gogs" , "issues" , "12345" ) + ` " class="ref-issue" rel="nofollow">gogits/gogs#12345</a></p> ` )
2018-01-27 05:33:32 -07:00
test (
"go-gitea/gitea#12345" ,
2023-10-23 20:54:59 -06:00
` <p><a href=" ` + util . URLJoin ( markup . TestAppURL , "go-gitea" , "gitea" , "issues" , "12345" ) + ` " class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p> ` )
2019-04-07 05:18:16 -06:00
test (
"/home/gitea/go-gitea/gitea#12345" ,
` <p>/home/gitea/go-gitea/gitea#12345</p> ` )
2022-02-25 16:26:43 -07:00
test (
2023-10-23 20:54:59 -06:00
util . URLJoin ( markup . TestAppURL , "gogitea" , "gitea" , "issues" , "12345" ) ,
` <p><a href=" ` + util . URLJoin ( markup . TestAppURL , "gogitea" , "gitea" , "issues" , "12345" ) + ` " class="ref-issue" rel="nofollow">gogitea/gitea#12345</a></p> ` )
2022-02-25 16:26:43 -07:00
test (
2023-10-23 20:54:59 -06:00
util . URLJoin ( markup . TestAppURL , "go-gitea" , "gitea" , "issues" , "12345" ) ,
` <p><a href=" ` + util . URLJoin ( markup . TestAppURL , "go-gitea" , "gitea" , "issues" , "12345" ) + ` " class="ref-issue" rel="nofollow">go-gitea/gitea#12345</a></p> ` )
2022-02-25 16:26:43 -07:00
test (
2023-10-23 20:54:59 -06:00
util . URLJoin ( markup . TestAppURL , "gogitea" , "some-repo-name" , "issues" , "12345" ) ,
` <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> ` )
2017-09-16 11:17:57 -06:00
}
func TestMisc_IsSameDomain ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2017-09-16 11:17:57 -06:00
2022-01-20 10:46:10 -07:00
sha := "b6dd6210eaebc915fd5be5579c58cce4da2e2579"
2023-10-23 20:54:59 -06:00
commit := util . URLJoin ( markup . TestRepoURL , "commit" , sha )
2017-09-16 11:17:57 -06:00
2023-10-23 20:54:59 -06:00
assert . True ( t , markup . IsSameDomain ( commit ) )
assert . False ( t , markup . IsSameDomain ( "http://google.com/ncr" ) )
assert . False ( t , markup . IsSameDomain ( "favicon.ico" ) )
2017-09-16 11:17:57 -06:00
}
2018-02-27 00:09:18 -07:00
2019-02-28 05:31:53 -07:00
func TestRender_links ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2019-02-28 05:31:53 -07:00
test := func ( input , expected string ) {
2023-10-23 20:54:59 -06:00
buffer , err := markup . RenderString ( & markup . RenderContext {
2023-03-05 14:59:05 -07:00
Ctx : git . DefaultContext ,
2022-06-15 21:33:23 -06:00
RelativePath : "a.md" ,
2024-01-15 01:49:24 -07:00
Links : markup . Links {
Base : markup . TestRepoURL ,
} ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
2019-07-23 12:50:39 -06:00
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( buffer ) )
2019-02-28 05:31:53 -07:00
}
// Text that should be turned into URL
2019-10-14 19:31:09 -06:00
defaultCustom := setting . Markdown . CustomURLSchemes
setting . Markdown . CustomURLSchemes = [ ] string { "ftp" , "magnet" }
2023-10-23 20:54:59 -06:00
markup . InitializeSanitizer ( )
markup . CustomLinkURLSchemes ( setting . Markdown . CustomURLSchemes )
2019-10-14 19:31:09 -06:00
2019-02-28 05:31:53 -07:00
test (
"https://www.example.com" ,
` <p><a href="https://www.example.com" rel="nofollow">https://www.example.com</a></p> ` )
test (
"http://www.example.com" ,
` <p><a href="http://www.example.com" rel="nofollow">http://www.example.com</a></p> ` )
test (
"https://example.com" ,
` <p><a href="https://example.com" rel="nofollow">https://example.com</a></p> ` )
test (
"http://example.com" ,
` <p><a href="http://example.com" rel="nofollow">http://example.com</a></p> ` )
test (
"http://foo.com/blah_blah" ,
` <p><a href="http://foo.com/blah_blah" rel="nofollow">http://foo.com/blah_blah</a></p> ` )
test (
"http://foo.com/blah_blah/" ,
` <p><a href="http://foo.com/blah_blah/" rel="nofollow">http://foo.com/blah_blah/</a></p> ` )
test (
"http://www.example.com/wpstyle/?p=364" ,
` <p><a href="http://www.example.com/wpstyle/?p=364" rel="nofollow">http://www.example.com/wpstyle/?p=364</a></p> ` )
test (
"https://www.example.com/foo/?bar=baz&inga=42&quux" ,
2021-07-08 19:30:31 -06:00
` <p><a href="https://www.example.com/foo/?bar=baz&inga=42&quux" rel="nofollow">https://www.example.com/foo/?bar=baz&inga=42&quux</a></p> ` )
2019-02-28 05:31:53 -07:00
test (
"http://142.42.1.1/" ,
` <p><a href="http://142.42.1.1/" rel="nofollow">http://142.42.1.1/</a></p> ` )
2019-03-07 13:12:01 -07:00
test (
"https://github.com/go-gitea/gitea/?p=aaa/bbb.html#ccc-ddd" ,
2021-07-08 19:30:31 -06:00
` <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> ` )
2019-03-07 13:12:01 -07:00
test (
"https://en.wikipedia.org/wiki/URL_(disambiguation)" ,
` <p><a href="https://en.wikipedia.org/wiki/URL_(disambiguation)" rel="nofollow">https://en.wikipedia.org/wiki/URL_(disambiguation)</a></p> ` )
test (
"https://foo_bar.example.com/" ,
` <p><a href="https://foo_bar.example.com/" rel="nofollow">https://foo_bar.example.com/</a></p> ` )
2019-03-27 09:37:54 -06:00
test (
"https://stackoverflow.com/questions/2896191/what-is-go-used-fore" ,
` <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> ` )
test (
"https://username:password@gitea.com" ,
` <p><a href="https://username:password@gitea.com" rel="nofollow">https://username:password@gitea.com</a></p> ` )
2019-10-14 19:31:09 -06:00
test (
"ftp://gitea.com/file.txt" ,
` <p><a href="ftp://gitea.com/file.txt" rel="nofollow">ftp://gitea.com/file.txt</a></p> ` )
test (
"magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download" ,
2021-07-08 19:30:31 -06:00
` <p><a href="magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download" rel="nofollow">magnet:?xt=urn:btih:5dee65101db281ac9c46344cd6b175cdcadabcde&dn=download</a></p> ` )
2024-02-21 03:08:08 -07:00
test (
` [link](https://example.com) ` ,
` <p><a href="https://example.com" rel="nofollow">link</a></p> ` )
test (
` [link](mailto:test@example.com) ` ,
` <p><a href="mailto:test@example.com" rel="nofollow">link</a></p> ` )
test (
` [link](javascript:xss) ` ,
` <p>link</p> ` )
2019-02-28 05:31:53 -07:00
// Test that should *not* be turned into URL
test (
"www.example.com" ,
` <p>www.example.com</p> ` )
test (
"example.com" ,
` <p>example.com</p> ` )
test (
"test.example.com" ,
` <p>test.example.com</p> ` )
test (
"http://" ,
` <p>http://</p> ` )
test (
"https://" ,
` <p>https://</p> ` )
test (
"://" ,
` <p>://</p> ` )
test (
"www" ,
` <p>www</p> ` )
2019-10-14 19:31:09 -06:00
test (
"ftps://gitea.com" ,
` <p>ftps://gitea.com</p> ` )
// Restore previous settings
setting . Markdown . CustomURLSchemes = defaultCustom
2023-10-23 20:54:59 -06:00
markup . InitializeSanitizer ( )
markup . CustomLinkURLSchemes ( setting . Markdown . CustomURLSchemes )
2019-02-28 05:31:53 -07:00
}
2019-04-07 05:18:16 -06:00
func TestRender_email ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2019-04-07 05:18:16 -06:00
test := func ( input , expected string ) {
2023-10-23 20:54:59 -06:00
res , err := markup . RenderString ( & markup . RenderContext {
2023-03-05 14:59:05 -07:00
Ctx : git . DefaultContext ,
2022-06-15 21:33:23 -06:00
RelativePath : "a.md" ,
2024-01-15 01:49:24 -07:00
Links : markup . Links {
Base : markup . TestRepoURL ,
} ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( res ) )
2019-04-07 05:18:16 -06:00
}
// Text that should be turned into email link
test (
"info@gitea.com" ,
` <p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a></p> ` )
test (
"(info@gitea.com)" ,
` <p>(<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>)</p> ` )
test (
"[info@gitea.com]" ,
` <p>[<a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>]</p> ` )
test (
"info@gitea.com." ,
` <p><a href="mailto:info@gitea.com" rel="nofollow">info@gitea.com</a>.</p> ` )
2019-04-24 19:53:42 -06:00
test (
"firstname+lastname@gitea.com" ,
` <p><a href="mailto:firstname+lastname@gitea.com" rel="nofollow">firstname+lastname@gitea.com</a></p> ` )
2019-04-07 05:18:16 -06:00
test (
"send email to info@gitea.co.uk." ,
` <p>send email to <a href="mailto:info@gitea.co.uk" rel="nofollow">info@gitea.co.uk</a>.</p> ` )
2023-11-10 21:08:19 -07:00
test (
` j . doe @ example . com ,
j . doe @ example . com .
j . doe @ example . com ;
j . doe @ example . com ?
j . doe @ example . com ! ` ,
` < p > < a href = "mailto:j.doe@example.com" rel = "nofollow" > j . doe @ example . com < / a > , < br / >
< a href = "mailto:j.doe@example.com" rel = "nofollow" > j . doe @ example . com < / a > . < br / >
< a href = "mailto:j.doe@example.com" rel = "nofollow" > j . doe @ example . com < / a > ; < br / >
< a href = "mailto:j.doe@example.com" rel = "nofollow" > j . doe @ example . com < / a > ? < br / >
< a href = "mailto:j.doe@example.com" rel = "nofollow" > j . doe @ example . com < / a > ! < / p > ` )
2019-04-07 05:18:16 -06:00
// Test that should *not* be turned into email links
test (
"\"info@gitea.com\"" ,
2020-06-13 00:10:17 -06:00
` <p>"info@gitea.com"</p> ` )
2019-04-07 05:18:16 -06:00
test (
"/home/gitea/mailstore/info@gitea/com" ,
` <p>/home/gitea/mailstore/info@gitea/com</p> ` )
test (
"git@try.gitea.io:go-gitea/gitea.git" ,
` <p>git@try.gitea.io:go-gitea/gitea.git</p> ` )
2019-04-24 19:53:42 -06:00
test (
"gitea@3" ,
` <p>gitea@3</p> ` )
test (
"gitea@gmail.c" ,
` <p>gitea@gmail.c</p> ` )
test (
"email@domain@domain.com" ,
` <p>email@domain@domain.com</p> ` )
test (
"email@domain..com" ,
` <p>email@domain..com</p> ` )
2019-04-07 05:18:16 -06:00
}
2020-04-28 12:05:39 -06:00
func TestRender_emoji ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
setting . StaticURLPrefix = markup . TestAppURL
2020-04-28 12:05:39 -06:00
test := func ( input , expected string ) {
2020-10-11 14:27:20 -06:00
expected = strings . ReplaceAll ( expected , "&" , "&" )
2023-10-23 20:54:59 -06:00
buffer , err := markup . RenderString ( & markup . RenderContext {
2023-03-05 14:59:05 -07:00
Ctx : git . DefaultContext ,
2022-06-15 21:33:23 -06:00
RelativePath : "a.md" ,
2024-01-15 01:49:24 -07:00
Links : markup . Links {
Base : markup . TestRepoURL ,
} ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
2020-04-28 12:05:39 -06:00
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( buffer ) )
}
// Make sure we can successfully match every emoji in our dataset with regex
for i := range emoji . GemojiData {
test (
emoji . GemojiData [ i ] . Emoji ,
` <p><span class="emoji" aria-label=" ` + emoji . GemojiData [ i ] . Description + ` "> ` + emoji . GemojiData [ i ] . Emoji + ` </span></p> ` )
}
for i := range emoji . GemojiData {
test (
":" + emoji . GemojiData [ i ] . Aliases [ 0 ] + ":" ,
` <p><span class="emoji" aria-label=" ` + emoji . GemojiData [ i ] . Description + ` "> ` + emoji . GemojiData [ i ] . Emoji + ` </span></p> ` )
}
2022-01-20 10:46:10 -07:00
// Text that should be turned into or recognized as emoji
2020-04-28 12:05:39 -06:00
test (
":gitea:" ,
2021-05-07 09:34:33 -06:00
` <p><span class="emoji" aria-label="gitea"><img alt=":gitea:" src=" ` + setting . StaticURLPrefix + ` /assets/img/emoji/gitea.png"/></span></p> ` )
2021-06-29 08:28:38 -06:00
test (
":custom-emoji:" ,
` <p>:custom-emoji:</p> ` )
setting . UI . CustomEmojisMap [ "custom-emoji" ] = ":custom-emoji:"
test (
":custom-emoji:" ,
` <p><span class="emoji" aria-label="custom-emoji"><img alt=":custom-emoji:" src=" ` + setting . StaticURLPrefix + ` /assets/img/emoji/custom-emoji.png"/></span></p> ` )
test (
"这是字符:1::+1: some🐊 \U0001f44d:custom-emoji: :gitea:" ,
` <p>这是字符:1:<span class="emoji" aria-label="thumbs up">👍</span> some<span class="emoji" aria-label="crocodile">🐊</span> ` +
` <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> ` +
` <span class="emoji" aria-label="gitea"><img alt=":gitea:" src=" ` + setting . StaticURLPrefix + ` /assets/img/emoji/gitea.png"/></span></p> ` )
2020-04-28 12:05:39 -06:00
test (
"Some text with 😄 in the middle" ,
` <p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p> ` )
test (
"Some text with :smile: in the middle" ,
` <p>Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle</p> ` )
2020-05-29 10:08:36 -06:00
test (
"Some text with 😄😄 2 emoji next to each other" ,
` <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> ` )
2020-07-25 07:40:04 -06:00
test (
"😎🤪🔐🤑❓" ,
2022-10-05 12:55:36 -06:00
` <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> ` )
2020-07-25 07:40:04 -06:00
2020-04-28 12:05:39 -06:00
// should match nothing
test (
"2001:0db8:85a3:0000:0000:8a2e:0370:7334" ,
` <p>2001:0db8:85a3:0000:0000:8a2e:0370:7334</p> ` )
test (
":not exist:" ,
` <p>:not exist:</p> ` )
}
2018-02-27 00:09:18 -07:00
func TestRender_ShortLinks ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
tree := util . URLJoin ( markup . TestRepoURL , "src" , "master" )
2018-02-27 00:09:18 -07:00
test := func ( input , expected , expectedWiki string ) {
2023-10-23 20:54:59 -06:00
buffer , err := markdown . RenderString ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : markup . TestRepoURL ,
BranchPath : "master" ,
} ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
2024-03-01 00:11:51 -07:00
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( string ( buffer ) ) )
2023-10-23 20:54:59 -06:00
buffer , err = markdown . RenderString ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : markup . TestRepoURL ,
} ,
Metas : localMetas ,
IsWiki : true ,
2021-04-19 16:25:08 -06:00
} , input )
assert . NoError ( t , err )
2024-03-01 00:11:51 -07:00
assert . Equal ( t , strings . TrimSpace ( expectedWiki ) , strings . TrimSpace ( string ( buffer ) ) )
2018-02-27 00:09:18 -07:00
}
2024-01-15 01:49:24 -07:00
mediatree := util . URLJoin ( markup . TestRepoURL , "media" , "master" )
2018-02-27 00:09:18 -07:00
url := util . URLJoin ( tree , "Link" )
2018-03-05 11:39:12 -07:00
otherURL := util . URLJoin ( tree , "Other-Link" )
2018-06-15 06:42:49 -06:00
encodedURL := util . URLJoin ( tree , "Link%3F" )
2024-01-15 01:49:24 -07:00
imgurl := util . URLJoin ( mediatree , "Link.jpg" )
otherImgurl := util . URLJoin ( mediatree , "Link+Other.jpg" )
encodedImgurl := util . URLJoin ( mediatree , "Link+%23.jpg" )
notencodedImgurl := util . URLJoin ( mediatree , "some" , "path" , "Link+#.jpg" )
2023-10-23 20:54:59 -06:00
urlWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "Link" )
otherURLWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "Other-Link" )
encodedURLWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "Link%3F" )
imgurlWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "raw" , "Link.jpg" )
otherImgurlWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "raw" , "Link+Other.jpg" )
encodedImgurlWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "raw" , "Link+%23.jpg" )
notencodedImgurlWiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "raw" , "some" , "path" , "Link+#.jpg" )
2018-02-27 00:09:18 -07:00
favicon := "http://google.com/favicon.ico"
test (
"[[Link]]" ,
` <p><a href=" ` + url + ` " rel="nofollow">Link</a></p> ` ,
` <p><a href=" ` + urlWiki + ` " rel="nofollow">Link</a></p> ` )
test (
"[[Link.jpg]]" ,
` <p><a href=" ` + imgurl + ` " rel="nofollow"><img src=" ` + imgurl + ` " title="Link.jpg" alt="Link.jpg"/></a></p> ` ,
` <p><a href=" ` + imgurlWiki + ` " rel="nofollow"><img src=" ` + imgurlWiki + ` " title="Link.jpg" alt="Link.jpg"/></a></p> ` )
test (
"[[" + favicon + "]]" ,
2020-02-28 13:05:12 -07:00
` <p><a href=" ` + favicon + ` " rel="nofollow"><img src=" ` + favicon + ` " title="favicon.ico" alt=" ` + favicon + ` "/></a></p> ` ,
` <p><a href=" ` + favicon + ` " rel="nofollow"><img src=" ` + favicon + ` " title="favicon.ico" alt=" ` + favicon + ` "/></a></p> ` )
2018-02-27 00:09:18 -07:00
test (
"[[Name|Link]]" ,
` <p><a href=" ` + url + ` " rel="nofollow">Name</a></p> ` ,
` <p><a href=" ` + urlWiki + ` " rel="nofollow">Name</a></p> ` )
test (
"[[Name|Link.jpg]]" ,
` <p><a href=" ` + imgurl + ` " rel="nofollow"><img src=" ` + imgurl + ` " title="Name" alt="Name"/></a></p> ` ,
` <p><a href=" ` + imgurlWiki + ` " rel="nofollow"><img src=" ` + imgurlWiki + ` " title="Name" alt="Name"/></a></p> ` )
test (
"[[Name|Link.jpg|alt=AltName]]" ,
` <p><a href=" ` + imgurl + ` " rel="nofollow"><img src=" ` + imgurl + ` " title="AltName" alt="AltName"/></a></p> ` ,
` <p><a href=" ` + imgurlWiki + ` " rel="nofollow"><img src=" ` + imgurlWiki + ` " title="AltName" alt="AltName"/></a></p> ` )
test (
"[[Name|Link.jpg|title=Title]]" ,
` <p><a href=" ` + imgurl + ` " rel="nofollow"><img src=" ` + imgurl + ` " title="Title" alt="Title"/></a></p> ` ,
` <p><a href=" ` + imgurlWiki + ` " rel="nofollow"><img src=" ` + imgurlWiki + ` " title="Title" alt="Title"/></a></p> ` )
test (
"[[Name|Link.jpg|alt=AltName|title=Title]]" ,
` <p><a href=" ` + imgurl + ` " rel="nofollow"><img src=" ` + imgurl + ` " title="Title" alt="AltName"/></a></p> ` ,
` <p><a href=" ` + imgurlWiki + ` " rel="nofollow"><img src=" ` + imgurlWiki + ` " title="Title" alt="AltName"/></a></p> ` )
test (
"[[Name|Link.jpg|alt=\"AltName\"|title='Title']]" ,
` <p><a href=" ` + imgurl + ` " rel="nofollow"><img src=" ` + imgurl + ` " title="Title" alt="AltName"/></a></p> ` ,
` <p><a href=" ` + imgurlWiki + ` " rel="nofollow"><img src=" ` + imgurlWiki + ` " title="Title" alt="AltName"/></a></p> ` )
test (
2018-03-05 11:39:12 -07:00
"[[Name|Link Other.jpg|alt=\"AltName\"|title='Title']]" ,
` <p><a href=" ` + otherImgurl + ` " rel="nofollow"><img src=" ` + otherImgurl + ` " title="Title" alt="AltName"/></a></p> ` ,
` <p><a href=" ` + otherImgurlWiki + ` " rel="nofollow"><img src=" ` + otherImgurlWiki + ` " title="Title" alt="AltName"/></a></p> ` )
test (
"[[Link]] [[Other Link]]" ,
` <p><a href=" ` + url + ` " rel="nofollow">Link</a> <a href=" ` + otherURL + ` " rel="nofollow">Other Link</a></p> ` ,
` <p><a href=" ` + urlWiki + ` " rel="nofollow">Link</a> <a href=" ` + otherURLWiki + ` " rel="nofollow">Other Link</a></p> ` )
2018-06-15 06:42:49 -06:00
test (
"[[Link?]]" ,
` <p><a href=" ` + encodedURL + ` " rel="nofollow">Link?</a></p> ` ,
` <p><a href=" ` + encodedURLWiki + ` " rel="nofollow">Link?</a></p> ` )
test (
"[[Link]] [[Other Link]] [[Link?]]" ,
` <p><a href=" ` + url + ` " rel="nofollow">Link</a> <a href=" ` + otherURL + ` " rel="nofollow">Other Link</a> <a href=" ` + encodedURL + ` " rel="nofollow">Link?</a></p> ` ,
` <p><a href=" ` + urlWiki + ` " rel="nofollow">Link</a> <a href=" ` + otherURLWiki + ` " rel="nofollow">Other Link</a> <a href=" ` + encodedURLWiki + ` " rel="nofollow">Link?</a></p> ` )
test (
"[[Link #.jpg]]" ,
2020-02-28 13:05:12 -07:00
` <p><a href=" ` + encodedImgurl + ` " rel="nofollow"><img src=" ` + encodedImgurl + ` " title="Link #.jpg" alt="Link #.jpg"/></a></p> ` ,
` <p><a href=" ` + encodedImgurlWiki + ` " rel="nofollow"><img src=" ` + encodedImgurlWiki + ` " title="Link #.jpg" alt="Link #.jpg"/></a></p> ` )
2018-06-15 06:42:49 -06:00
test (
"[[Name|Link #.jpg|alt=\"AltName\"|title='Title']]" ,
` <p><a href=" ` + encodedImgurl + ` " rel="nofollow"><img src=" ` + encodedImgurl + ` " title="Title" alt="AltName"/></a></p> ` ,
` <p><a href=" ` + encodedImgurlWiki + ` " rel="nofollow"><img src=" ` + encodedImgurlWiki + ` " title="Title" alt="AltName"/></a></p> ` )
test (
"[[some/path/Link #.jpg]]" ,
2020-02-28 13:05:12 -07:00
` <p><a href=" ` + notencodedImgurl + ` " rel="nofollow"><img src=" ` + notencodedImgurl + ` " title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p> ` ,
` <p><a href=" ` + notencodedImgurlWiki + ` " rel="nofollow"><img src=" ` + notencodedImgurlWiki + ` " title="Link #.jpg" alt="some/path/Link #.jpg"/></a></p> ` )
2019-03-07 12:13:44 -07:00
test (
"<p><a href=\"https://example.org\">[[foobar]]</a></p>" ,
2019-12-30 18:53:28 -07:00
` <p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p> ` ,
` <p><a href="https://example.org" rel="nofollow">[[foobar]]</a></p> ` )
2018-02-27 00:09:18 -07:00
}
2021-01-20 08:10:50 -07:00
2021-07-04 03:26:04 -06:00
func TestRender_RelativeImages ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2021-07-04 03:26:04 -06:00
test := func ( input , expected , expectedWiki string ) {
2023-10-23 20:54:59 -06:00
buffer , err := markdown . RenderString ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : markup . TestRepoURL ,
BranchPath : "master" ,
} ,
Metas : localMetas ,
2021-07-04 03:26:04 -06:00
} , input )
assert . NoError ( t , err )
2024-03-01 00:11:51 -07:00
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( string ( buffer ) ) )
2023-10-23 20:54:59 -06:00
buffer , err = markdown . RenderString ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : markup . TestRepoURL ,
} ,
Metas : localMetas ,
IsWiki : true ,
2021-07-04 03:26:04 -06:00
} , input )
assert . NoError ( t , err )
2024-03-01 00:11:51 -07:00
assert . Equal ( t , strings . TrimSpace ( expectedWiki ) , strings . TrimSpace ( string ( buffer ) ) )
2021-07-04 03:26:04 -06:00
}
2023-10-23 20:54:59 -06:00
rawwiki := util . URLJoin ( markup . TestRepoURL , "wiki" , "raw" )
mediatree := util . URLJoin ( markup . TestRepoURL , "media" , "master" )
2021-07-04 03:26:04 -06:00
test (
` <img src="Link"> ` ,
` <img src=" ` + util . URLJoin ( mediatree , "Link" ) + ` "/> ` ,
` <img src=" ` + util . URLJoin ( rawwiki , "Link" ) + ` "/> ` )
test (
` <img src="./icon.png"> ` ,
` <img src=" ` + util . URLJoin ( mediatree , "icon.png" ) + ` "/> ` ,
` <img src=" ` + util . URLJoin ( rawwiki , "icon.png" ) + ` "/> ` )
}
2021-01-20 08:10:50 -07:00
func Test_ParseClusterFuzz ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2021-01-20 08:10:50 -07:00
2022-01-20 10:46:10 -07:00
localMetas := map [ string ] string {
2021-01-20 08:10:50 -07:00
"user" : "go-gitea" ,
"repo" : "gitea" ,
}
data := "<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
2021-04-19 16:25:08 -06:00
var res strings . Builder
2023-10-23 20:54:59 -06:00
err := markup . PostProcess ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : "https://example.com" ,
} ,
Metas : localMetas ,
2021-04-19 16:25:08 -06:00
} , strings . NewReader ( data ) , & res )
2021-01-20 08:10:50 -07:00
assert . NoError ( t , err )
2021-04-19 16:25:08 -06:00
assert . NotContains ( t , res . String ( ) , "<html" )
2021-01-20 08:10:50 -07:00
data = "<!DOCTYPE html>\n<A><maTH><tr><MN><bodY ÿ><temPlate></template><tH><tr></A><tH><d<bodY "
2021-04-19 16:25:08 -06:00
res . Reset ( )
2023-10-23 20:54:59 -06:00
err = markup . PostProcess ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : "https://example.com" ,
} ,
Metas : localMetas ,
2021-04-19 16:25:08 -06:00
} , strings . NewReader ( data ) , & res )
2021-01-20 08:10:50 -07:00
assert . NoError ( t , err )
2021-04-19 16:25:08 -06:00
assert . NotContains ( t , res . String ( ) , "<html" )
2021-01-20 08:10:50 -07:00
}
2021-06-07 10:55:26 -06:00
2023-08-07 07:11:25 -06:00
func TestPostProcess_RenderDocument ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2024-01-15 01:49:24 -07:00
setting . StaticURLPrefix = markup . TestAppURL // can't run standalone
2023-08-07 07:11:25 -06:00
localMetas := map [ string ] string {
"user" : "go-gitea" ,
"repo" : "gitea" ,
"mode" : "document" ,
}
test := func ( input , expected string ) {
var res strings . Builder
2023-10-23 20:54:59 -06:00
err := markup . PostProcess ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
2024-03-13 04:34:58 -06:00
AbsolutePrefix : true ,
Base : "https://example.com" ,
2024-01-15 01:49:24 -07:00
} ,
Metas : localMetas ,
2023-08-07 07:11:25 -06:00
} , strings . NewReader ( input ) , & res )
assert . NoError ( t , err )
assert . Equal ( t , strings . TrimSpace ( expected ) , strings . TrimSpace ( res . String ( ) ) )
}
2024-01-02 23:01:12 -07:00
// Issue index shouldn't be post processing in a document.
2023-08-07 07:11:25 -06:00
test (
"#1" ,
"#1" )
2024-01-02 23:01:12 -07:00
// But cross-referenced issue index should work.
test (
"go-gitea/gitea#12345" ,
` <a href=" ` + util . URLJoin ( markup . TestAppURL , "go-gitea" , "gitea" , "issues" , "12345" ) + ` " class="ref-issue">go-gitea/gitea#12345</a> ` )
2023-08-07 07:11:25 -06:00
// Test that other post processing still works.
test (
":gitea:" ,
` <span class="emoji" aria-label="gitea"><img alt=":gitea:" src=" ` + setting . StaticURLPrefix + ` /assets/img/emoji/gitea.png"/></span> ` )
test (
"Some text with 😄 in the middle" ,
` Some text with <span class="emoji" aria-label="grinning face with smiling eyes">😄</span> in the middle ` )
test ( "http://localhost:3000/person/repo/issues/4#issuecomment-1234" ,
` <a href="http://localhost:3000/person/repo/issues/4#issuecomment-1234" class="ref-issue">person/repo#4 (comment)</a> ` )
}
2021-06-07 10:55:26 -06:00
func TestIssue16020 ( t * testing . T ) {
2023-10-23 20:54:59 -06:00
setting . AppURL = markup . TestAppURL
2021-06-07 10:55:26 -06:00
2022-01-20 10:46:10 -07:00
localMetas := map [ string ] string {
2021-06-07 10:55:26 -06:00
"user" : "go-gitea" ,
"repo" : "gitea" ,
}
data := ` <img src="data:image/png;base64,i//V"/> `
var res strings . Builder
2023-10-23 20:54:59 -06:00
err := markup . PostProcess ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Metas : localMetas ,
2021-06-07 10:55:26 -06:00
} , strings . NewReader ( data ) , & res )
assert . NoError ( t , err )
assert . Equal ( t , data , res . String ( ) )
}
2021-06-17 04:35:05 -06:00
func BenchmarkEmojiPostprocess ( b * testing . B ) {
data := "🥰 "
for len ( data ) < 1 << 16 {
data += data
}
b . ResetTimer ( )
for i := 0 ; i < b . N ; i ++ {
var res strings . Builder
2023-10-23 20:54:59 -06:00
err := markup . PostProcess ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Metas : localMetas ,
2021-06-17 04:35:05 -06:00
} , strings . NewReader ( data ) , & res )
assert . NoError ( b , err )
}
}
2021-08-12 16:22:05 -06:00
func TestFuzz ( t * testing . T ) {
s := "t/l/issues/8#/../../a"
2023-10-23 20:54:59 -06:00
renderContext := markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Links : markup . Links {
Base : "https://example.com/go-gitea/gitea" ,
} ,
2021-08-12 16:22:05 -06:00
Metas : map [ string ] string {
"user" : "go-gitea" ,
"repo" : "gitea" ,
} ,
}
2023-10-23 20:54:59 -06:00
err := markup . PostProcess ( & renderContext , strings . NewReader ( s ) , io . Discard )
2021-08-12 16:22:05 -06:00
assert . NoError ( t , err )
}
2022-01-30 16:48:47 -07:00
func TestIssue18471 ( t * testing . T ) {
data := ` http://domain/org/repo/compare/783b039...da951ce `
var res strings . Builder
2023-10-23 20:54:59 -06:00
err := markup . PostProcess ( & markup . RenderContext {
2024-01-15 01:49:24 -07:00
Ctx : git . DefaultContext ,
Metas : localMetas ,
2022-01-30 16:48:47 -07:00
} , strings . NewReader ( data ) , & res )
assert . NoError ( t , err )
2023-04-22 15:56:27 -06:00
assert . Equal ( t , "<a href=\"http://domain/org/repo/compare/783b039...da951ce\" class=\"compare\"><code class=\"nohighlight\">783b039...da951ce</code></a>" , res . String ( ) )
2022-01-30 16:48:47 -07:00
}
2024-02-21 03:08:08 -07:00
func TestIsFullURL ( t * testing . T ) {
assert . True ( t , markup . IsFullURLString ( "https://example.com" ) )
assert . True ( t , markup . IsFullURLString ( "mailto:test@example.com" ) )
assert . False ( t , markup . IsFullURLString ( "/foo:bar" ) )
}