mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -05:00
Fail with error when double-rendering text in markdownify/RenderString
This commit prevents the most commons case of infinite recursion in link render hooks when the `linkify` option is enabled (see below). This is always a user error, but getting a `stack overflow` (the current stack limit in Go is 1 GB on 64-bit, 250 MB on 32-bit) error isn't very helpful. This fix will not prevent all such errors, though, but we may do better once #9570 is in place. So, these will fail: ``` <a href="{{ .Destination | safeURL }}" >{{ .Text | markdownify }}</a> <a href="{{ .Destination | safeURL }}" >{{ .Text | .Page.RenderString }}</a> ``` `.Text` is already rendered to `HTML`. The above needs to be rewritten to: ``` <a href="{{ .Destination | safeURL }}" >{{ .Text | safeHTML }}</a> <a href="{{ .Destination | safeURL }}" >{{ .Text | safeHTML }}</a> ``` Fixes #8959
This commit is contained in:
parent
5697348e17
commit
4e14cf7607
7 changed files with 122 additions and 20 deletions
20
common/types/hstring/stringtypes.go
Normal file
20
common/types/hstring/stringtypes.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package hstring
|
||||||
|
|
||||||
|
type RenderedString string
|
||||||
|
|
||||||
|
func (s RenderedString) String() string {
|
||||||
|
return string(s)
|
||||||
|
}
|
30
common/types/hstring/stringtypes_test.go
Normal file
30
common/types/hstring/stringtypes_test.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// Copyright 2022 The Hugo Authors. All rights reserved.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package hstring
|
||||||
|
|
||||||
|
import (
|
||||||
|
"html/template"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
"github.com/spf13/cast"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringTypes(t *testing.T) {
|
||||||
|
c := qt.New(t)
|
||||||
|
|
||||||
|
// Validate that it will behave like a string in Hugo settings.
|
||||||
|
c.Assert(cast.ToString(RenderedString("Hugo")), qt.Equals, "Hugo")
|
||||||
|
c.Assert(template.HTML(RenderedString("Hugo")), qt.Equals, template.HTML("Hugo"))
|
||||||
|
}
|
|
@ -24,6 +24,7 @@ import (
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/text"
|
"github.com/gohugoio/hugo/common/text"
|
||||||
|
"github.com/gohugoio/hugo/common/types/hstring"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
@ -351,8 +352,16 @@ func (p *pageContentOutput) RenderString(args ...interface{}) (template.HTML, er
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
contentToRender := args[sidx]
|
||||||
|
|
||||||
|
if _, ok := contentToRender.(hstring.RenderedString); ok {
|
||||||
|
// This content is already rendered, this is potentially
|
||||||
|
// a infinite recursion.
|
||||||
|
return "", errors.New("text is already rendered, repeating it may cause infinite recursion")
|
||||||
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
s, err = cast.ToStringE(args[sidx])
|
s, err = cast.ToStringE(contentToRender)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
@ -515,7 +524,6 @@ func (p *pageContentOutput) initRenderHooks() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found1 {
|
if !found1 {
|
||||||
if tp == hooks.CodeBlockRendererType {
|
if tp == hooks.CodeBlockRendererType {
|
||||||
// No user provided tempplate for code blocks, so we use the native Go code version -- which is also faster.
|
// No user provided tempplate for code blocks, so we use the native Go code version -- which is also faster.
|
||||||
|
|
|
@ -18,6 +18,7 @@ import (
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/common/hugio"
|
"github.com/gohugoio/hugo/common/hugio"
|
||||||
"github.com/gohugoio/hugo/common/text"
|
"github.com/gohugoio/hugo/common/text"
|
||||||
|
"github.com/gohugoio/hugo/common/types/hstring"
|
||||||
"github.com/gohugoio/hugo/identity"
|
"github.com/gohugoio/hugo/identity"
|
||||||
"github.com/gohugoio/hugo/markup/internal/attributes"
|
"github.com/gohugoio/hugo/markup/internal/attributes"
|
||||||
)
|
)
|
||||||
|
@ -32,7 +33,7 @@ type LinkContext interface {
|
||||||
Page() interface{}
|
Page() interface{}
|
||||||
Destination() string
|
Destination() string
|
||||||
Title() string
|
Title() string
|
||||||
Text() string
|
Text() hstring.RenderedString
|
||||||
PlainText() string
|
PlainText() string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,7 +76,7 @@ type HeadingContext interface {
|
||||||
// Anchor is the HTML id assigned to the heading.
|
// Anchor is the HTML id assigned to the heading.
|
||||||
Anchor() string
|
Anchor() string
|
||||||
// Text is the rendered (HTML) heading text, excluding the heading marker.
|
// Text is the rendered (HTML) heading text, excluding the heading marker.
|
||||||
Text() string
|
Text() hstring.RenderedString
|
||||||
// PlainText is the unrendered version of Text.
|
// PlainText is the unrendered version of Text.
|
||||||
PlainText() string
|
PlainText() string
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
qt "github.com/frankban/quicktest"
|
||||||
|
|
||||||
"github.com/gohugoio/hugo/hugolib"
|
"github.com/gohugoio/hugo/hugolib"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -395,6 +397,49 @@ FENCE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iisse #8959
|
||||||
|
func TestHookInfiniteRecursion(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
for _, renderFunc := range []string{"markdownify", ".Page.RenderString"} {
|
||||||
|
t.Run(renderFunc, func(t *testing.T) {
|
||||||
|
|
||||||
|
files := `
|
||||||
|
-- config.toml --
|
||||||
|
-- layouts/_default/_markup/render-link.html --
|
||||||
|
<a href="{{ .Destination | safeURL }}">{{ .Text | RENDERFUNC }}</a>
|
||||||
|
-- layouts/_default/single.html --
|
||||||
|
{{ .Content }}
|
||||||
|
-- content/p1.md --
|
||||||
|
---
|
||||||
|
title: "p1"
|
||||||
|
---
|
||||||
|
|
||||||
|
https://example.org
|
||||||
|
|
||||||
|
a@b.com
|
||||||
|
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
files = strings.ReplaceAll(files, "RENDERFUNC", renderFunc)
|
||||||
|
|
||||||
|
b, err := hugolib.NewIntegrationTestBuilder(
|
||||||
|
hugolib.IntegrationTestConfig{
|
||||||
|
T: t,
|
||||||
|
TxtarString: files,
|
||||||
|
},
|
||||||
|
).BuildE()
|
||||||
|
|
||||||
|
b.Assert(err, qt.IsNotNil)
|
||||||
|
b.Assert(err.Error(), qt.Contains, "text is already rendered, repeating it may cause infinite recursion")
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
// Issue 9594
|
// Issue 9594
|
||||||
func TestQuotesInImgAltAttr(t *testing.T) {
|
func TestQuotesInImgAltAttr(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gohugoio/hugo/common/types/hstring"
|
||||||
"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/goldmark_config"
|
||||||
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
"github.com/gohugoio/hugo/markup/goldmark/internal/render"
|
||||||
|
@ -49,7 +50,7 @@ type linkContext struct {
|
||||||
page interface{}
|
page interface{}
|
||||||
destination string
|
destination string
|
||||||
title string
|
title string
|
||||||
text string
|
text hstring.RenderedString
|
||||||
plainText string
|
plainText string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,7 +66,7 @@ func (ctx linkContext) Page() interface{} {
|
||||||
return ctx.page
|
return ctx.page
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx linkContext) Text() string {
|
func (ctx linkContext) Text() hstring.RenderedString {
|
||||||
return ctx.text
|
return ctx.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,7 +82,7 @@ type headingContext struct {
|
||||||
page interface{}
|
page interface{}
|
||||||
level int
|
level int
|
||||||
anchor string
|
anchor string
|
||||||
text string
|
text hstring.RenderedString
|
||||||
plainText string
|
plainText string
|
||||||
*attributes.AttributesHolder
|
*attributes.AttributesHolder
|
||||||
}
|
}
|
||||||
|
@ -98,7 +99,7 @@ func (ctx headingContext) Anchor() string {
|
||||||
return ctx.anchor
|
return ctx.anchor
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ctx headingContext) Text() string {
|
func (ctx headingContext) Text() hstring.RenderedString {
|
||||||
return ctx.text
|
return ctx.text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +157,7 @@ func (r *hookedRenderer) renderImage(w util.BufWriter, source []byte, node ast.N
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
destination: string(n.Destination),
|
destination: string(n.Destination),
|
||||||
title: string(n.Title),
|
title: string(n.Title),
|
||||||
text: string(text),
|
text: hstring.RenderedString(text),
|
||||||
plainText: string(n.Text(source)),
|
plainText: string(n.Text(source)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -226,7 +227,7 @@ func (r *hookedRenderer) renderLink(w util.BufWriter, source []byte, node ast.No
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
destination: string(n.Destination),
|
destination: string(n.Destination),
|
||||||
title: string(n.Title),
|
title: string(n.Title),
|
||||||
text: string(text),
|
text: hstring.RenderedString(text),
|
||||||
plainText: string(n.Text(source)),
|
plainText: string(n.Text(source)),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -293,7 +294,7 @@ func (r *hookedRenderer) renderAutoLink(w util.BufWriter, source []byte, node as
|
||||||
linkContext{
|
linkContext{
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
destination: url,
|
destination: url,
|
||||||
text: label,
|
text: hstring.RenderedString(label),
|
||||||
plainText: label,
|
plainText: label,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -381,7 +382,7 @@ func (r *hookedRenderer) renderHeading(w util.BufWriter, source []byte, node ast
|
||||||
page: ctx.DocumentContext().Document,
|
page: ctx.DocumentContext().Document,
|
||||||
level: n.Level,
|
level: n.Level,
|
||||||
anchor: string(anchor),
|
anchor: string(anchor),
|
||||||
text: string(text),
|
text: hstring.RenderedString(text),
|
||||||
plainText: string(n.Text(source)),
|
plainText: string(n.Text(source)),
|
||||||
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
|
AttributesHolder: attributes.New(n.Attributes(), attributes.AttributesOwnerGeneral),
|
||||||
},
|
},
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
|
|
||||||
"github.com/alecthomas/chroma/lexers"
|
"github.com/alecthomas/chroma/lexers"
|
||||||
"github.com/gohugoio/hugo/cache/namedmemcache"
|
"github.com/gohugoio/hugo/cache/namedmemcache"
|
||||||
"github.com/gohugoio/hugo/common/herrors"
|
|
||||||
"github.com/gohugoio/hugo/markup/converter/hooks"
|
"github.com/gohugoio/hugo/markup/converter/hooks"
|
||||||
"github.com/gohugoio/hugo/markup/highlight"
|
"github.com/gohugoio/hugo/markup/highlight"
|
||||||
|
|
||||||
|
@ -119,20 +118,18 @@ func (ns *Namespace) HTMLUnescape(s interface{}) (string, error) {
|
||||||
|
|
||||||
// Markdownify renders a given input from Markdown to HTML.
|
// Markdownify renders a given input from Markdown to HTML.
|
||||||
func (ns *Namespace) Markdownify(s interface{}) (template.HTML, error) {
|
func (ns *Namespace) Markdownify(s interface{}) (template.HTML, error) {
|
||||||
defer herrors.Recover()
|
|
||||||
ss, err := cast.ToStringE(s)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
home := ns.deps.Site.Home()
|
home := ns.deps.Site.Home()
|
||||||
if home == nil {
|
if home == nil {
|
||||||
panic("home must not be nil")
|
panic("home must not be nil")
|
||||||
}
|
}
|
||||||
sss, err := home.RenderString(ss)
|
ss, err := home.RenderString(s)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
// Strip if this is a short inline type of text.
|
// Strip if this is a short inline type of text.
|
||||||
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(sss))
|
bb := ns.deps.ContentSpec.TrimShortHTML([]byte(ss))
|
||||||
|
|
||||||
return helpers.BytesToHTML(bb), nil
|
return helpers.BytesToHTML(bb), nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue