mirror of
https://github.com/yuin/goldmark
synced 2025-03-04 23:04:52 +00:00
Add WithUnsafe option
This commit is contained in:
parent
0cca3c1ea1
commit
77aabc8f8e
3 changed files with 110 additions and 32 deletions
13
README.md
13
README.md
|
|
@ -50,8 +50,8 @@ md := goldmark.NewMarkdown(
|
||||||
parser.WithHeadingID(),
|
parser.WithHeadingID(),
|
||||||
),
|
),
|
||||||
goldmark.WithRendererOptions(
|
goldmark.WithRendererOptions(
|
||||||
html.WithSoftLineBreak(true),
|
html.WithHardWraps(),
|
||||||
html.WithXHTML(true),
|
html.WithXHTML(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
@ -78,8 +78,9 @@ Parser and Renderer options
|
||||||
| Functional option | Type | Description |
|
| Functional option | Type | Description |
|
||||||
| ----------------- | ---- | ----------- |
|
| ----------------- | ---- | ----------- |
|
||||||
| `html.WithWriter` | `html.Writer` | `html.Writer` for writing contents to an `io.Writer`. |
|
| `html.WithWriter` | `html.Writer` | `html.Writer` for writing contents to an `io.Writer`. |
|
||||||
| `html.WithSoftLineBreak` | `-` | Render new lines as `<br>` if true, otherwise `\n` .|
|
| `html.WithHardWraps` | `-` | Render new lines as `<br>`.|
|
||||||
| `html.WithXHTML` | `-` | Render as XHTML. |
|
| `html.WithXHTML` | `-` | Render as XHTML. |
|
||||||
|
| `html.WithUnsafe` | `-` | By default, goldmark does not render raw HTMLs and potentially dangerous links. With this option, goldmark renders these contents as it is. |
|
||||||
|
|
||||||
### Built-in extensions
|
### Built-in extensions
|
||||||
|
|
||||||
|
|
@ -104,6 +105,12 @@ Summary:
|
||||||
3. Write a renderer that implements `renderer.NodeRenderer`.
|
3. Write a renderer that implements `renderer.NodeRenderer`.
|
||||||
4. Define your goldmark extension that implements `goldmark.Extender`.
|
4. Define your goldmark extension that implements `goldmark.Extender`.
|
||||||
|
|
||||||
|
Security
|
||||||
|
--------------------
|
||||||
|
By default, goldmark does not render raw HTMLs and potentially dangerous urls.
|
||||||
|
If you need to gain more control about untrusted contents, it is recommended to
|
||||||
|
use HTML sanitizer such as [bluemonday](https://github.com/microcosm-cc/bluemonday).
|
||||||
|
|
||||||
Benchmark
|
Benchmark
|
||||||
--------------------
|
--------------------
|
||||||
You can run this benchmark in the `_benchmark` directory.
|
You can run this benchmark in the `_benchmark` directory.
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,10 @@ func TestSpec(t *testing.T) {
|
||||||
if err := json.Unmarshal(bs, &testCases); err != nil {
|
if err := json.Unmarshal(bs, &testCases); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
markdown := New(WithRendererOptions(html.WithXHTML()))
|
markdown := New(WithRendererOptions(
|
||||||
|
html.WithXHTML(),
|
||||||
|
html.WithUnsafe(),
|
||||||
|
))
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
var out bytes.Buffer
|
var out bytes.Buffer
|
||||||
if err := markdown.Convert([]byte(testCase.Markdown), &out); err != nil {
|
if err := markdown.Convert([]byte(testCase.Markdown), &out); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -13,26 +13,30 @@ import (
|
||||||
// A Config struct has configurations for the HTML based renderers.
|
// A Config struct has configurations for the HTML based renderers.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
Writer Writer
|
Writer Writer
|
||||||
SoftLineBreak bool
|
HardWraps bool
|
||||||
XHTML bool
|
XHTML bool
|
||||||
|
Unsafe bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfig returns a new Config with defaults.
|
// NewConfig returns a new Config with defaults.
|
||||||
func NewConfig() Config {
|
func NewConfig() Config {
|
||||||
return Config{
|
return Config{
|
||||||
Writer: DefaultWriter,
|
Writer: DefaultWriter,
|
||||||
SoftLineBreak: false,
|
HardWraps: false,
|
||||||
XHTML: false,
|
XHTML: false,
|
||||||
|
Unsafe: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetOption implements renderer.NodeRenderer.SetOption.
|
// SetOption implements renderer.NodeRenderer.SetOption.
|
||||||
func (c *Config) SetOption(name renderer.OptionName, value interface{}) {
|
func (c *Config) SetOption(name renderer.OptionName, value interface{}) {
|
||||||
switch name {
|
switch name {
|
||||||
case SoftLineBreak:
|
case HardWraps:
|
||||||
c.SoftLineBreak = value.(bool)
|
c.HardWraps = value.(bool)
|
||||||
case XHTML:
|
case XHTML:
|
||||||
c.XHTML = value.(bool)
|
c.XHTML = value.(bool)
|
||||||
|
case Unsafe:
|
||||||
|
c.Unsafe = value.(bool)
|
||||||
case TextWriter:
|
case TextWriter:
|
||||||
c.Writer = value.(Writer)
|
c.Writer = value.(Writer)
|
||||||
}
|
}
|
||||||
|
|
@ -67,27 +71,27 @@ func WithWriter(writer Writer) interface {
|
||||||
return &withWriter{writer}
|
return &withWriter{writer}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SoftLineBreak is an option name used in WithSoftLineBreak.
|
// HardWraps is an option name used in WithHardWraps.
|
||||||
const SoftLineBreak renderer.OptionName = "SoftLineBreak"
|
const HardWraps renderer.OptionName = "HardWraps"
|
||||||
|
|
||||||
type withSoftLineBreak struct {
|
type withHardWraps struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *withSoftLineBreak) SetConfig(c *renderer.Config) {
|
func (o *withHardWraps) SetConfig(c *renderer.Config) {
|
||||||
c.Options[SoftLineBreak] = true
|
c.Options[HardWraps] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *withSoftLineBreak) SetHTMLOption(c *Config) {
|
func (o *withHardWraps) SetHTMLOption(c *Config) {
|
||||||
c.SoftLineBreak = true
|
c.HardWraps = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithSoftLineBreak is a functional option that indicates whether softline breaks
|
// WithHardWraps is a functional option that indicates whether softline breaks
|
||||||
// should be rendered as '<br>'.
|
// should be rendered as '<br>'.
|
||||||
func WithSoftLineBreak() interface {
|
func WithHardWraps() interface {
|
||||||
renderer.Option
|
renderer.Option
|
||||||
Option
|
Option
|
||||||
} {
|
} {
|
||||||
return &withSoftLineBreak{}
|
return &withHardWraps{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XHTML is an option name used in WithXHTML.
|
// XHTML is an option name used in WithXHTML.
|
||||||
|
|
@ -113,6 +117,29 @@ func WithXHTML() interface {
|
||||||
return &withXHTML{}
|
return &withXHTML{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unsafe is an option name used in WithUnsafe.
|
||||||
|
const Unsafe renderer.OptionName = "Unsafe"
|
||||||
|
|
||||||
|
type withUnsafe struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withUnsafe) SetConfig(c *renderer.Config) {
|
||||||
|
c.Options[Unsafe] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *withUnsafe) SetHTMLOption(c *Config) {
|
||||||
|
c.Unsafe = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithUnsafe is a functional option that renders dangerous contents
|
||||||
|
// (raw htmls and potentially dangerous links) as it is.
|
||||||
|
func WithUnsafe() interface {
|
||||||
|
renderer.Option
|
||||||
|
Option
|
||||||
|
} {
|
||||||
|
return &withUnsafe{}
|
||||||
|
}
|
||||||
|
|
||||||
// A Renderer struct is an implementation of renderer.NodeRenderer that renders
|
// A Renderer struct is an implementation of renderer.NodeRenderer that renders
|
||||||
// nodes as (X)HTML.
|
// nodes as (X)HTML.
|
||||||
type Renderer struct {
|
type Renderer struct {
|
||||||
|
|
@ -255,15 +282,23 @@ func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, n *ast
|
||||||
|
|
||||||
func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, n *ast.HTMLBlock, entering bool) ast.WalkStatus {
|
func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, n *ast.HTMLBlock, entering bool) ast.WalkStatus {
|
||||||
if entering {
|
if entering {
|
||||||
|
if r.Unsafe {
|
||||||
l := n.Lines().Len()
|
l := n.Lines().Len()
|
||||||
for i := 0; i < l; i++ {
|
for i := 0; i < l; i++ {
|
||||||
line := n.Lines().At(i)
|
line := n.Lines().At(i)
|
||||||
w.Write(line.Value(source))
|
w.Write(line.Value(source))
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
w.WriteString("<!-- raw HTML omitted -->\n")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if n.HasClosure() {
|
if n.HasClosure() {
|
||||||
|
if r.Unsafe {
|
||||||
closure := n.ClosureLine
|
closure := n.ClosureLine
|
||||||
w.Write(closure.Value(source))
|
w.Write(closure.Value(source))
|
||||||
|
} else {
|
||||||
|
w.WriteString("<!-- raw HTML omitted -->\n")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
|
|
@ -394,7 +429,9 @@ func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, n *ast.Emphas
|
||||||
func (r *Renderer) renderLink(w util.BufWriter, source []byte, n *ast.Link, entering bool) ast.WalkStatus {
|
func (r *Renderer) renderLink(w util.BufWriter, source []byte, n *ast.Link, entering bool) ast.WalkStatus {
|
||||||
if entering {
|
if entering {
|
||||||
w.WriteString("<a href=\"")
|
w.WriteString("<a href=\"")
|
||||||
|
if r.Unsafe || !IsDangerousURL(n.Destination) {
|
||||||
w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||||
|
}
|
||||||
w.WriteByte('"')
|
w.WriteByte('"')
|
||||||
if n.Title != nil {
|
if n.Title != nil {
|
||||||
w.WriteString(` title="`)
|
w.WriteString(` title="`)
|
||||||
|
|
@ -412,7 +449,9 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, n *ast.Image, en
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
}
|
}
|
||||||
w.WriteString("<img src=\"")
|
w.WriteString("<img src=\"")
|
||||||
|
if r.Unsafe || !IsDangerousURL(n.Destination) {
|
||||||
w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
|
||||||
|
}
|
||||||
w.WriteString(`" alt="`)
|
w.WriteString(`" alt="`)
|
||||||
w.Write(n.Text(source))
|
w.Write(n.Text(source))
|
||||||
w.WriteByte('"')
|
w.WriteByte('"')
|
||||||
|
|
@ -430,8 +469,12 @@ func (r *Renderer) renderImage(w util.BufWriter, source []byte, n *ast.Image, en
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, n *ast.RawHTML, entering bool) ast.WalkStatus {
|
func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, n *ast.RawHTML, entering bool) ast.WalkStatus {
|
||||||
|
if r.Unsafe {
|
||||||
return ast.WalkContinue
|
return ast.WalkContinue
|
||||||
}
|
}
|
||||||
|
w.WriteString("<!-- raw HTML omitted -->")
|
||||||
|
return ast.WalkSkipChildren
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, entering bool) ast.WalkStatus {
|
func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, entering bool) ast.WalkStatus {
|
||||||
if !entering {
|
if !entering {
|
||||||
|
|
@ -442,7 +485,7 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, ente
|
||||||
w.Write(segment.Value(source))
|
w.Write(segment.Value(source))
|
||||||
} else {
|
} else {
|
||||||
r.Writer.Write(w, segment.Value(source))
|
r.Writer.Write(w, segment.Value(source))
|
||||||
if n.HardLineBreak() || (n.SoftLineBreak() && r.SoftLineBreak) {
|
if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) {
|
||||||
if r.XHTML {
|
if r.XHTML {
|
||||||
w.WriteString("<br />\n")
|
w.WriteString("<br />\n")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -586,3 +629,28 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
|
||||||
|
|
||||||
// DefaultWriter is a default implementation of the Writer.
|
// DefaultWriter is a default implementation of the Writer.
|
||||||
var DefaultWriter = &defaultWriter{}
|
var DefaultWriter = &defaultWriter{}
|
||||||
|
|
||||||
|
var bDataImage = []byte("data:image/")
|
||||||
|
var bPng = []byte("png;")
|
||||||
|
var bGif = []byte("gif;")
|
||||||
|
var bJpeg = []byte("jpeg;")
|
||||||
|
var bWebp = []byte("webp;")
|
||||||
|
var bJs = []byte("javascript:")
|
||||||
|
var bVb = []byte("vbscript:")
|
||||||
|
var bFile = []byte("file:")
|
||||||
|
var bData = []byte("data:")
|
||||||
|
|
||||||
|
// IsDangerousURL returns true if given url seems a potentially dangerous url,
|
||||||
|
// otherwise false.
|
||||||
|
func IsDangerousURL(url []byte) bool {
|
||||||
|
if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
|
||||||
|
v := url[11:]
|
||||||
|
if bytes.HasPrefix(v, bPng) || bytes.HasPrefix(v, bGif) ||
|
||||||
|
bytes.HasPrefix(v, bJpeg) || bytes.HasPrefix(v, bWebp) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return bytes.HasPrefix(url, bJs) || bytes.HasPrefix(url, bVb) ||
|
||||||
|
bytes.HasPrefix(url, bFile) || bytes.HasPrefix(url, bData)
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue