diff --git a/README.md b/README.md
index 9af7ee2..600b0f7 100644
--- a/README.md
+++ b/README.md
@@ -50,8 +50,8 @@ md := goldmark.NewMarkdown(
parser.WithHeadingID(),
),
goldmark.WithRendererOptions(
- html.WithSoftLineBreak(true),
- html.WithXHTML(true),
+ html.WithHardWraps(),
+ html.WithXHTML(),
),
)
var buf bytes.Buffer
@@ -78,8 +78,9 @@ Parser and Renderer options
| Functional option | Type | Description |
| ----------------- | ---- | ----------- |
| `html.WithWriter` | `html.Writer` | `html.Writer` for writing contents to an `io.Writer`. |
-| `html.WithSoftLineBreak` | `-` | Render new lines as `
` if true, otherwise `\n` .|
+| `html.WithHardWraps` | `-` | Render new lines as `
`.|
| `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
@@ -104,6 +105,12 @@ Summary:
3. Write a renderer that implements `renderer.NodeRenderer`.
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
--------------------
You can run this benchmark in the `_benchmark` directory.
diff --git a/commonmark_test.go b/commonmark_test.go
index 0318cc1..d975900 100644
--- a/commonmark_test.go
+++ b/commonmark_test.go
@@ -27,7 +27,10 @@ func TestSpec(t *testing.T) {
if err := json.Unmarshal(bs, &testCases); err != nil {
panic(err)
}
- markdown := New(WithRendererOptions(html.WithXHTML()))
+ markdown := New(WithRendererOptions(
+ html.WithXHTML(),
+ html.WithUnsafe(),
+ ))
for _, testCase := range testCases {
var out bytes.Buffer
if err := markdown.Convert([]byte(testCase.Markdown), &out); err != nil {
diff --git a/renderer/html/html.go b/renderer/html/html.go
index 3fa5470..903f591 100644
--- a/renderer/html/html.go
+++ b/renderer/html/html.go
@@ -12,27 +12,31 @@ import (
// A Config struct has configurations for the HTML based renderers.
type Config struct {
- Writer Writer
- SoftLineBreak bool
- XHTML bool
+ Writer Writer
+ HardWraps bool
+ XHTML bool
+ Unsafe bool
}
// NewConfig returns a new Config with defaults.
func NewConfig() Config {
return Config{
- Writer: DefaultWriter,
- SoftLineBreak: false,
- XHTML: false,
+ Writer: DefaultWriter,
+ HardWraps: false,
+ XHTML: false,
+ Unsafe: false,
}
}
// SetOption implements renderer.NodeRenderer.SetOption.
func (c *Config) SetOption(name renderer.OptionName, value interface{}) {
switch name {
- case SoftLineBreak:
- c.SoftLineBreak = value.(bool)
+ case HardWraps:
+ c.HardWraps = value.(bool)
case XHTML:
c.XHTML = value.(bool)
+ case Unsafe:
+ c.Unsafe = value.(bool)
case TextWriter:
c.Writer = value.(Writer)
}
@@ -67,27 +71,27 @@ func WithWriter(writer Writer) interface {
return &withWriter{writer}
}
-// SoftLineBreak is an option name used in WithSoftLineBreak.
-const SoftLineBreak renderer.OptionName = "SoftLineBreak"
+// HardWraps is an option name used in WithHardWraps.
+const HardWraps renderer.OptionName = "HardWraps"
-type withSoftLineBreak struct {
+type withHardWraps struct {
}
-func (o *withSoftLineBreak) SetConfig(c *renderer.Config) {
- c.Options[SoftLineBreak] = true
+func (o *withHardWraps) SetConfig(c *renderer.Config) {
+ c.Options[HardWraps] = true
}
-func (o *withSoftLineBreak) SetHTMLOption(c *Config) {
- c.SoftLineBreak = true
+func (o *withHardWraps) SetHTMLOption(c *Config) {
+ 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 '
'.
-func WithSoftLineBreak() interface {
+func WithHardWraps() interface {
renderer.Option
Option
} {
- return &withSoftLineBreak{}
+ return &withHardWraps{}
}
// XHTML is an option name used in WithXHTML.
@@ -113,6 +117,29 @@ func WithXHTML() interface {
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
// nodes as (X)HTML.
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 {
if entering {
- l := n.Lines().Len()
- for i := 0; i < l; i++ {
- line := n.Lines().At(i)
- w.Write(line.Value(source))
+ if r.Unsafe {
+ l := n.Lines().Len()
+ for i := 0; i < l; i++ {
+ line := n.Lines().At(i)
+ w.Write(line.Value(source))
+ }
+ } else {
+ w.WriteString("\n")
}
} else {
if n.HasClosure() {
- closure := n.ClosureLine
- w.Write(closure.Value(source))
+ if r.Unsafe {
+ closure := n.ClosureLine
+ w.Write(closure.Value(source))
+ } else {
+ w.WriteString("\n")
+ }
}
}
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 {
if entering {
w.WriteString("")
+ return ast.WalkSkipChildren
}
func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, entering bool) ast.WalkStatus {
@@ -442,7 +485,7 @@ func (r *Renderer) renderText(w util.BufWriter, source []byte, n *ast.Text, ente
w.Write(segment.Value(source))
} else {
r.Writer.Write(w, segment.Value(source))
- if n.HardLineBreak() || (n.SoftLineBreak() && r.SoftLineBreak) {
+ if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) {
if r.XHTML {
w.WriteString("
\n")
} else {
@@ -586,3 +629,28 @@ func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
// DefaultWriter is a default implementation of the Writer.
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)
+}