Repository: gohugoio/hugo (language: go)

Issue:
Default to https for linkify
See closed issue upstream: https://github.com/yuin/goldmark/issues/267

To me it does not make sense that the CommonMark spec should dictate the protocol for the rendered markup, so we need to fix that.

I suggest we add another config option, so we get:

```
linkify = true
linkifyProtocol = "https"
```


Discussion / hints:

Fix patch:
diff --git a/markup/goldmark/convert.go b/markup/goldmark/convert.go
index 4c1641a0b56..9442ee9e7ac 100644
--- a/markup/goldmark/convert.go
+++ b/markup/goldmark/convert.go
@@ -94,7 +94,7 @@ func newMarkdown(pcfg converter.ProviderConfig) goldmark.Markdown {
 
 	var (
 		extensions = []goldmark.Extender{
-			newLinks(),
+			newLinks(cfg),
 			newTocExtension(rendererOptions),
 		}
 		parserOptions []parser.Option
diff --git a/markup/goldmark/goldmark_config/config.go b/markup/goldmark/goldmark_config/config.go
index 82b8d96301c..a3238091b18 100644
--- a/markup/goldmark/goldmark_config/config.go
+++ b/markup/goldmark/goldmark_config/config.go
@@ -23,13 +23,14 @@ const (
 // DefaultConfig holds the default Goldmark configuration.
 var Default = Config{
 	Extensions: Extensions{
-		Typographer:    true,
-		Footnote:       true,
-		DefinitionList: true,
-		Table:          true,
-		Strikethrough:  true,
-		Linkify:        true,
-		TaskList:       true,
+		Typographer:     true,
+		Footnote:        true,
+		DefinitionList:  true,
+		Table:           true,
+		Strikethrough:   true,
+		Linkify:         true,
+		LinkifyProtocol: "https",
+		TaskList:        true,
 	},
 	Renderer: Renderer{
 		Unsafe: false,
@@ -57,10 +58,11 @@ type Extensions struct {
 	DefinitionList bool
 
 	// GitHub flavored markdown
-	Table         bool
-	Strikethrough bool
-	Linkify       bool
-	TaskList      bool
+	Table           bool
+	Strikethrough   bool
+	Linkify         bool
+	LinkifyProtocol string
+	TaskList        bool
 }
 
 type Renderer struct {
diff --git a/markup/goldmark/render_hooks.go b/markup/goldmark/render_hooks.go
index 138a60d263b..a22030f545c 100644
--- a/markup/goldmark/render_hooks.go
+++ b/markup/goldmark/render_hooks.go
@@ -18,6 +18,7 @@ import (
 	"strings"
 
 	"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/internal/attributes"
 
@@ -30,8 +31,9 @@ import (
 
 var _ renderer.SetOptioner = (*hookedRenderer)(nil)
 
-func newLinkRenderer() renderer.NodeRenderer {
+func newLinkRenderer(cfg goldmark_config.Config) renderer.NodeRenderer {
 	r := &hookedRenderer{
+		linkifyProtocol: []byte(cfg.Extensions.LinkifyProtocol),
 		Config: html.Config{
 			Writer: html.DefaultWriter,
 		},
@@ -39,8 +41,8 @@ func newLinkRenderer() renderer.NodeRenderer {
 	return r
 }
 
-func newLinks() goldmark.Extender {
-	return &links{}
+func newLinks(cfg goldmark_config.Config) goldmark.Extender {
+	return &links{cfg: cfg}
 }
 
 type linkContext struct {
@@ -105,6 +107,7 @@ func (ctx headingContext) PlainText() string {
 }
 
 type hookedRenderer struct {
+	linkifyProtocol []byte
 	html.Config
 }
 
@@ -279,7 +282,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
 		return r.renderAutoLinkDefault(w, source, node, entering)
 	}
 
-	url := string(n.URL(source))
+	url := string(r.autoLinkURL(n, source))
 	label := string(n.Label(source))
 	if n.AutoLinkType == ast.AutoLinkEmail && !strings.HasPrefix(strings.ToLower(url), "mailto:") {
 		url = "mailto:" + url
@@ -310,8 +313,9 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte,
 	if !entering {
 		return ast.WalkContinue, nil
 	}
+
 	_, _ = w.WriteString(`<a href="`)
-	url := n.URL(source)
+	url := r.autoLinkURL(n, source)
 	label := n.Label(source)
 	if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
 		_, _ = w.WriteString("mailto:")
@@ -329,6 +333,17 @@ func (r *hookedRenderer) renderAutoLinkDefault(w util.BufWriter, source []byte,
 	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) {
 	n := node.(*ast.Heading)
 	var hr hooks.HeadingRenderer
@@ -394,11 +409,13 @@ func (r *hookedRenderer) renderHeadingDefault(w util.BufWriter, source []byte, n
 	return ast.WalkContinue, nil
 }
 
-type links struct{}
+type links struct {
+	cfg goldmark_config.Config
+}
 
 // Extend implements goldmark.Extender.
 func (e *links) Extend(m goldmark.Markdown) {
 	m.Renderer().AddOptions(renderer.WithNodeRenderers(
-		util.Prioritized(newLinkRenderer(), 100),
+		util.Prioritized(newLinkRenderer(e.cfg), 100),
 	))
 }


Test patch:
diff --git a/markup/goldmark/integration_test.go b/markup/goldmark/integration_test.go
index 89cd5bbb644..d8f218b31ff 100644
--- a/markup/goldmark/integration_test.go
+++ b/markup/goldmark/integration_test.go
@@ -423,3 +423,70 @@ title: "p1"
 		<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>",
+		)
+
+	}
+}