mirror of
https://github.com/gogs/gogs.git
synced 2026-02-28 09:10:57 +01:00
Compare commits
4 Commits
release-ar
...
migrate/go
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
997011bfb2 | ||
|
|
60f62b6583 | ||
|
|
3b7e331191 | ||
|
|
f9b4c5a3ff |
2
go.mod
2
go.mod
@@ -36,7 +36,6 @@ require (
|
|||||||
github.com/olekukonko/tablewriter v1.1.3
|
github.com/olekukonko/tablewriter v1.1.3
|
||||||
github.com/pquerna/otp v1.5.0
|
github.com/pquerna/otp v1.5.0
|
||||||
github.com/prometheus/client_golang v1.23.0
|
github.com/prometheus/client_golang v1.23.0
|
||||||
github.com/russross/blackfriday v1.6.0
|
|
||||||
github.com/sergi/go-diff v1.4.0
|
github.com/sergi/go-diff v1.4.0
|
||||||
github.com/sourcegraph/run v0.12.0
|
github.com/sourcegraph/run v0.12.0
|
||||||
github.com/stretchr/testify v1.11.1
|
github.com/stretchr/testify v1.11.1
|
||||||
@@ -45,6 +44,7 @@ require (
|
|||||||
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
|
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
|
||||||
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e
|
github.com/unknwon/paginater v0.0.0-20170405233947-45e5d631308e
|
||||||
github.com/urfave/cli v1.22.17
|
github.com/urfave/cli v1.22.17
|
||||||
|
github.com/yuin/goldmark v1.7.16
|
||||||
golang.org/x/crypto v0.47.0
|
golang.org/x/crypto v0.47.0
|
||||||
golang.org/x/image v0.35.0
|
golang.org/x/image v0.35.0
|
||||||
golang.org/x/net v0.48.0
|
golang.org/x/net v0.48.0
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -399,8 +399,6 @@ github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qq
|
|||||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||||
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
|
||||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
|
||||||
github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY=
|
|
||||||
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
|
||||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
github.com/saintfish/chardet v0.0.0-20120816061221-3af4cd4741ca h1:NugYot0LIVPxTvN8n+Kvkn6TrbMyxQiuvKdEwFdR9vI=
|
||||||
@@ -457,6 +455,8 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ=
|
|||||||
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
||||||
|
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs=
|
||||||
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
|
||||||
go.bobheadxi.dev/streamline v1.2.1 h1:IqKSA1TbeuDqCzYNAwtlh8sqf3tsQus8XgJdkCWFT8c=
|
go.bobheadxi.dev/streamline v1.2.1 h1:IqKSA1TbeuDqCzYNAwtlh8sqf3tsQus8XgJdkCWFT8c=
|
||||||
|
|||||||
@@ -54,5 +54,5 @@ func (c *Context) renderNoticeBanner() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Data["ServerNotice"] = string(markup.RawMarkdown(buf, ""))
|
c.Data["ServerNotice"] = string(markup.SanitizeBytes(markup.RawMarkdown(buf, "")))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,21 @@ package markup
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"html"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/russross/blackfriday"
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/ast"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/parser"
|
||||||
|
"github.com/yuin/goldmark/renderer"
|
||||||
|
goldmarkhtml "github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/text"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
log "unknwon.dev/clog/v2"
|
||||||
|
|
||||||
"gogs.io/gogs/internal/conf"
|
"gogs.io/gogs/internal/conf"
|
||||||
"gogs.io/gogs/internal/lazyregexp"
|
"gogs.io/gogs/internal/lazyregexp"
|
||||||
@@ -25,40 +35,69 @@ func IsMarkdownFile(name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkdownRenderer is a extended version of underlying Markdown render object.
|
var (
|
||||||
type MarkdownRenderer struct {
|
validLinksPattern = lazyregexp.New(`^[a-z][\w-]+://|^mailto:`)
|
||||||
blackfriday.Renderer
|
linkifyURLRegexp = lazyregexp.New(`^(?:http|https|ftp)://[-a-zA-Z0-9@:%._+~#=]{1,256}(?:\.[a-z]+)?(?::\d+)?(?:[/#?][-a-zA-Z0-9@:%_+.~#$!?&/=();,'\^{}\[\]` + "`" + `]*)?`)
|
||||||
urlPrefix string
|
)
|
||||||
}
|
|
||||||
|
|
||||||
var validLinksPattern = lazyregexp.New(`^[a-z][\w-]+://|^mailto:`)
|
|
||||||
|
|
||||||
// isLink reports whether link fits valid format.
|
|
||||||
func isLink(link []byte) bool {
|
func isLink(link []byte) bool {
|
||||||
return validLinksPattern.Match(link)
|
return validLinksPattern.Match(link)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Link defines how formal links should be processed to produce corresponding HTML elements.
|
type linkTransformer struct {
|
||||||
func (r *MarkdownRenderer) Link(out *bytes.Buffer, link, title, content []byte) {
|
urlPrefix string
|
||||||
if len(link) > 0 && !isLink(link) {
|
|
||||||
if link[0] != '#' {
|
|
||||||
link = []byte(path.Join(r.urlPrefix, string(link)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
r.Renderer.Link(out, link, title, content)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// AutoLink defines how auto-detected links should be processed to produce corresponding HTML elements.
|
func (t *linkTransformer) Transform(node *ast.Document, reader text.Reader, _ parser.Context) {
|
||||||
// Reference for kind: https://github.com/russross/blackfriday/blob/master/markdown.go#L69-L76
|
_ = ast.Walk(node, func(n ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
func (r *MarkdownRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
if !entering {
|
||||||
if kind != blackfriday.LINK_TYPE_NORMAL {
|
return ast.WalkContinue, nil
|
||||||
r.Renderer.AutoLink(out, link, kind)
|
}
|
||||||
return
|
if link, ok := n.(*ast.Link); ok {
|
||||||
|
dest := link.Destination
|
||||||
|
if len(dest) > 0 && !isLink(dest) && dest[0] != '#' {
|
||||||
|
link.Destination = []byte(joinURLPath(t.urlPrefix, string(dest)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// joinURLPath joins a base URL prefix with a relative path. It uses net/url for
|
||||||
|
// absolute URL prefixes to preserve the scheme and host, and path.Join for
|
||||||
|
// path-only prefixes.
|
||||||
|
func joinURLPath(prefix, relPath string) string {
|
||||||
|
u, err := url.Parse(prefix)
|
||||||
|
if err != nil || u.Scheme == "" {
|
||||||
|
return path.Join(prefix, relPath)
|
||||||
|
}
|
||||||
|
u.Path = path.Join(u.Path, relPath)
|
||||||
|
return u.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type gogsRenderer struct {
|
||||||
|
urlPrefix string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *gogsRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
|
||||||
|
reg.Register(ast.KindAutoLink, r.renderAutoLink)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *gogsRenderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
|
||||||
|
n := node.(*ast.AutoLink)
|
||||||
|
if !entering {
|
||||||
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since this method could only possibly serve one link at a time,
|
if n.AutoLinkType != ast.AutoLinkURL {
|
||||||
// we do not need to find all.
|
url := n.URL(source)
|
||||||
|
escaped := html.EscapeString(string(url))
|
||||||
|
_, _ = fmt.Fprintf(w, `<a href="mailto:%s">%s</a>`, escaped, escaped)
|
||||||
|
return ast.WalkContinue, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
link := n.URL(source)
|
||||||
|
|
||||||
if bytes.HasPrefix(link, []byte(conf.Server.ExternalURL)) {
|
if bytes.HasPrefix(link, []byte(conf.Server.ExternalURL)) {
|
||||||
m := CommitPattern.Find(link)
|
m := CommitPattern.Find(link)
|
||||||
if m != nil {
|
if m != nil {
|
||||||
@@ -68,8 +107,9 @@ func (r *MarkdownRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
|||||||
if j == -1 {
|
if j == -1 {
|
||||||
j = len(m)
|
j = len(m)
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(out, ` <code><a href="%s">%s</a></code>`, m, tool.ShortSHA1(string(m[i+7:j])))
|
escapedURL := html.EscapeString(string(m))
|
||||||
return
|
_, _ = fmt.Fprintf(w, ` <code><a href="%s">%s</a></code>`, escapedURL, tool.ShortSHA1(string(m[i+7:j])))
|
||||||
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
m = IssueFullPattern.Find(link)
|
m = IssueFullPattern.Find(link)
|
||||||
@@ -82,78 +122,64 @@ func (r *MarkdownRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
index := string(m[i+7 : j])
|
index := string(m[i+7 : j])
|
||||||
|
escapedURL := html.EscapeString(string(m))
|
||||||
fullRepoURL := conf.Server.ExternalURL + strings.TrimPrefix(r.urlPrefix, "/")
|
fullRepoURL := conf.Server.ExternalURL + strings.TrimPrefix(r.urlPrefix, "/")
|
||||||
var link string
|
var href string
|
||||||
if strings.HasPrefix(string(m), fullRepoURL) {
|
if strings.HasPrefix(string(m), fullRepoURL) {
|
||||||
// Use a short issue reference if the URL refers to this repository
|
href = fmt.Sprintf(`<a href="%s">#%s</a>`, escapedURL, html.EscapeString(index))
|
||||||
link = fmt.Sprintf(`<a href="%s">#%s</a>`, m, index)
|
|
||||||
} else {
|
} else {
|
||||||
// Use a cross-repository issue reference if the URL refers to a different repository
|
repo := html.EscapeString(string(m[len(conf.Server.ExternalURL) : i-1]))
|
||||||
repo := string(m[len(conf.Server.ExternalURL) : i-1])
|
href = fmt.Sprintf(`<a href="%s">%s#%s</a>`, escapedURL, repo, html.EscapeString(index))
|
||||||
link = fmt.Sprintf(`<a href="%s">%s#%s</a>`, m, repo, index)
|
|
||||||
}
|
}
|
||||||
out.WriteString(link)
|
_, _ = w.WriteString(href)
|
||||||
return
|
return ast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Renderer.AutoLink(out, link, kind)
|
escapedLink := html.EscapeString(string(link))
|
||||||
}
|
_, _ = fmt.Fprintf(w, `<a href="%s">%s</a>`, escapedLink, escapedLink)
|
||||||
|
return ast.WalkContinue, nil
|
||||||
// ListItem defines how list items should be processed to produce corresponding HTML elements.
|
|
||||||
func (r *MarkdownRenderer) ListItem(out *bytes.Buffer, text []byte, flags int) {
|
|
||||||
// Detect procedures to draw checkboxes.
|
|
||||||
switch {
|
|
||||||
case bytes.HasPrefix(text, []byte("[ ] ")):
|
|
||||||
text = append([]byte(`<input type="checkbox" disabled="" />`), text[3:]...)
|
|
||||||
case bytes.HasPrefix(text, []byte("[x] ")):
|
|
||||||
text = append([]byte(`<input type="checkbox" disabled="" checked="" />`), text[3:]...)
|
|
||||||
}
|
|
||||||
r.Renderer.ListItem(out, text, flags)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// RawMarkdown renders content in Markdown syntax to HTML without handling special links.
|
// RawMarkdown renders content in Markdown syntax to HTML without handling special links.
|
||||||
func RawMarkdown(body []byte, urlPrefix string) []byte {
|
func RawMarkdown(body []byte, urlPrefix string) []byte {
|
||||||
htmlFlags := 0
|
extensions := []goldmark.Extender{
|
||||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
extension.Table,
|
||||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
extension.Strikethrough,
|
||||||
|
extension.TaskList,
|
||||||
|
extension.NewLinkify(extension.WithLinkifyURLRegexp(linkifyURLRegexp.Regexp())),
|
||||||
|
}
|
||||||
|
|
||||||
if conf.Smartypants.Enabled {
|
if conf.Smartypants.Enabled {
|
||||||
htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
|
extensions = append(extensions, extension.Typographer)
|
||||||
if conf.Smartypants.Fractions {
|
|
||||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
|
|
||||||
}
|
|
||||||
if conf.Smartypants.Dashes {
|
|
||||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_DASHES
|
|
||||||
}
|
|
||||||
if conf.Smartypants.LatexDashes {
|
|
||||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
|
|
||||||
}
|
|
||||||
if conf.Smartypants.AngledQuotes {
|
|
||||||
htmlFlags |= blackfriday.HTML_SMARTYPANTS_ANGLED_QUOTES
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer := &MarkdownRenderer{
|
rendererOpts := []renderer.Option{
|
||||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
renderer.WithNodeRenderers(
|
||||||
urlPrefix: urlPrefix,
|
util.Prioritized(&gogsRenderer{urlPrefix: urlPrefix}, 0),
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up the parser
|
|
||||||
extensions := 0
|
|
||||||
extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
|
|
||||||
extensions |= blackfriday.EXTENSION_TABLES
|
|
||||||
extensions |= blackfriday.EXTENSION_FENCED_CODE
|
|
||||||
extensions |= blackfriday.EXTENSION_AUTOLINK
|
|
||||||
extensions |= blackfriday.EXTENSION_STRIKETHROUGH
|
|
||||||
extensions |= blackfriday.EXTENSION_SPACE_HEADERS
|
|
||||||
extensions |= blackfriday.EXTENSION_NO_EMPTY_LINE_BEFORE_BLOCK
|
|
||||||
|
|
||||||
if conf.Markdown.EnableHardLineBreak {
|
if conf.Markdown.EnableHardLineBreak {
|
||||||
extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK
|
rendererOpts = append(rendererOpts, goldmarkhtml.WithHardWraps())
|
||||||
}
|
}
|
||||||
|
|
||||||
return blackfriday.Markdown(body, renderer, extensions)
|
md := goldmark.New(
|
||||||
|
goldmark.WithExtensions(extensions...),
|
||||||
|
goldmark.WithParserOptions(
|
||||||
|
parser.WithASTTransformers(
|
||||||
|
util.Prioritized(&linkTransformer{urlPrefix: urlPrefix}, 0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
goldmark.WithRendererOptions(rendererOpts...),
|
||||||
|
)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
if err := md.Convert(body, &buf); err != nil {
|
||||||
|
log.Error("Failed to convert Markdown: %v", err)
|
||||||
|
return []byte(html.EscapeString(string(body)))
|
||||||
|
}
|
||||||
|
return buf.Bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Markdown takes a string or []byte and renders to HTML in Markdown syntax with special links.
|
// Markdown takes a string or []byte and renders to HTML in Markdown syntax with special links.
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package markup_test
|
package markup_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/russross/blackfriday"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"gogs.io/gogs/internal/conf"
|
"gogs.io/gogs/internal/conf"
|
||||||
@@ -13,7 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func Test_IsMarkdownFile(t *testing.T) {
|
func Test_IsMarkdownFile(t *testing.T) {
|
||||||
// TODO: Refactor to accept a list of extensions
|
oldExts := conf.Markdown.FileExtensions
|
||||||
|
defer func() { conf.Markdown.FileExtensions = oldExts }()
|
||||||
|
|
||||||
conf.Markdown.FileExtensions = strings.Split(".md,.markdown,.mdown,.mkd", ",")
|
conf.Markdown.FileExtensions = strings.Split(".md,.markdown,.mdown,.mkd", ",")
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
ext string
|
ext string
|
||||||
@@ -32,41 +32,152 @@ func Test_IsMarkdownFile(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Test_Markdown(t *testing.T) {
|
func Test_RawMarkdown_AutoLink(t *testing.T) {
|
||||||
// TODO: Refactor to accept URL
|
oldURL := conf.Server.ExternalURL
|
||||||
|
defer func() { conf.Server.ExternalURL = oldURL }()
|
||||||
|
|
||||||
conf.Server.ExternalURL = "http://localhost:3000/"
|
conf.Server.ExternalURL = "http://localhost:3000/"
|
||||||
|
|
||||||
htmlFlags := 0
|
|
||||||
htmlFlags |= blackfriday.HTML_SKIP_STYLE
|
|
||||||
htmlFlags |= blackfriday.HTML_OMIT_CONTENTS
|
|
||||||
renderer := &MarkdownRenderer{
|
|
||||||
Renderer: blackfriday.HtmlRenderer(htmlFlags, "", ""),
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
input string
|
name string
|
||||||
expVal string
|
input string
|
||||||
|
want string
|
||||||
}{
|
}{
|
||||||
// Issue URL
|
{
|
||||||
{input: "http://localhost:3000/user/repo/issues/3333", expVal: "<a href=\"http://localhost:3000/user/repo/issues/3333\">#3333</a>"},
|
name: "issue URL from same instance",
|
||||||
{input: "http://1111/2222/ssss-issues/3333?param=blah&blahh=333", expVal: "<a href=\"http://1111/2222/ssss-issues/3333?param=blah&blahh=333\">http://1111/2222/ssss-issues/3333?param=blah&blahh=333</a>"},
|
input: "http://localhost:3000/user/repo/issues/3333",
|
||||||
{input: "http://test.com/issues/33333", expVal: "<a href=\"http://test.com/issues/33333\">http://test.com/issues/33333</a>"},
|
want: "<p><a href=\"http://localhost:3000/user/repo/issues/3333\">#3333</a></p>\n",
|
||||||
{input: "http://test.com/issues/3", expVal: "<a href=\"http://test.com/issues/3\">http://test.com/issues/3</a>"},
|
},
|
||||||
{input: "http://issues/333", expVal: "<a href=\"http://issues/333\">http://issues/333</a>"},
|
{
|
||||||
{input: "https://issues/333", expVal: "<a href=\"https://issues/333\">https://issues/333</a>"},
|
name: "non-matching issue-like URL",
|
||||||
{input: "http://tissues/0", expVal: "<a href=\"http://tissues/0\">http://tissues/0</a>"},
|
input: "http://1111/2222/ssss-issues/3333?param=blah&blahh=333",
|
||||||
|
want: "<p><a href=\"http://1111/2222/ssss-issues/3333?param=blah&blahh=333\">http://1111/2222/ssss-issues/3333?param=blah&blahh=333</a></p>\n",
|
||||||
// Commit URL
|
},
|
||||||
{input: "http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae", expVal: " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">d8a994ef24</a></code>"},
|
{
|
||||||
{input: "http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", expVal: " <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">d8a994ef24</a></code>"},
|
name: "external issue URL",
|
||||||
{input: "https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2", expVal: "<a href=\"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2</a>"},
|
input: "http://test.com/issues/33333",
|
||||||
{input: "https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae", expVal: "<a href=\"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae</a>"},
|
want: "<p><a href=\"http://test.com/issues/33333\">http://test.com/issues/33333</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "commit URL from same instance",
|
||||||
|
input: "http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||||
|
want: "<p> <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">d8a994ef24</a></code></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "commit URL with fragment from same instance",
|
||||||
|
input: "http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2",
|
||||||
|
want: "<p> <code><a href=\"http://localhost:3000/user/project/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">d8a994ef24</a></code></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "external commit URL",
|
||||||
|
input: "https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2",
|
||||||
|
want: "<p><a href=\"https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2\">https://external-link.gogs.io/gogs/gogs/commit/d8a994ef243349f321568f9e36d5c3f444b99cae#diff-2</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "issue URL with single digit",
|
||||||
|
input: "http://test.com/issues/3",
|
||||||
|
want: "<p><a href=\"http://test.com/issues/3\">http://test.com/issues/3</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host without dot in issue-like URL",
|
||||||
|
input: "http://issues/333",
|
||||||
|
want: "<p><a href=\"http://issues/333\">http://issues/333</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "https host without dot in issue-like URL",
|
||||||
|
input: "https://issues/333",
|
||||||
|
want: "<p><a href=\"https://issues/333\">https://issues/333</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "host without dot resembling keyword",
|
||||||
|
input: "http://tissues/0",
|
||||||
|
want: "<p><a href=\"http://tissues/0\">http://tissues/0</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "https commit-like URL without dot",
|
||||||
|
input: "https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae",
|
||||||
|
want: "<p><a href=\"https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae\">https://commit/d8a994ef243349f321568f9e36d5c3f444b99cae</a></p>\n",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run(test.name, func(t *testing.T) {
|
||||||
buf := new(bytes.Buffer)
|
got := string(RawMarkdown([]byte(test.input), ""))
|
||||||
renderer.AutoLink(buf, []byte(test.input), blackfriday.LINK_TYPE_NORMAL)
|
assert.Equal(t, test.want, got)
|
||||||
assert.Equal(t, test.expVal, buf.String())
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("cross-repo issue URL from same instance", func(t *testing.T) {
|
||||||
|
got := string(RawMarkdown([]byte("http://localhost:3000/other/repo/issues/42"), "/user/myrepo"))
|
||||||
|
assert.Equal(t, "<p><a href=\"http://localhost:3000/other/repo/issues/42\">other/repo#42</a></p>\n", got)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("same-repo issue URL with fragment", func(t *testing.T) {
|
||||||
|
got := string(RawMarkdown([]byte("http://localhost:3000/user/myrepo/issues/42#issuecomment-1"), "/user/myrepo"))
|
||||||
|
assert.Equal(t, "<p><a href=\"http://localhost:3000/user/myrepo/issues/42#issuecomment-1\">#42</a></p>\n", got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_RawMarkdown_LinkRewriting(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
urlPrefix string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "relative link with path-only prefix",
|
||||||
|
input: "[text](other-file.md)",
|
||||||
|
urlPrefix: "/user/repo/src/branch/main",
|
||||||
|
want: "<p><a href=\"/user/repo/src/branch/main/other-file.md\">text</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "relative link with absolute URL prefix",
|
||||||
|
input: "[text](other-file.md)",
|
||||||
|
urlPrefix: "http://localhost:3000/user/repo/src/branch/main",
|
||||||
|
want: "<p><a href=\"http://localhost:3000/user/repo/src/branch/main/other-file.md\">text</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "absolute link not rewritten",
|
||||||
|
input: "[text](https://example.com/page)",
|
||||||
|
urlPrefix: "/user/repo/src/branch/main",
|
||||||
|
want: "<p><a href=\"https://example.com/page\">text</a></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "anchor-only link not rewritten",
|
||||||
|
input: "[text](#section)",
|
||||||
|
urlPrefix: "/user/repo/src/branch/main",
|
||||||
|
want: "<p><a href=\"#section\">text</a></p>\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := string(RawMarkdown([]byte(test.input), test.urlPrefix))
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_RawMarkdown_HTMLPassthrough(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "inline HTML tags are stripped",
|
||||||
|
input: "Hello <em>world</em>",
|
||||||
|
want: "<p>Hello <!-- raw HTML omitted -->world<!-- raw HTML omitted --></p>\n",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "block HTML tags are stripped",
|
||||||
|
input: "<div>content</div>",
|
||||||
|
want: "<!-- raw HTML omitted -->\n",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
got := string(RawMarkdown([]byte(test.input), ""))
|
||||||
|
assert.Equal(t, test.want, got)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user