gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

html.go (24702B)


      1 package html
      2 
      3 import (
      4 	"bytes"
      5 	"fmt"
      6 	"strconv"
      7 	"unicode/utf8"
      8 
      9 	"github.com/yuin/goldmark/ast"
     10 	"github.com/yuin/goldmark/renderer"
     11 	"github.com/yuin/goldmark/util"
     12 )
     13 
     14 // A Config struct has configurations for the HTML based renderers.
     15 type Config struct {
     16 	Writer              Writer
     17 	HardWraps           bool
     18 	EastAsianLineBreaks bool
     19 	XHTML               bool
     20 	Unsafe              bool
     21 }
     22 
     23 // NewConfig returns a new Config with defaults.
     24 func NewConfig() Config {
     25 	return Config{
     26 		Writer:              DefaultWriter,
     27 		HardWraps:           false,
     28 		EastAsianLineBreaks: false,
     29 		XHTML:               false,
     30 		Unsafe:              false,
     31 	}
     32 }
     33 
     34 // SetOption implements renderer.NodeRenderer.SetOption.
     35 func (c *Config) SetOption(name renderer.OptionName, value interface{}) {
     36 	switch name {
     37 	case optHardWraps:
     38 		c.HardWraps = value.(bool)
     39 	case optEastAsianLineBreaks:
     40 		c.EastAsianLineBreaks = value.(bool)
     41 	case optXHTML:
     42 		c.XHTML = value.(bool)
     43 	case optUnsafe:
     44 		c.Unsafe = value.(bool)
     45 	case optTextWriter:
     46 		c.Writer = value.(Writer)
     47 	}
     48 }
     49 
     50 // An Option interface sets options for HTML based renderers.
     51 type Option interface {
     52 	SetHTMLOption(*Config)
     53 }
     54 
     55 // TextWriter is an option name used in WithWriter.
     56 const optTextWriter renderer.OptionName = "Writer"
     57 
     58 type withWriter struct {
     59 	value Writer
     60 }
     61 
     62 func (o *withWriter) SetConfig(c *renderer.Config) {
     63 	c.Options[optTextWriter] = o.value
     64 }
     65 
     66 func (o *withWriter) SetHTMLOption(c *Config) {
     67 	c.Writer = o.value
     68 }
     69 
     70 // WithWriter is a functional option that allow you to set the given writer to
     71 // the renderer.
     72 func WithWriter(writer Writer) interface {
     73 	renderer.Option
     74 	Option
     75 } {
     76 	return &withWriter{writer}
     77 }
     78 
     79 // HardWraps is an option name used in WithHardWraps.
     80 const optHardWraps renderer.OptionName = "HardWraps"
     81 
     82 type withHardWraps struct {
     83 }
     84 
     85 func (o *withHardWraps) SetConfig(c *renderer.Config) {
     86 	c.Options[optHardWraps] = true
     87 }
     88 
     89 func (o *withHardWraps) SetHTMLOption(c *Config) {
     90 	c.HardWraps = true
     91 }
     92 
     93 // WithHardWraps is a functional option that indicates whether softline breaks
     94 // should be rendered as '<br>'.
     95 func WithHardWraps() interface {
     96 	renderer.Option
     97 	Option
     98 } {
     99 	return &withHardWraps{}
    100 }
    101 
    102 // EastAsianLineBreaks is an option name used in WithEastAsianLineBreaks.
    103 const optEastAsianLineBreaks renderer.OptionName = "EastAsianLineBreaks"
    104 
    105 type withEastAsianLineBreaks struct {
    106 }
    107 
    108 func (o *withEastAsianLineBreaks) SetConfig(c *renderer.Config) {
    109 	c.Options[optEastAsianLineBreaks] = true
    110 }
    111 
    112 func (o *withEastAsianLineBreaks) SetHTMLOption(c *Config) {
    113 	c.EastAsianLineBreaks = true
    114 }
    115 
    116 // WithEastAsianLineBreaks is a functional option that indicates whether softline breaks
    117 // between east asian wide characters should be ignored.
    118 func WithEastAsianLineBreaks() interface {
    119 	renderer.Option
    120 	Option
    121 } {
    122 	return &withEastAsianLineBreaks{}
    123 }
    124 
    125 // XHTML is an option name used in WithXHTML.
    126 const optXHTML renderer.OptionName = "XHTML"
    127 
    128 type withXHTML struct {
    129 }
    130 
    131 func (o *withXHTML) SetConfig(c *renderer.Config) {
    132 	c.Options[optXHTML] = true
    133 }
    134 
    135 func (o *withXHTML) SetHTMLOption(c *Config) {
    136 	c.XHTML = true
    137 }
    138 
    139 // WithXHTML is a functional option indicates that nodes should be rendered in
    140 // xhtml instead of HTML5.
    141 func WithXHTML() interface {
    142 	Option
    143 	renderer.Option
    144 } {
    145 	return &withXHTML{}
    146 }
    147 
    148 // Unsafe is an option name used in WithUnsafe.
    149 const optUnsafe renderer.OptionName = "Unsafe"
    150 
    151 type withUnsafe struct {
    152 }
    153 
    154 func (o *withUnsafe) SetConfig(c *renderer.Config) {
    155 	c.Options[optUnsafe] = true
    156 }
    157 
    158 func (o *withUnsafe) SetHTMLOption(c *Config) {
    159 	c.Unsafe = true
    160 }
    161 
    162 // WithUnsafe is a functional option that renders dangerous contents
    163 // (raw htmls and potentially dangerous links) as it is.
    164 func WithUnsafe() interface {
    165 	renderer.Option
    166 	Option
    167 } {
    168 	return &withUnsafe{}
    169 }
    170 
    171 // A Renderer struct is an implementation of renderer.NodeRenderer that renders
    172 // nodes as (X)HTML.
    173 type Renderer struct {
    174 	Config
    175 }
    176 
    177 // NewRenderer returns a new Renderer with given options.
    178 func NewRenderer(opts ...Option) renderer.NodeRenderer {
    179 	r := &Renderer{
    180 		Config: NewConfig(),
    181 	}
    182 
    183 	for _, opt := range opts {
    184 		opt.SetHTMLOption(&r.Config)
    185 	}
    186 	return r
    187 }
    188 
    189 // RegisterFuncs implements NodeRenderer.RegisterFuncs .
    190 func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
    191 	// blocks
    192 
    193 	reg.Register(ast.KindDocument, r.renderDocument)
    194 	reg.Register(ast.KindHeading, r.renderHeading)
    195 	reg.Register(ast.KindBlockquote, r.renderBlockquote)
    196 	reg.Register(ast.KindCodeBlock, r.renderCodeBlock)
    197 	reg.Register(ast.KindFencedCodeBlock, r.renderFencedCodeBlock)
    198 	reg.Register(ast.KindHTMLBlock, r.renderHTMLBlock)
    199 	reg.Register(ast.KindList, r.renderList)
    200 	reg.Register(ast.KindListItem, r.renderListItem)
    201 	reg.Register(ast.KindParagraph, r.renderParagraph)
    202 	reg.Register(ast.KindTextBlock, r.renderTextBlock)
    203 	reg.Register(ast.KindThematicBreak, r.renderThematicBreak)
    204 
    205 	// inlines
    206 
    207 	reg.Register(ast.KindAutoLink, r.renderAutoLink)
    208 	reg.Register(ast.KindCodeSpan, r.renderCodeSpan)
    209 	reg.Register(ast.KindEmphasis, r.renderEmphasis)
    210 	reg.Register(ast.KindImage, r.renderImage)
    211 	reg.Register(ast.KindLink, r.renderLink)
    212 	reg.Register(ast.KindRawHTML, r.renderRawHTML)
    213 	reg.Register(ast.KindText, r.renderText)
    214 	reg.Register(ast.KindString, r.renderString)
    215 }
    216 
    217 func (r *Renderer) writeLines(w util.BufWriter, source []byte, n ast.Node) {
    218 	l := n.Lines().Len()
    219 	for i := 0; i < l; i++ {
    220 		line := n.Lines().At(i)
    221 		r.Writer.RawWrite(w, line.Value(source))
    222 	}
    223 }
    224 
    225 // GlobalAttributeFilter defines attribute names which any elements can have.
    226 var GlobalAttributeFilter = util.NewBytesFilter(
    227 	[]byte("accesskey"),
    228 	[]byte("autocapitalize"),
    229 	[]byte("autofocus"),
    230 	[]byte("class"),
    231 	[]byte("contenteditable"),
    232 	[]byte("dir"),
    233 	[]byte("draggable"),
    234 	[]byte("enterkeyhint"),
    235 	[]byte("hidden"),
    236 	[]byte("id"),
    237 	[]byte("inert"),
    238 	[]byte("inputmode"),
    239 	[]byte("is"),
    240 	[]byte("itemid"),
    241 	[]byte("itemprop"),
    242 	[]byte("itemref"),
    243 	[]byte("itemscope"),
    244 	[]byte("itemtype"),
    245 	[]byte("lang"),
    246 	[]byte("part"),
    247 	[]byte("role"),
    248 	[]byte("slot"),
    249 	[]byte("spellcheck"),
    250 	[]byte("style"),
    251 	[]byte("tabindex"),
    252 	[]byte("title"),
    253 	[]byte("translate"),
    254 )
    255 
    256 func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    257 	// nothing to do
    258 	return ast.WalkContinue, nil
    259 }
    260 
    261 // HeadingAttributeFilter defines attribute names which heading elements can have
    262 var HeadingAttributeFilter = GlobalAttributeFilter
    263 
    264 func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    265 	n := node.(*ast.Heading)
    266 	if entering {
    267 		_, _ = w.WriteString("<h")
    268 		_ = w.WriteByte("0123456"[n.Level])
    269 		if n.Attributes() != nil {
    270 			RenderAttributes(w, node, HeadingAttributeFilter)
    271 		}
    272 		_ = w.WriteByte('>')
    273 	} else {
    274 		_, _ = w.WriteString("</h")
    275 		_ = w.WriteByte("0123456"[n.Level])
    276 		_, _ = w.WriteString(">\n")
    277 	}
    278 	return ast.WalkContinue, nil
    279 }
    280 
    281 // BlockquoteAttributeFilter defines attribute names which blockquote elements can have
    282 var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend(
    283 	[]byte("cite"),
    284 )
    285 
    286 func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    287 	if entering {
    288 		if n.Attributes() != nil {
    289 			_, _ = w.WriteString("<blockquote")
    290 			RenderAttributes(w, n, BlockquoteAttributeFilter)
    291 			_ = w.WriteByte('>')
    292 		} else {
    293 			_, _ = w.WriteString("<blockquote>\n")
    294 		}
    295 	} else {
    296 		_, _ = w.WriteString("</blockquote>\n")
    297 	}
    298 	return ast.WalkContinue, nil
    299 }
    300 
    301 func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    302 	if entering {
    303 		_, _ = w.WriteString("<pre><code>")
    304 		r.writeLines(w, source, n)
    305 	} else {
    306 		_, _ = w.WriteString("</code></pre>\n")
    307 	}
    308 	return ast.WalkContinue, nil
    309 }
    310 
    311 func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    312 	n := node.(*ast.FencedCodeBlock)
    313 	if entering {
    314 		_, _ = w.WriteString("<pre><code")
    315 		language := n.Language(source)
    316 		if language != nil {
    317 			_, _ = w.WriteString(" class=\"language-")
    318 			r.Writer.Write(w, language)
    319 			_, _ = w.WriteString("\"")
    320 		}
    321 		_ = w.WriteByte('>')
    322 		r.writeLines(w, source, n)
    323 	} else {
    324 		_, _ = w.WriteString("</code></pre>\n")
    325 	}
    326 	return ast.WalkContinue, nil
    327 }
    328 
    329 func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    330 	n := node.(*ast.HTMLBlock)
    331 	if entering {
    332 		if r.Unsafe {
    333 			l := n.Lines().Len()
    334 			for i := 0; i < l; i++ {
    335 				line := n.Lines().At(i)
    336 				r.Writer.SecureWrite(w, line.Value(source))
    337 			}
    338 		} else {
    339 			_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
    340 		}
    341 	} else {
    342 		if n.HasClosure() {
    343 			if r.Unsafe {
    344 				closure := n.ClosureLine
    345 				r.Writer.SecureWrite(w, closure.Value(source))
    346 			} else {
    347 				_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
    348 			}
    349 		}
    350 	}
    351 	return ast.WalkContinue, nil
    352 }
    353 
    354 // ListAttributeFilter defines attribute names which list elements can have.
    355 var ListAttributeFilter = GlobalAttributeFilter.Extend(
    356 	[]byte("start"),
    357 	[]byte("reversed"),
    358 	[]byte("type"),
    359 )
    360 
    361 func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    362 	n := node.(*ast.List)
    363 	tag := "ul"
    364 	if n.IsOrdered() {
    365 		tag = "ol"
    366 	}
    367 	if entering {
    368 		_ = w.WriteByte('<')
    369 		_, _ = w.WriteString(tag)
    370 		if n.IsOrdered() && n.Start != 1 {
    371 			fmt.Fprintf(w, " start=\"%d\"", n.Start)
    372 		}
    373 		if n.Attributes() != nil {
    374 			RenderAttributes(w, n, ListAttributeFilter)
    375 		}
    376 		_, _ = w.WriteString(">\n")
    377 	} else {
    378 		_, _ = w.WriteString("</")
    379 		_, _ = w.WriteString(tag)
    380 		_, _ = w.WriteString(">\n")
    381 	}
    382 	return ast.WalkContinue, nil
    383 }
    384 
    385 // ListItemAttributeFilter defines attribute names which list item elements can have.
    386 var ListItemAttributeFilter = GlobalAttributeFilter.Extend(
    387 	[]byte("value"),
    388 )
    389 
    390 func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    391 	if entering {
    392 		if n.Attributes() != nil {
    393 			_, _ = w.WriteString("<li")
    394 			RenderAttributes(w, n, ListItemAttributeFilter)
    395 			_ = w.WriteByte('>')
    396 		} else {
    397 			_, _ = w.WriteString("<li>")
    398 		}
    399 		fc := n.FirstChild()
    400 		if fc != nil {
    401 			if _, ok := fc.(*ast.TextBlock); !ok {
    402 				_ = w.WriteByte('\n')
    403 			}
    404 		}
    405 	} else {
    406 		_, _ = w.WriteString("</li>\n")
    407 	}
    408 	return ast.WalkContinue, nil
    409 }
    410 
    411 // ParagraphAttributeFilter defines attribute names which paragraph elements can have.
    412 var ParagraphAttributeFilter = GlobalAttributeFilter
    413 
    414 func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    415 	if entering {
    416 		if n.Attributes() != nil {
    417 			_, _ = w.WriteString("<p")
    418 			RenderAttributes(w, n, ParagraphAttributeFilter)
    419 			_ = w.WriteByte('>')
    420 		} else {
    421 			_, _ = w.WriteString("<p>")
    422 		}
    423 	} else {
    424 		_, _ = w.WriteString("</p>\n")
    425 	}
    426 	return ast.WalkContinue, nil
    427 }
    428 
    429 func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    430 	if !entering {
    431 		if _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil {
    432 			_ = w.WriteByte('\n')
    433 		}
    434 	}
    435 	return ast.WalkContinue, nil
    436 }
    437 
    438 // ThematicAttributeFilter defines attribute names which hr elements can have.
    439 var ThematicAttributeFilter = GlobalAttributeFilter.Extend(
    440 	[]byte("align"),   // [Deprecated]
    441 	[]byte("color"),   // [Not Standardized]
    442 	[]byte("noshade"), // [Deprecated]
    443 	[]byte("size"),    // [Deprecated]
    444 	[]byte("width"),   // [Deprecated]
    445 )
    446 
    447 func (r *Renderer) renderThematicBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    448 	if !entering {
    449 		return ast.WalkContinue, nil
    450 	}
    451 	_, _ = w.WriteString("<hr")
    452 	if n.Attributes() != nil {
    453 		RenderAttributes(w, n, ThematicAttributeFilter)
    454 	}
    455 	if r.XHTML {
    456 		_, _ = w.WriteString(" />\n")
    457 	} else {
    458 		_, _ = w.WriteString(">\n")
    459 	}
    460 	return ast.WalkContinue, nil
    461 }
    462 
    463 // LinkAttributeFilter defines attribute names which link elements can have.
    464 var LinkAttributeFilter = GlobalAttributeFilter.Extend(
    465 	[]byte("download"),
    466 	// []byte("href"),
    467 	[]byte("hreflang"),
    468 	[]byte("media"),
    469 	[]byte("ping"),
    470 	[]byte("referrerpolicy"),
    471 	[]byte("rel"),
    472 	[]byte("shape"),
    473 	[]byte("target"),
    474 )
    475 
    476 func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    477 	n := node.(*ast.AutoLink)
    478 	if !entering {
    479 		return ast.WalkContinue, nil
    480 	}
    481 	_, _ = w.WriteString(`<a href="`)
    482 	url := n.URL(source)
    483 	label := n.Label(source)
    484 	if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
    485 		_, _ = w.WriteString("mailto:")
    486 	}
    487 	_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
    488 	if n.Attributes() != nil {
    489 		_ = w.WriteByte('"')
    490 		RenderAttributes(w, n, LinkAttributeFilter)
    491 		_ = w.WriteByte('>')
    492 	} else {
    493 		_, _ = w.WriteString(`">`)
    494 	}
    495 	_, _ = w.Write(util.EscapeHTML(label))
    496 	_, _ = w.WriteString(`</a>`)
    497 	return ast.WalkContinue, nil
    498 }
    499 
    500 // CodeAttributeFilter defines attribute names which code elements can have.
    501 var CodeAttributeFilter = GlobalAttributeFilter
    502 
    503 func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
    504 	if entering {
    505 		if n.Attributes() != nil {
    506 			_, _ = w.WriteString("<code")
    507 			RenderAttributes(w, n, CodeAttributeFilter)
    508 			_ = w.WriteByte('>')
    509 		} else {
    510 			_, _ = w.WriteString("<code>")
    511 		}
    512 		for c := n.FirstChild(); c != nil; c = c.NextSibling() {
    513 			segment := c.(*ast.Text).Segment
    514 			value := segment.Value(source)
    515 			if bytes.HasSuffix(value, []byte("\n")) {
    516 				r.Writer.RawWrite(w, value[:len(value)-1])
    517 				r.Writer.RawWrite(w, []byte(" "))
    518 			} else {
    519 				r.Writer.RawWrite(w, value)
    520 			}
    521 		}
    522 		return ast.WalkSkipChildren, nil
    523 	}
    524 	_, _ = w.WriteString("</code>")
    525 	return ast.WalkContinue, nil
    526 }
    527 
    528 // EmphasisAttributeFilter defines attribute names which emphasis elements can have.
    529 var EmphasisAttributeFilter = GlobalAttributeFilter
    530 
    531 func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    532 	n := node.(*ast.Emphasis)
    533 	tag := "em"
    534 	if n.Level == 2 {
    535 		tag = "strong"
    536 	}
    537 	if entering {
    538 		_ = w.WriteByte('<')
    539 		_, _ = w.WriteString(tag)
    540 		if n.Attributes() != nil {
    541 			RenderAttributes(w, n, EmphasisAttributeFilter)
    542 		}
    543 		_ = w.WriteByte('>')
    544 	} else {
    545 		_, _ = w.WriteString("</")
    546 		_, _ = w.WriteString(tag)
    547 		_ = w.WriteByte('>')
    548 	}
    549 	return ast.WalkContinue, nil
    550 }
    551 
    552 func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    553 	n := node.(*ast.Link)
    554 	if entering {
    555 		_, _ = w.WriteString("<a href=\"")
    556 		if r.Unsafe || !IsDangerousURL(n.Destination) {
    557 			_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
    558 		}
    559 		_ = w.WriteByte('"')
    560 		if n.Title != nil {
    561 			_, _ = w.WriteString(` title="`)
    562 			r.Writer.Write(w, n.Title)
    563 			_ = w.WriteByte('"')
    564 		}
    565 		if n.Attributes() != nil {
    566 			RenderAttributes(w, n, LinkAttributeFilter)
    567 		}
    568 		_ = w.WriteByte('>')
    569 	} else {
    570 		_, _ = w.WriteString("</a>")
    571 	}
    572 	return ast.WalkContinue, nil
    573 }
    574 
    575 // ImageAttributeFilter defines attribute names which image elements can have.
    576 var ImageAttributeFilter = GlobalAttributeFilter.Extend(
    577 	[]byte("align"),
    578 	[]byte("border"),
    579 	[]byte("crossorigin"),
    580 	[]byte("decoding"),
    581 	[]byte("height"),
    582 	[]byte("importance"),
    583 	[]byte("intrinsicsize"),
    584 	[]byte("ismap"),
    585 	[]byte("loading"),
    586 	[]byte("referrerpolicy"),
    587 	[]byte("sizes"),
    588 	[]byte("srcset"),
    589 	[]byte("usemap"),
    590 	[]byte("width"),
    591 )
    592 
    593 func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    594 	if !entering {
    595 		return ast.WalkContinue, nil
    596 	}
    597 	n := node.(*ast.Image)
    598 	_, _ = w.WriteString("<img src=\"")
    599 	if r.Unsafe || !IsDangerousURL(n.Destination) {
    600 		_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
    601 	}
    602 	_, _ = w.WriteString(`" alt="`)
    603 	_, _ = w.Write(nodeToHTMLText(n, source))
    604 	_ = w.WriteByte('"')
    605 	if n.Title != nil {
    606 		_, _ = w.WriteString(` title="`)
    607 		r.Writer.Write(w, n.Title)
    608 		_ = w.WriteByte('"')
    609 	}
    610 	if n.Attributes() != nil {
    611 		RenderAttributes(w, n, ImageAttributeFilter)
    612 	}
    613 	if r.XHTML {
    614 		_, _ = w.WriteString(" />")
    615 	} else {
    616 		_, _ = w.WriteString(">")
    617 	}
    618 	return ast.WalkSkipChildren, nil
    619 }
    620 
    621 func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    622 	if !entering {
    623 		return ast.WalkSkipChildren, nil
    624 	}
    625 	if r.Unsafe {
    626 		n := node.(*ast.RawHTML)
    627 		l := n.Segments.Len()
    628 		for i := 0; i < l; i++ {
    629 			segment := n.Segments.At(i)
    630 			_, _ = w.Write(segment.Value(source))
    631 		}
    632 		return ast.WalkSkipChildren, nil
    633 	}
    634 	_, _ = w.WriteString("<!-- raw HTML omitted -->")
    635 	return ast.WalkSkipChildren, nil
    636 }
    637 
    638 func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    639 	if !entering {
    640 		return ast.WalkContinue, nil
    641 	}
    642 	n := node.(*ast.Text)
    643 	segment := n.Segment
    644 	if n.IsRaw() {
    645 		r.Writer.RawWrite(w, segment.Value(source))
    646 	} else {
    647 		value := segment.Value(source)
    648 		r.Writer.Write(w, value)
    649 		if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) {
    650 			if r.XHTML {
    651 				_, _ = w.WriteString("<br />\n")
    652 			} else {
    653 				_, _ = w.WriteString("<br>\n")
    654 			}
    655 		} else if n.SoftLineBreak() {
    656 			if r.EastAsianLineBreaks && len(value) != 0 {
    657 				sibling := node.NextSibling()
    658 				if sibling != nil && sibling.Kind() == ast.KindText {
    659 					if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 {
    660 						thisLastRune := util.ToRune(value, len(value)-1)
    661 						siblingFirstRune, _ := utf8.DecodeRune(siblingText)
    662 						if !(util.IsEastAsianWideRune(thisLastRune) &&
    663 							util.IsEastAsianWideRune(siblingFirstRune)) {
    664 							_ = w.WriteByte('\n')
    665 						}
    666 					}
    667 				}
    668 			} else {
    669 				_ = w.WriteByte('\n')
    670 			}
    671 		}
    672 	}
    673 	return ast.WalkContinue, nil
    674 }
    675 
    676 func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
    677 	if !entering {
    678 		return ast.WalkContinue, nil
    679 	}
    680 	n := node.(*ast.String)
    681 	if n.IsCode() {
    682 		_, _ = w.Write(n.Value)
    683 	} else {
    684 		if n.IsRaw() {
    685 			r.Writer.RawWrite(w, n.Value)
    686 		} else {
    687 			r.Writer.Write(w, n.Value)
    688 		}
    689 	}
    690 	return ast.WalkContinue, nil
    691 }
    692 
    693 var dataPrefix = []byte("data-")
    694 
    695 // RenderAttributes renders given node's attributes.
    696 // You can specify attribute names to render by the filter.
    697 // If filter is nil, RenderAttributes renders all attributes.
    698 func RenderAttributes(w util.BufWriter, node ast.Node, filter util.BytesFilter) {
    699 	for _, attr := range node.Attributes() {
    700 		if filter != nil && !filter.Contains(attr.Name) {
    701 			if !bytes.HasPrefix(attr.Name, dataPrefix) {
    702 				continue
    703 			}
    704 		}
    705 		_, _ = w.WriteString(" ")
    706 		_, _ = w.Write(attr.Name)
    707 		_, _ = w.WriteString(`="`)
    708 		// TODO: convert numeric values to strings
    709 		_, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
    710 		_ = w.WriteByte('"')
    711 	}
    712 }
    713 
    714 // A Writer interface writes textual contents to a writer.
    715 type Writer interface {
    716 	// Write writes the given source to writer with resolving references and unescaping
    717 	// backslash escaped characters.
    718 	Write(writer util.BufWriter, source []byte)
    719 
    720 	// RawWrite writes the given source to writer without resolving references and
    721 	// unescaping backslash escaped characters.
    722 	RawWrite(writer util.BufWriter, source []byte)
    723 
    724 	// SecureWrite writes the given source to writer with replacing insecure characters.
    725 	SecureWrite(writer util.BufWriter, source []byte)
    726 }
    727 
    728 var replacementCharacter = []byte("\ufffd")
    729 
    730 // A WriterConfig struct has configurations for the HTML based writers.
    731 type WriterConfig struct {
    732 	// EscapedSpace is an option that indicates that a '\' escaped half-space(0x20) should not be rendered.
    733 	EscapedSpace bool
    734 }
    735 
    736 // A WriterOption interface sets options for HTML based writers.
    737 type WriterOption func(*WriterConfig)
    738 
    739 // WithEscapedSpace is a WriterOption indicates that a '\' escaped half-space(0x20) should not be rendered.
    740 func WithEscapedSpace() WriterOption {
    741 	return func(c *WriterConfig) {
    742 		c.EscapedSpace = true
    743 	}
    744 }
    745 
    746 type defaultWriter struct {
    747 	WriterConfig
    748 }
    749 
    750 // NewWriter returns a new Writer.
    751 func NewWriter(opts ...WriterOption) Writer {
    752 	w := &defaultWriter{}
    753 	for _, opt := range opts {
    754 		opt(&w.WriterConfig)
    755 	}
    756 	return w
    757 }
    758 
    759 func escapeRune(writer util.BufWriter, r rune) {
    760 	if r < 256 {
    761 		v := util.EscapeHTMLByte(byte(r))
    762 		if v != nil {
    763 			_, _ = writer.Write(v)
    764 			return
    765 		}
    766 	}
    767 	_, _ = writer.WriteRune(util.ToValidRune(r))
    768 }
    769 
    770 func (d *defaultWriter) SecureWrite(writer util.BufWriter, source []byte) {
    771 	n := 0
    772 	l := len(source)
    773 	for i := 0; i < l; i++ {
    774 		if source[i] == '\u0000' {
    775 			_, _ = writer.Write(source[i-n : i])
    776 			n = 0
    777 			_, _ = writer.Write(replacementCharacter)
    778 			continue
    779 		}
    780 		n++
    781 	}
    782 	if n != 0 {
    783 		_, _ = writer.Write(source[l-n:])
    784 	}
    785 }
    786 
    787 func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) {
    788 	n := 0
    789 	l := len(source)
    790 	for i := 0; i < l; i++ {
    791 		v := util.EscapeHTMLByte(source[i])
    792 		if v != nil {
    793 			_, _ = writer.Write(source[i-n : i])
    794 			n = 0
    795 			_, _ = writer.Write(v)
    796 			continue
    797 		}
    798 		n++
    799 	}
    800 	if n != 0 {
    801 		_, _ = writer.Write(source[l-n:])
    802 	}
    803 }
    804 
    805 func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
    806 	escaped := false
    807 	var ok bool
    808 	limit := len(source)
    809 	n := 0
    810 	for i := 0; i < limit; i++ {
    811 		c := source[i]
    812 		if escaped {
    813 			if util.IsPunct(c) {
    814 				d.RawWrite(writer, source[n:i-1])
    815 				n = i
    816 				escaped = false
    817 				continue
    818 			}
    819 			if d.EscapedSpace && c == ' ' {
    820 				d.RawWrite(writer, source[n:i-1])
    821 				n = i + 1
    822 				escaped = false
    823 				continue
    824 			}
    825 		}
    826 		if c == '\x00' {
    827 			d.RawWrite(writer, source[n:i])
    828 			d.RawWrite(writer, replacementCharacter)
    829 			n = i + 1
    830 			escaped = false
    831 			continue
    832 		}
    833 		if c == '&' {
    834 			pos := i
    835 			next := i + 1
    836 			if next < limit && source[next] == '#' {
    837 				nnext := next + 1
    838 				if nnext < limit {
    839 					nc := source[nnext]
    840 					// code point like #x22;
    841 					if nnext < limit && nc == 'x' || nc == 'X' {
    842 						start := nnext + 1
    843 						i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal)
    844 						if ok && i < limit && source[i] == ';' && i-start < 7 {
    845 							v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
    846 							d.RawWrite(writer, source[n:pos])
    847 							n = i + 1
    848 							escapeRune(writer, rune(v))
    849 							continue
    850 						}
    851 						// code point like #1234;
    852 					} else if nc >= '0' && nc <= '9' {
    853 						start := nnext
    854 						i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric)
    855 						if ok && i < limit && i-start < 8 && source[i] == ';' {
    856 							v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 10, 32)
    857 							d.RawWrite(writer, source[n:pos])
    858 							n = i + 1
    859 							escapeRune(writer, rune(v))
    860 							continue
    861 						}
    862 					}
    863 				}
    864 			} else {
    865 				start := next
    866 				i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsAlphaNumeric)
    867 				// entity reference
    868 				if ok && i < limit && source[i] == ';' {
    869 					name := util.BytesToReadOnlyString(source[start:i])
    870 					entity, ok := util.LookUpHTML5EntityByName(name)
    871 					if ok {
    872 						d.RawWrite(writer, source[n:pos])
    873 						n = i + 1
    874 						d.RawWrite(writer, entity.Characters)
    875 						continue
    876 					}
    877 				}
    878 			}
    879 			i = next - 1
    880 		}
    881 		if c == '\\' {
    882 			escaped = true
    883 			continue
    884 		}
    885 		escaped = false
    886 	}
    887 	d.RawWrite(writer, source[n:])
    888 }
    889 
    890 // DefaultWriter is a default instance of the Writer.
    891 var DefaultWriter = NewWriter()
    892 
    893 var bDataImage = []byte("data:image/")
    894 var bPng = []byte("png;")
    895 var bGif = []byte("gif;")
    896 var bJpeg = []byte("jpeg;")
    897 var bWebp = []byte("webp;")
    898 var bSvg = []byte("svg+xml;")
    899 var bJs = []byte("javascript:")
    900 var bVb = []byte("vbscript:")
    901 var bFile = []byte("file:")
    902 var bData = []byte("data:")
    903 
    904 // IsDangerousURL returns true if the given url seems a potentially dangerous url,
    905 // otherwise false.
    906 func IsDangerousURL(url []byte) bool {
    907 	if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
    908 		v := url[11:]
    909 		if bytes.HasPrefix(v, bPng) || bytes.HasPrefix(v, bGif) ||
    910 			bytes.HasPrefix(v, bJpeg) || bytes.HasPrefix(v, bWebp) ||
    911 			bytes.HasPrefix(v, bSvg) {
    912 			return false
    913 		}
    914 		return true
    915 	}
    916 	return bytes.HasPrefix(url, bJs) || bytes.HasPrefix(url, bVb) ||
    917 		bytes.HasPrefix(url, bFile) || bytes.HasPrefix(url, bData)
    918 }
    919 
    920 func nodeToHTMLText(n ast.Node, source []byte) []byte {
    921 	var buf bytes.Buffer
    922 	for c := n.FirstChild(); c != nil; c = c.NextSibling() {
    923 		if s, ok := c.(*ast.String); ok && s.IsCode() {
    924 			buf.Write(s.Text(source))
    925 		} else if !c.HasChildren() {
    926 			buf.Write(util.EscapeHTML(c.Text(source)))
    927 		} else {
    928 			buf.Write(nodeToHTMLText(c, source))
    929 		}
    930 	}
    931 	return buf.Bytes()
    932 }