mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Fix bugs found in fuzzing
This commit is contained in:
parent
36e42c4e73
commit
883918a85c
19 changed files with 190 additions and 83 deletions
6
.gitignore
vendored
6
.gitignore
vendored
|
|
@ -11,3 +11,9 @@
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
fuzz/corpus
|
||||||
|
fuzz/crashers
|
||||||
|
fuzz/suppressions
|
||||||
|
fuzz/fuzz-fuzz.zip
|
||||||
|
|
|
||||||
11
Makefile
11
Makefile
|
|
@ -1,7 +1,16 @@
|
||||||
.PHONY: test
|
.PHONY: test fuzz
|
||||||
|
|
||||||
test:
|
test:
|
||||||
go test -coverprofile=profile.out -coverpkg=github.com/yuin/goldmark,github.com/yuin/goldmark/ast,github.com/yuin/goldmark/extension,github.com/yuin/goldmark/extension/ast,github.com/yuin/goldmark/parser,github.com/yuin/goldmark/renderer,github.com/yuin/goldmark/renderer/html,github.com/yuin/goldmark/text,github.com/yuin/goldmark/util ./...
|
go test -coverprofile=profile.out -coverpkg=github.com/yuin/goldmark,github.com/yuin/goldmark/ast,github.com/yuin/goldmark/extension,github.com/yuin/goldmark/extension/ast,github.com/yuin/goldmark/parser,github.com/yuin/goldmark/renderer,github.com/yuin/goldmark/renderer/html,github.com/yuin/goldmark/text,github.com/yuin/goldmark/util ./...
|
||||||
|
|
||||||
cov: test
|
cov: test
|
||||||
go tool cover -html=profile.out
|
go tool cover -html=profile.out
|
||||||
|
|
||||||
|
fuzz:
|
||||||
|
which go-fuzz 2>&1 > /dev/null || (GO111MODULE=off go get -u github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build; GO111MODULE=off go get -d github.com/dvyukov/go-fuzz-corpus; true)
|
||||||
|
cd ./fuzz && go-fuzz-build
|
||||||
|
rm -rf ./fuzz/corpus
|
||||||
|
rm -rf ./fuzz/crashers
|
||||||
|
rm -rf ./fuzz/suppressions
|
||||||
|
rm -f ./fuzz/fuzz-fuzz.zip
|
||||||
|
cd ./fuzz && go-fuzz
|
||||||
|
|
|
||||||
|
|
@ -348,7 +348,6 @@ func (n *BaseNode) SetAttribute(name, value []byte) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
n.attributes = append(n.attributes, Attribute{name, value})
|
n.attributes = append(n.attributes, Attribute{name, value})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attribute implements Node.Attribute.
|
// Attribute implements Node.Attribute.
|
||||||
|
|
@ -396,10 +395,8 @@ func DumpHelper(v Node, source []byte, level int, kv map[string]string, cb func(
|
||||||
fmt.Printf("\"\n")
|
fmt.Printf("\"\n")
|
||||||
fmt.Printf("%sHasBlankPreviousLines: %v\n", indent2, v.HasBlankPreviousLines())
|
fmt.Printf("%sHasBlankPreviousLines: %v\n", indent2, v.HasBlankPreviousLines())
|
||||||
}
|
}
|
||||||
if kv != nil {
|
for name, value := range kv {
|
||||||
for name, value := range kv {
|
fmt.Printf("%s%s: %s\n", indent2, name, value)
|
||||||
fmt.Printf("%s%s: %s\n", indent2, name, value)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if cb != nil {
|
if cb != nil {
|
||||||
cb(level + 1)
|
cb(level + 1)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ func (b *definitionListParser) Open(parent gast.Node, reader text.Reader, pc par
|
||||||
}
|
}
|
||||||
line, _ := reader.PeekLine()
|
line, _ := reader.PeekLine()
|
||||||
pos := pc.BlockOffset()
|
pos := pc.BlockOffset()
|
||||||
if line[pos] != ':' {
|
if pos < 0 || line[pos] != ':' {
|
||||||
return nil, parser.NoChildren
|
return nil, parser.NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -105,10 +105,13 @@ func NewDefinitionDescriptionParser() parser.BlockParser {
|
||||||
func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
func (b *definitionDescriptionParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
||||||
line, _ := reader.PeekLine()
|
line, _ := reader.PeekLine()
|
||||||
pos := pc.BlockOffset()
|
pos := pc.BlockOffset()
|
||||||
if line[pos] != ':' {
|
if pos < 0 || line[pos] != ':' {
|
||||||
return nil, parser.NoChildren
|
return nil, parser.NoChildren
|
||||||
}
|
}
|
||||||
list, _ := parent.(*ast.DefinitionList)
|
list, _ := parent.(*ast.DefinitionList)
|
||||||
|
if list == nil {
|
||||||
|
return nil, parser.NoChildren
|
||||||
|
}
|
||||||
para := list.TemporaryParagraph
|
para := list.TemporaryParagraph
|
||||||
list.TemporaryParagraph = nil
|
list.TemporaryParagraph = nil
|
||||||
if para != nil {
|
if para != nil {
|
||||||
|
|
@ -183,18 +186,18 @@ func (r *DefinitionListHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFunc
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionList(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<dl>\n")
|
_, _ = w.WriteString("<dl>\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</dl>\n")
|
_, _ = w.WriteString("</dl>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *DefinitionListHTMLRenderer) renderDefinitionTerm(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<dt>")
|
_, _ = w.WriteString("<dt>")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</dt>\n")
|
_, _ = w.WriteString("</dt>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
@ -203,12 +206,12 @@ func (r *DefinitionListHTMLRenderer) renderDefinitionDescription(w util.BufWrite
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.DefinitionDescription)
|
n := node.(*ast.DefinitionDescription)
|
||||||
if n.IsTight {
|
if n.IsTight {
|
||||||
w.WriteString("<dd>")
|
_, _ = w.WriteString("<dd>")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("<dd>\n")
|
_, _ = w.WriteString("<dd>\n")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</dd>\n")
|
_, _ = w.WriteString("</dd>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ func NewFootnoteBlockParser() parser.BlockParser {
|
||||||
func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc parser.Context) (gast.Node, parser.State) {
|
||||||
line, segment := reader.PeekLine()
|
line, segment := reader.PeekLine()
|
||||||
pos := pc.BlockOffset()
|
pos := pc.BlockOffset()
|
||||||
if line[pos] != '[' {
|
if pos < 0 || line[pos] != '[' {
|
||||||
return nil, parser.NoChildren
|
return nil, parser.NoChildren
|
||||||
}
|
}
|
||||||
pos++
|
pos++
|
||||||
|
|
@ -37,7 +37,7 @@ func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc pars
|
||||||
return nil, parser.NoChildren
|
return nil, parser.NoChildren
|
||||||
}
|
}
|
||||||
open := pos + 1
|
open := pos + 1
|
||||||
closes := -1
|
closes := 0
|
||||||
closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
|
closure := util.FindClosure(line[pos+1:], '[', ']', false, false)
|
||||||
if closure > -1 {
|
if closure > -1 {
|
||||||
closes = pos + 1 + closure
|
closes = pos + 1 + closure
|
||||||
|
|
@ -52,10 +52,15 @@ func (b *footnoteBlockParser) Open(parent gast.Node, reader text.Reader, pc pars
|
||||||
if util.IsBlank(label) {
|
if util.IsBlank(label) {
|
||||||
return nil, parser.NoChildren
|
return nil, parser.NoChildren
|
||||||
}
|
}
|
||||||
|
item := ast.NewFootnote(label)
|
||||||
|
|
||||||
pos = pos + 2 + closes - open + 2
|
pos = pos + 2 + closes - open + 2
|
||||||
|
if pos >= len(line) {
|
||||||
|
reader.Advance(pos)
|
||||||
|
return item, parser.NoChildren
|
||||||
|
}
|
||||||
childpos, padding := util.IndentPosition(line[pos:], pos, 1)
|
childpos, padding := util.IndentPosition(line[pos:], pos, 1)
|
||||||
reader.AdvanceAndSetPadding(pos+childpos, padding)
|
reader.AdvanceAndSetPadding(pos+childpos, padding)
|
||||||
item := ast.NewFootnote(label)
|
|
||||||
return item, parser.HasChildren
|
return item, parser.HasChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -207,13 +212,13 @@ func (r *FootnoteHTMLRenderer) renderFootnoteLink(w util.BufWriter, source []byt
|
||||||
if entering {
|
if entering {
|
||||||
n := node.(*ast.FootnoteLink)
|
n := node.(*ast.FootnoteLink)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
w.WriteString(`<sup id="fnref:`)
|
_, _ = w.WriteString(`<sup id="fnref:`)
|
||||||
w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
w.WriteString(`"><a href="#fn:`)
|
_, _ = w.WriteString(`"><a href="#fn:`)
|
||||||
w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
|
_, _ = w.WriteString(`" class="footnote-ref" role="doc-noteref">`)
|
||||||
w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
w.WriteString(`</a></sup>`)
|
_, _ = w.WriteString(`</a></sup>`)
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
@ -222,12 +227,12 @@ func (r *FootnoteHTMLRenderer) renderFootnote(w util.BufWriter, source []byte, n
|
||||||
n := node.(*ast.Footnote)
|
n := node.(*ast.Footnote)
|
||||||
is := strconv.Itoa(n.Index)
|
is := strconv.Itoa(n.Index)
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString(`<li id="fn:`)
|
_, _ = w.WriteString(`<li id="fn:`)
|
||||||
w.WriteString(is)
|
_, _ = w.WriteString(is)
|
||||||
w.WriteString(`" role="doc-endnote">`)
|
_, _ = w.WriteString(`" role="doc-endnote">`)
|
||||||
w.WriteString("\n")
|
_, _ = w.WriteString("\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</li>\n")
|
_, _ = w.WriteString("</li>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
@ -238,20 +243,20 @@ func (r *FootnoteHTMLRenderer) renderFootnoteList(w util.BufWriter, source []byt
|
||||||
tag = "div"
|
tag = "div"
|
||||||
}
|
}
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<")
|
_, _ = w.WriteString("<")
|
||||||
w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
w.WriteString(` class="footnotes" role="doc-endnotes">`)
|
_, _ = w.WriteString(` class="footnotes" role="doc-endnotes">`)
|
||||||
if r.Config.XHTML {
|
if r.Config.XHTML {
|
||||||
w.WriteString("\n<hr />\n")
|
_, _ = w.WriteString("\n<hr />\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("\n<hr>\n")
|
_, _ = w.WriteString("\n<hr>\n")
|
||||||
}
|
}
|
||||||
w.WriteString("<ol>\n")
|
_, _ = w.WriteString("<ol>\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</ol>\n")
|
_, _ = w.WriteString("</ol>\n")
|
||||||
w.WriteString("<")
|
_, _ = w.WriteString("<")
|
||||||
w.WriteString(tag)
|
_, _ = w.WriteString(tag)
|
||||||
w.WriteString(">\n")
|
_, _ = w.WriteString(">\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -169,22 +169,22 @@ func (r *TableHTMLRenderer) RegisterFuncs(reg renderer.NodeRendererFuncRegistere
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTable(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<table>\n")
|
_, _ = w.WriteString("<table>\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</table>\n")
|
_, _ = w.WriteString("</table>\n")
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<thead>\n")
|
_, _ = w.WriteString("<thead>\n")
|
||||||
w.WriteString("<tr>\n")
|
_, _ = w.WriteString("<tr>\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</tr>\n")
|
_, _ = w.WriteString("</tr>\n")
|
||||||
w.WriteString("</thead>\n")
|
_, _ = w.WriteString("</thead>\n")
|
||||||
if n.NextSibling() != nil {
|
if n.NextSibling() != nil {
|
||||||
w.WriteString("<tbody>\n")
|
_, _ = w.WriteString("<tbody>\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
|
|
@ -192,11 +192,11 @@ func (r *TableHTMLRenderer) renderTableHeader(w util.BufWriter, source []byte, n
|
||||||
|
|
||||||
func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
func (r *TableHTMLRenderer) renderTableRow(w util.BufWriter, source []byte, n gast.Node, entering bool) (gast.WalkStatus, error) {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<tr>\n")
|
_, _ = w.WriteString("<tr>\n")
|
||||||
} else {
|
} else {
|
||||||
w.WriteString("</tr>\n")
|
_, _ = w.WriteString("</tr>\n")
|
||||||
if n.Parent().LastChild() == n {
|
if n.Parent().LastChild() == n {
|
||||||
w.WriteString("</tbody>\n")
|
_, _ = w.WriteString("</tbody>\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gast.WalkContinue, nil
|
return gast.WalkContinue, nil
|
||||||
|
|
|
||||||
28
fuzz/fuzz.go
Normal file
28
fuzz/fuzz.go
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
package fuzz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Fuzz(data []byte) int {
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.DefinitionList,
|
||||||
|
extension.Footnote,
|
||||||
|
extension.GFM,
|
||||||
|
extension.Typographer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := markdown.Convert(data, &b); err != nil {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1
|
||||||
|
}
|
||||||
41
fuzz/fuzz_test.go
Normal file
41
fuzz/fuzz_test.go
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
package fuzz
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/yuin/goldmark"
|
||||||
|
"github.com/yuin/goldmark/extension"
|
||||||
|
"github.com/yuin/goldmark/renderer/html"
|
||||||
|
"github.com/yuin/goldmark/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ = fmt.Printf
|
||||||
|
|
||||||
|
func TestFuzz(t *testing.T) {
|
||||||
|
crasher := "6dff3d03167cb144d4e2891edac76ee740a77bc7"
|
||||||
|
data, err := ioutil.ReadFile("crashers/" + crasher)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Printf("%s\n", util.VisualizeSpaces(data))
|
||||||
|
fmt.Println("||||||||||||||||||||||")
|
||||||
|
markdown := goldmark.New(
|
||||||
|
goldmark.WithRendererOptions(
|
||||||
|
html.WithUnsafe(),
|
||||||
|
),
|
||||||
|
goldmark.WithExtensions(
|
||||||
|
extension.DefinitionList,
|
||||||
|
extension.Footnote,
|
||||||
|
extension.GFM,
|
||||||
|
extension.Typographer,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
var b bytes.Buffer
|
||||||
|
if err := markdown.Convert(data, &b); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(b.String())
|
||||||
|
}
|
||||||
|
|
@ -77,6 +77,9 @@ func NewATXHeadingParser(opts ...HeadingOption) BlockParser {
|
||||||
func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
|
func (b *atxHeadingParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
|
||||||
line, segment := reader.PeekLine()
|
line, segment := reader.PeekLine()
|
||||||
pos := pc.BlockOffset()
|
pos := pc.BlockOffset()
|
||||||
|
if pos < 0 {
|
||||||
|
return nil, NoChildren
|
||||||
|
}
|
||||||
i := pos
|
i := pos
|
||||||
for ; i < len(line) && line[i] == '#'; i++ {
|
for ; i < len(line) && line[i] == '#'; i++ {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,7 +55,7 @@ func (b *codeBlockParser) Close(node ast.Node, reader text.Reader, pc Context) {
|
||||||
lines := node.Lines()
|
lines := node.Lines()
|
||||||
length := lines.Len() - 1
|
length := lines.Len() - 1
|
||||||
source := reader.Source()
|
source := reader.Source()
|
||||||
for {
|
for length >= 0 {
|
||||||
line := lines.At(length)
|
line := lines.At(length)
|
||||||
if util.IsBlank(line.Value(source)) {
|
if util.IsBlank(line.Value(source)) {
|
||||||
length--
|
length--
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ var fencedCodeBlockInfoKey = NewContextKey()
|
||||||
func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
|
func (b *fencedCodeBlockParser) Open(parent ast.Node, reader text.Reader, pc Context) (ast.Node, State) {
|
||||||
line, segment := reader.PeekLine()
|
line, segment := reader.PeekLine()
|
||||||
pos := pc.BlockOffset()
|
pos := pc.BlockOffset()
|
||||||
if line[pos] != '`' && line[pos] != '~' {
|
if pos < 0 || (line[pos] != '`' && line[pos] != '~') {
|
||||||
return nil, NoChildren
|
return nil, NoChildren
|
||||||
}
|
}
|
||||||
findent := pos
|
findent := pos
|
||||||
|
|
|
||||||
|
|
@ -109,7 +109,7 @@ func (b *htmlBlockParser) Open(parent ast.Node, reader text.Reader, pc Context)
|
||||||
var node *ast.HTMLBlock
|
var node *ast.HTMLBlock
|
||||||
line, segment := reader.PeekLine()
|
line, segment := reader.PeekLine()
|
||||||
last := pc.LastOpenedBlock().Node
|
last := pc.LastOpenedBlock().Node
|
||||||
if pos := pc.BlockOffset(); line[pos] != '<' {
|
if pos := pc.BlockOffset(); pos < 0 || line[pos] != '<' {
|
||||||
return nil, NoChildren
|
return nil, NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func parseListItem(line []byte) ([6]int, listItemType) {
|
||||||
ret[1] = i
|
ret[1] = i
|
||||||
ret[2] = i
|
ret[2] = i
|
||||||
var typ listItemType
|
var typ listItemType
|
||||||
if i < l && line[i] == '-' || line[i] == '*' || line[i] == '+' {
|
if i < l && (line[i] == '-' || line[i] == '*' || line[i] == '+') {
|
||||||
i++
|
i++
|
||||||
ret[3] = i
|
ret[3] = i
|
||||||
typ = bulletList
|
typ = bulletList
|
||||||
|
|
@ -46,7 +46,7 @@ func parseListItem(line []byte) ([6]int, listItemType) {
|
||||||
if ret[3] == ret[2] || ret[3]-ret[2] > 9 {
|
if ret[3] == ret[2] || ret[3]-ret[2] > 9 {
|
||||||
return ret, notList
|
return ret, notList
|
||||||
}
|
}
|
||||||
if i < l && line[i] == '.' || line[i] == ')' {
|
if i < l && (line[i] == '.' || line[i] == ')') {
|
||||||
i++
|
i++
|
||||||
ret[3] = i
|
ret[3] = i
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -56,12 +56,17 @@ func parseListItem(line []byte) ([6]int, listItemType) {
|
||||||
} else {
|
} else {
|
||||||
return ret, notList
|
return ret, notList
|
||||||
}
|
}
|
||||||
if line[i] != '\n' {
|
if i < l && line[i] != '\n' {
|
||||||
w, _ := util.IndentWidth(line[i:], 0)
|
w, _ := util.IndentWidth(line[i:], 0)
|
||||||
if w == 0 {
|
if w == 0 {
|
||||||
return ret, notList
|
return ret, notList
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if i >= l {
|
||||||
|
ret[4] = -1
|
||||||
|
ret[5] = -1
|
||||||
|
return ret, typ
|
||||||
|
}
|
||||||
ret[4] = i
|
ret[4] = i
|
||||||
ret[5] = len(line)
|
ret[5] = len(line)
|
||||||
if line[ret[5]-1] == '\n' && line[i] != '\n' {
|
if line[ret[5]-1] == '\n' && line[i] != '\n' {
|
||||||
|
|
@ -80,7 +85,7 @@ func matchesListItem(source []byte, strict bool) ([6]int, listItemType) {
|
||||||
|
|
||||||
func calcListOffset(source []byte, match [6]int) int {
|
func calcListOffset(source []byte, match [6]int) int {
|
||||||
offset := 0
|
offset := 0
|
||||||
if util.IsBlank(source[match[4]:]) { // list item starts with a blank line
|
if match[4] < 0 || util.IsBlank(source[match[4]:]) { // list item starts with a blank line
|
||||||
offset = 1
|
offset = 1
|
||||||
} else {
|
} else {
|
||||||
offset, _ = util.IndentWidth(source[match[4]:], match[4])
|
offset, _ = util.IndentWidth(source[match[4]:], match[4])
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ func (b *listItemParser) Open(parent ast.Node, reader text.Reader, pc Context) (
|
||||||
}
|
}
|
||||||
itemOffset := calcListOffset(line, match)
|
itemOffset := calcListOffset(line, match)
|
||||||
node := ast.NewListItem(match[3] + itemOffset)
|
node := ast.NewListItem(match[3] + itemOffset)
|
||||||
if match[5]-match[4] == 1 {
|
if match[4] < 0 || match[5]-match[4] == 1 {
|
||||||
return node, NoChildren
|
return node, NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ type Context interface {
|
||||||
|
|
||||||
// BlockOffset returns a first non-space character position on current line.
|
// BlockOffset returns a first non-space character position on current line.
|
||||||
// This value is valid only for BlockParser.Open.
|
// This value is valid only for BlockParser.Open.
|
||||||
|
// BlockOffset returns -1 if current line is blank.
|
||||||
BlockOffset() int
|
BlockOffset() int
|
||||||
|
|
||||||
// BlockOffset sets a first non-space character position on current line.
|
// BlockOffset sets a first non-space character position on current line.
|
||||||
|
|
@ -833,7 +834,11 @@ retry:
|
||||||
//currentLineNum, _ = reader.Position()
|
//currentLineNum, _ = reader.Position()
|
||||||
line, _ = reader.PeekLine()
|
line, _ = reader.PeekLine()
|
||||||
w, pos = util.IndentWidth(line, 0)
|
w, pos = util.IndentWidth(line, 0)
|
||||||
pc.SetBlockOffset(pos)
|
if w >= len(line) {
|
||||||
|
pc.SetBlockOffset(-1)
|
||||||
|
} else {
|
||||||
|
pc.SetBlockOffset(pos)
|
||||||
|
}
|
||||||
shouldPeek = false
|
shouldPeek = false
|
||||||
if line == nil || line[0] == '\n' {
|
if line == nil || line[0] == '\n' {
|
||||||
break
|
break
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ func (b *setextHeadingParser) Open(parent ast.Node, reader text.Reader, pc Conte
|
||||||
}
|
}
|
||||||
node := ast.NewHeading(level)
|
node := ast.NewHeading(level)
|
||||||
node.Lines().Append(segment)
|
node.Lines().Append(segment)
|
||||||
pc.Set(temporaryParagraphKey, paragraph)
|
pc.Set(temporaryParagraphKey, last)
|
||||||
return node, NoChildren
|
return node, NoChildren
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -577,28 +577,30 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
||||||
next := i + 1
|
next := i + 1
|
||||||
if next < limit && source[next] == '#' {
|
if next < limit && source[next] == '#' {
|
||||||
nnext := next + 1
|
nnext := next + 1
|
||||||
nc := source[nnext]
|
if nnext < limit {
|
||||||
// code point like #x22;
|
nc := source[nnext]
|
||||||
if nnext < limit && nc == 'x' || nc == 'X' {
|
// code point like #x22;
|
||||||
start := nnext + 1
|
if nnext < limit && nc == 'x' || nc == 'X' {
|
||||||
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal)
|
start := nnext + 1
|
||||||
if ok && i < limit && source[i] == ';' {
|
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal)
|
||||||
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
|
if ok && i < limit && source[i] == ';' {
|
||||||
d.RawWrite(writer, source[n:pos])
|
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
|
||||||
n = i + 1
|
d.RawWrite(writer, source[n:pos])
|
||||||
escapeRune(writer, rune(v))
|
n = i + 1
|
||||||
continue
|
escapeRune(writer, rune(v))
|
||||||
}
|
continue
|
||||||
// code point like #1234;
|
}
|
||||||
} else if nc >= '0' && nc <= '9' {
|
// code point like #1234;
|
||||||
start := nnext
|
} else if nc >= '0' && nc <= '9' {
|
||||||
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric)
|
start := nnext
|
||||||
if ok && i < limit && i-start < 8 && source[i] == ';' {
|
i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric)
|
||||||
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 0, 32)
|
if ok && i < limit && i-start < 8 && source[i] == ';' {
|
||||||
d.RawWrite(writer, source[n:pos])
|
v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 0, 32)
|
||||||
n = i + 1
|
d.RawWrite(writer, source[n:pos])
|
||||||
escapeRune(writer, rune(v))
|
n = i + 1
|
||||||
continue
|
escapeRune(writer, rune(v))
|
||||||
|
continue
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -326,6 +326,9 @@ func (r *blockReader) PrecendingCharacter() rune {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if i < 0 {
|
||||||
|
return rune('\n')
|
||||||
|
}
|
||||||
rn, _ := utf8.DecodeRune(r.source[i:])
|
rn, _ := utf8.DecodeRune(r.source[i:])
|
||||||
return rn
|
return rn
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -567,7 +567,7 @@ func URLEscape(v []byte, resolveReference bool) []byte {
|
||||||
i += int(u8len)
|
i += int(u8len)
|
||||||
n = i
|
n = i
|
||||||
}
|
}
|
||||||
if cob.IsCopied() {
|
if cob.IsCopied() && n < limit {
|
||||||
cob.Write(v[n:])
|
cob.Write(v[n:])
|
||||||
}
|
}
|
||||||
return cob.Bytes()
|
return cob.Bytes()
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue