markup/goldmark: Fail on invalid Markdown attributes

This commit is contained in:
Bjørn Erik Pedersen 2023-03-14 12:45:09 +01:00
parent 0fbab7cbc5
commit b0b1b76dc9
5 changed files with 89 additions and 10 deletions

View file

@ -61,6 +61,16 @@ var OffsetMatcher = func(m LineMatcher) int {
return -1 return -1
} }
// ContainsMatcher is a line matcher that matches by line content.
func ContainsMatcher(text string) func(m LineMatcher) int {
return func(m LineMatcher) int {
if idx := strings.Index(m.Line, text); idx != -1 {
return idx + 1
}
return -1
}
}
// ErrorContext contains contextual information about an error. This will // ErrorContext contains contextual information about an error. This will
// typically be the lines surrounding some problem in a file. // typically be the lines surrounding some problem in a file.
type ErrorContext struct { type ErrorContext struct {

View file

@ -392,3 +392,17 @@ func extractPosition(e error) (pos text.Position) {
} }
return return
} }
// TextSegmentError is an error with a text segment attached.
type TextSegmentError struct {
Segment string
Err error
}
func (e TextSegmentError) Unwrap() error {
return e.Err
}
func (e TextSegmentError) Error() string {
return e.Err.Error()
}

View file

@ -617,7 +617,13 @@ func (p *pageState) wrapError(err error) error {
} }
} }
return herrors.NewFileErrorFromFile(err, filename, p.s.SourceSpec.Fs.Source, herrors.NopLineMatcher) lineMatcher := herrors.NopLineMatcher
if textSegmentErr, ok := err.(*herrors.TextSegmentError); ok {
lineMatcher = herrors.ContainsMatcher(textSegmentErr.Segment)
}
return herrors.NewFileErrorFromFile(err, filename, p.s.SourceSpec.Fs.Source, lineMatcher)
} }

View file

@ -18,6 +18,8 @@ import (
"strings" "strings"
"testing" "testing"
qt "github.com/frankban/quicktest"
"github.com/gohugoio/hugo/hugolib" "github.com/gohugoio/hugo/hugolib"
) )
@ -384,3 +386,40 @@ Common
} }
} }
// Issue 10835
func TestAttributesValidation(t *testing.T) {
t.Parallel()
files := `
-- hugo.toml --
disableKinds = ["taxonomy", "term"]
-- content/p1.md --
---
title: "p1"
---
## Issue 10835
§§§bash { color=red dimensions=300x200 }
Hello, World!
§§§
-- layouts/index.html --
-- layouts/_default/single.html --
{{ .Content }}
-- layouts/_default/_markup/render-codeblock.html --
Attributes: {{ .Attributes }}|Type: {{ .Type }}|
`
b, err := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
},
).BuildE()
b.Assert(err, qt.Not(qt.IsNil))
b.Assert(err.Error(), qt.Contains, "p1.md:7:9\": failed to parse Markdown attributes; you may need to quote the values")
}

View file

@ -15,6 +15,7 @@ package codeblocks
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"strings" "strings"
"sync" "sync"
@ -101,7 +102,10 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
} }
// IsDefaultCodeBlockRendererProvider // IsDefaultCodeBlockRendererProvider
attrs := getAttributes(n.b, info) attrs, attrStr, err := getAttributes(n.b, info)
if err != nil {
return ast.WalkStop, &herrors.TextSegmentError{Err: err, Segment: attrStr}
}
cbctx := &codeBlockContext{ cbctx := &codeBlockContext{
page: ctx.DocumentContext().Document, page: ctx.DocumentContext().Document,
lang: lang, lang: lang,
@ -123,7 +127,7 @@ func (r *htmlRenderer) renderCodeBlock(w util.BufWriter, src []byte, node ast.No
cr := renderer.(hooks.CodeBlockRenderer) cr := renderer.(hooks.CodeBlockRenderer)
err := cr.RenderCodeblock( err = cr.RenderCodeblock(
ctx.RenderContext().Ctx, ctx.RenderContext().Ctx,
w, w,
cbctx, cbctx,
@ -182,30 +186,36 @@ func getLang(node *ast.FencedCodeBlock, src []byte) string {
return lang return lang
} }
func getAttributes(node *ast.FencedCodeBlock, infostr []byte) []ast.Attribute { func getAttributes(node *ast.FencedCodeBlock, infostr []byte) ([]ast.Attribute, string, error) {
if node.Attributes() != nil { if node.Attributes() != nil {
return node.Attributes() return node.Attributes(), "", nil
} }
if infostr != nil { if infostr != nil {
attrStartIdx := -1 attrStartIdx := -1
attrEndIdx := -1
for idx, char := range infostr { for idx, char := range infostr {
if char == '{' { if attrEndIdx == -1 && char == '{' {
attrStartIdx = idx attrStartIdx = idx
}
if attrStartIdx != -1 && char == '}' {
attrEndIdx = idx
break break
} }
} }
if attrStartIdx != -1 { if attrStartIdx != -1 && attrEndIdx != -1 {
n := ast.NewTextBlock() // dummy node for storing attributes n := ast.NewTextBlock() // dummy node for storing attributes
attrStr := infostr[attrStartIdx:] attrStr := infostr[attrStartIdx : attrEndIdx+1]
if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr { if attrs, hasAttr := parser.ParseAttributes(text.NewReader(attrStr)); hasAttr {
for _, attr := range attrs { for _, attr := range attrs {
n.SetAttribute(attr.Name, attr.Value) n.SetAttribute(attr.Name, attr.Value)
} }
return n.Attributes() return n.Attributes(), "", nil
} else {
return nil, string(attrStr), errors.New("failed to parse Markdown attributes; you may need to quote the values")
} }
} }
} }
return nil return nil, "", nil
} }