markup/goldmark: Default to https for linkify

Fixes #9639
This commit is contained in:
Bjørn Erik Pedersen 2022-03-09 18:26:32 +01:00
parent f98e570b17
commit 5697348e17
4 changed files with 105 additions and 19 deletions

View file

@ -94,7 +94,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
var ( var (
extensions = []goldmark.Extender{ extensions = []goldmark.Extender{
newLinks(), newLinks(cfg),
newTocExtension(rendererOptions), newTocExtension(rendererOptions),
} }
parserOptions []parser.Option parserOptions []parser.Option

View file

@ -23,13 +23,14 @@ const (
// DefaultConfig holds the default Goldmark configuration. // DefaultConfig holds the default Goldmark configuration.
var Default = Config{ var Default = Config{
Extensions: Extensions{ Extensions: Extensions{
Typographer: true, Typographer: true,
Footnote: true, Footnote: true,
DefinitionList: true, DefinitionList: true,
Table: true, Table: true,
Strikethrough: true, Strikethrough: true,
Linkify: true, Linkify: true,
TaskList: true, LinkifyProtocol: "https",
TaskList: true,
}, },
Renderer: Renderer{ Renderer: Renderer{
Unsafe: false, Unsafe: false,
@ -57,10 +58,11 @@ type Extensions struct {
DefinitionList bool DefinitionList bool
// GitHub flavored markdown // GitHub flavored markdown
Table bool Table bool
Strikethrough bool Strikethrough bool
Linkify bool Linkify bool
TaskList bool LinkifyProtocol string
TaskList bool
} }
type Renderer struct { type Renderer struct {

View file

@ -423,3 +423,70 @@ title: "p1"
<img src="b.jpg" alt="&quot;a&quot;"> <img src="b.jpg" alt="&quot;a&quot;">
`) `)
} }
func TestLinkifyProtocol(t *testing.T) {
t.Parallel()
runTest := func(protocol string, withHook bool) *hugolib.IntegrationTestBuilder {
files := `
-- config.toml --
[markup.goldmark]
[markup.goldmark.extensions]
linkify = true
linkifyProtocol = "PROTOCOL"
-- content/p1.md --
---
title: "p1"
---
Link no procol: www.example.org
Link http procol: http://www.example.org
Link https procol: https://www.example.org
-- layouts/_default/single.html --
{{ .Content }}
`
files = strings.ReplaceAll(files, "PROTOCOL", protocol)
if withHook {
files += `-- layouts/_default/_markup/render-link.html --
<a href="{{ .Destination | safeURL }}">{{ .Text | safeHTML }}</a>`
}
return hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).Build()
}
for _, withHook := range []bool{false, true} {
b := runTest("https", withHook)
b.AssertFileContent("public/p1/index.html",
"Link no procol: <a href=\"https://www.example.org\">www.example.org</a>",
"Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>",
"Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>",
)
b = runTest("http", withHook)
b.AssertFileContent("public/p1/index.html",
"Link no procol: <a href=\"http://www.example.org\">www.example.org</a>",
"Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>",
"Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>",
)
b = runTest("gopher", withHook)
b.AssertFileContent("public/p1/index.html",
"Link no procol: <a href=\"gopher://www.example.org\">www.example.org</a>",
"Link http procol: <a href=\"http://www.example.org\">http://www.example.org</a>",
"Link https procol: <a href=\"https://www.example.org\">https://www.example.org</a></p>",
)
}
}

View file

@ -18,6 +18,7 @@ import (
"strings" "strings"
"github.com/gohugoio/hugo/markup/converter/hooks" "github.com/gohugoio/hugo/markup/converter/hooks"
"github.com/gohugoio/hugo/markup/goldmark/goldmark_config"
"github.com/gohugoio/hugo/markup/goldmark/internal/render" "github.com/gohugoio/hugo/markup/goldmark/internal/render"
"github.com/gohugoio/hugo/markup/internal/attributes" "github.com/gohugoio/hugo/markup/internal/attributes"
@ -30,8 +31,9 @@ import (
var _ renderer.SetOptioner = (*hookedRenderer)(nil) var _ renderer.SetOptioner = (*hookedRenderer)(nil)
func newLinkRenderer() renderer.NodeRenderer { func newLinkRenderer(cfg goldmark_config.Config) renderer.NodeRenderer {
r := &hookedRenderer{ r := &hookedRenderer{
linkifyProtocol: []byte(cfg.Extensions.LinkifyProtocol),
Config: html.Config{ Config: html.Config{
Writer: html.DefaultWriter, Writer: html.DefaultWriter,
}, },
@ -39,8 +41,8 @@ func newLinkRenderer() renderer.NodeRenderer {
return r return r
} }
func newLinks() goldmark.Extender { func newLinks(cfg goldmark_config.Config) goldmark.Extender {
return &links{} return &links{cfg: cfg}
} }
type linkContext struct { type linkContext struct {
@ -105,6 +107,7 @@ func (ctx headingContext) PlainText() string {
} }
type hookedRenderer struct { type hookedRenderer struct {
linkifyProtocol []byte
html.Config html.Config
} }
@ -279,7 +282,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
return r.renderAutoLinkDefault(w, source, node, entering) return r.renderAutoLinkDefault(w, source, node, entering)
} }
url := string(n.URL(source)) url := string(r.autoLinkURL(n, source))
label := string(n.Label(source)) label := string(n.Label(source))
if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(url), "mailto:") { if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(url), "mailto:") {
url = "mailto:" + url url = "mailto:" + url
@ -310,8 +313,9 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte,
if !entering { if !entering {
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }
_, _ = w.WriteString(`<a href="`) _, _ = w.WriteString(`<a href="`)
url := n.URL(source) url := r.autoLinkURL(n, source)
label := n.Label(source) label := n.Label(source)
if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) { if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
_, _ = w.WriteString("mailto:") _, _ = w.WriteString("mailto:")
@ -329,6 +333,17 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte,
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }
func (r *hookedRenderer) autoLinkURL(n *ast.AutoLink, source []byte) []byte {
url := n.URL(source)
if len(n.Protocol) > 0 && !bytes.Equal(n.Protocol, r.linkifyProtocol) {
// The CommonMark spec says "http" is the correct protocol for links,
// but this doesn't make much sense (the fact that they should care about the rendered output).
// Note that n.Protocol is not set if protocol is provided by user.
url = append(r.linkifyProtocol, url[len(n.Protocol):]...)
}
return url
}
func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) { func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
n := node.(*ast.Heading) n := node.(*ast.Heading)
var hr hooks.HeadingRenderer var hr hooks.HeadingRenderer
@ -394,11 +409,13 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n
return ast.WalkContinue, nil return ast.WalkContinue, nil
} }
type links struct{} type links struct {
cfg goldmark_config.Config
}
// Extend implements goldmark.Extender. // Extend implements goldmark.Extender.
func (e *links) Extend(m goldmark.Markdown) { func (e *links) Extend(m goldmark.Markdown) {
m.Renderer().AddOptions(renderer.WithNodeRenderers( m.Renderer().AddOptions(renderer.WithNodeRenderers(
util.Prioritized(newLinkRenderer(), 100), util.Prioritized(newLinkRenderer(e.cfg), 100),
)) ))
} }