...

Source file src/github.com/yuin/goldmark/renderer/html/html.go

Documentation: github.com/yuin/goldmark/renderer/html

     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("slot"),
   248  	[]byte("spellcheck"),
   249  	[]byte("style"),
   250  	[]byte("tabindex"),
   251  	[]byte("title"),
   252  	[]byte("translate"),
   253  )
   254  
   255  func (r *Renderer) renderDocument(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   256  	// nothing to do
   257  	return ast.WalkContinue, nil
   258  }
   259  
   260  // HeadingAttributeFilter defines attribute names which heading elements can have
   261  var HeadingAttributeFilter = GlobalAttributeFilter
   262  
   263  func (r *Renderer) renderHeading(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   264  	n := node.(*ast.Heading)
   265  	if entering {
   266  		_, _ = w.WriteString("<h")
   267  		_ = w.WriteByte("0123456"[n.Level])
   268  		if n.Attributes() != nil {
   269  			RenderAttributes(w, node, HeadingAttributeFilter)
   270  		}
   271  		_ = w.WriteByte('>')
   272  	} else {
   273  		_, _ = w.WriteString("</h")
   274  		_ = w.WriteByte("0123456"[n.Level])
   275  		_, _ = w.WriteString(">\n")
   276  	}
   277  	return ast.WalkContinue, nil
   278  }
   279  
   280  // BlockquoteAttributeFilter defines attribute names which blockquote elements can have
   281  var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend(
   282  	[]byte("cite"),
   283  )
   284  
   285  func (r *Renderer) renderBlockquote(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   286  	if entering {
   287  		if n.Attributes() != nil {
   288  			_, _ = w.WriteString("<blockquote")
   289  			RenderAttributes(w, n, BlockquoteAttributeFilter)
   290  			_ = w.WriteByte('>')
   291  		} else {
   292  			_, _ = w.WriteString("<blockquote>\n")
   293  		}
   294  	} else {
   295  		_, _ = w.WriteString("</blockquote>\n")
   296  	}
   297  	return ast.WalkContinue, nil
   298  }
   299  
   300  func (r *Renderer) renderCodeBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   301  	if entering {
   302  		_, _ = w.WriteString("<pre><code>")
   303  		r.writeLines(w, source, n)
   304  	} else {
   305  		_, _ = w.WriteString("</code></pre>\n")
   306  	}
   307  	return ast.WalkContinue, nil
   308  }
   309  
   310  func (r *Renderer) renderFencedCodeBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   311  	n := node.(*ast.FencedCodeBlock)
   312  	if entering {
   313  		_, _ = w.WriteString("<pre><code")
   314  		language := n.Language(source)
   315  		if language != nil {
   316  			_, _ = w.WriteString(" class=\"language-")
   317  			r.Writer.Write(w, language)
   318  			_, _ = w.WriteString("\"")
   319  		}
   320  		_ = w.WriteByte('>')
   321  		r.writeLines(w, source, n)
   322  	} else {
   323  		_, _ = w.WriteString("</code></pre>\n")
   324  	}
   325  	return ast.WalkContinue, nil
   326  }
   327  
   328  func (r *Renderer) renderHTMLBlock(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   329  	n := node.(*ast.HTMLBlock)
   330  	if entering {
   331  		if r.Unsafe {
   332  			l := n.Lines().Len()
   333  			for i := 0; i < l; i++ {
   334  				line := n.Lines().At(i)
   335  				r.Writer.SecureWrite(w, line.Value(source))
   336  			}
   337  		} else {
   338  			_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
   339  		}
   340  	} else {
   341  		if n.HasClosure() {
   342  			if r.Unsafe {
   343  				closure := n.ClosureLine
   344  				r.Writer.SecureWrite(w, closure.Value(source))
   345  			} else {
   346  				_, _ = w.WriteString("<!-- raw HTML omitted -->\n")
   347  			}
   348  		}
   349  	}
   350  	return ast.WalkContinue, nil
   351  }
   352  
   353  // ListAttributeFilter defines attribute names which list elements can have.
   354  var ListAttributeFilter = GlobalAttributeFilter.Extend(
   355  	[]byte("start"),
   356  	[]byte("reversed"),
   357  	[]byte("type"),
   358  )
   359  
   360  func (r *Renderer) renderList(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   361  	n := node.(*ast.List)
   362  	tag := "ul"
   363  	if n.IsOrdered() {
   364  		tag = "ol"
   365  	}
   366  	if entering {
   367  		_ = w.WriteByte('<')
   368  		_, _ = w.WriteString(tag)
   369  		if n.IsOrdered() && n.Start != 1 {
   370  			fmt.Fprintf(w, " start=\"%d\"", n.Start)
   371  		}
   372  		if n.Attributes() != nil {
   373  			RenderAttributes(w, n, ListAttributeFilter)
   374  		}
   375  		_, _ = w.WriteString(">\n")
   376  	} else {
   377  		_, _ = w.WriteString("</")
   378  		_, _ = w.WriteString(tag)
   379  		_, _ = w.WriteString(">\n")
   380  	}
   381  	return ast.WalkContinue, nil
   382  }
   383  
   384  // ListItemAttributeFilter defines attribute names which list item elements can have.
   385  var ListItemAttributeFilter = GlobalAttributeFilter.Extend(
   386  	[]byte("value"),
   387  )
   388  
   389  func (r *Renderer) renderListItem(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   390  	if entering {
   391  		if n.Attributes() != nil {
   392  			_, _ = w.WriteString("<li")
   393  			RenderAttributes(w, n, ListItemAttributeFilter)
   394  			_ = w.WriteByte('>')
   395  		} else {
   396  			_, _ = w.WriteString("<li>")
   397  		}
   398  		fc := n.FirstChild()
   399  		if fc != nil {
   400  			if _, ok := fc.(*ast.TextBlock); !ok {
   401  				_ = w.WriteByte('\n')
   402  			}
   403  		}
   404  	} else {
   405  		_, _ = w.WriteString("</li>\n")
   406  	}
   407  	return ast.WalkContinue, nil
   408  }
   409  
   410  // ParagraphAttributeFilter defines attribute names which paragraph elements can have.
   411  var ParagraphAttributeFilter = GlobalAttributeFilter
   412  
   413  func (r *Renderer) renderParagraph(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   414  	if entering {
   415  		if n.Attributes() != nil {
   416  			_, _ = w.WriteString("<p")
   417  			RenderAttributes(w, n, ParagraphAttributeFilter)
   418  			_ = w.WriteByte('>')
   419  		} else {
   420  			_, _ = w.WriteString("<p>")
   421  		}
   422  	} else {
   423  		_, _ = w.WriteString("</p>\n")
   424  	}
   425  	return ast.WalkContinue, nil
   426  }
   427  
   428  func (r *Renderer) renderTextBlock(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   429  	if !entering {
   430  		if _, ok := n.NextSibling().(ast.Node); ok && n.FirstChild() != nil {
   431  			_ = w.WriteByte('\n')
   432  		}
   433  	}
   434  	return ast.WalkContinue, nil
   435  }
   436  
   437  // ThematicAttributeFilter defines attribute names which hr elements can have.
   438  var ThematicAttributeFilter = GlobalAttributeFilter.Extend(
   439  	[]byte("align"),   // [Deprecated]
   440  	[]byte("color"),   // [Not Standardized]
   441  	[]byte("noshade"), // [Deprecated]
   442  	[]byte("size"),    // [Deprecated]
   443  	[]byte("width"),   // [Deprecated]
   444  )
   445  
   446  func (r *Renderer) renderThematicBreak(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   447  	if !entering {
   448  		return ast.WalkContinue, nil
   449  	}
   450  	_, _ = w.WriteString("<hr")
   451  	if n.Attributes() != nil {
   452  		RenderAttributes(w, n, ThematicAttributeFilter)
   453  	}
   454  	if r.XHTML {
   455  		_, _ = w.WriteString(" />\n")
   456  	} else {
   457  		_, _ = w.WriteString(">\n")
   458  	}
   459  	return ast.WalkContinue, nil
   460  }
   461  
   462  // LinkAttributeFilter defines attribute names which link elements can have.
   463  var LinkAttributeFilter = GlobalAttributeFilter.Extend(
   464  	[]byte("download"),
   465  	// []byte("href"),
   466  	[]byte("hreflang"),
   467  	[]byte("media"),
   468  	[]byte("ping"),
   469  	[]byte("referrerpolicy"),
   470  	[]byte("rel"),
   471  	[]byte("shape"),
   472  	[]byte("target"),
   473  )
   474  
   475  func (r *Renderer) renderAutoLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   476  	n := node.(*ast.AutoLink)
   477  	if !entering {
   478  		return ast.WalkContinue, nil
   479  	}
   480  	_, _ = w.WriteString(`<a href="`)
   481  	url := n.URL(source)
   482  	label := n.Label(source)
   483  	if n.AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(url), []byte("mailto:")) {
   484  		_, _ = w.WriteString("mailto:")
   485  	}
   486  	_, _ = w.Write(util.EscapeHTML(util.URLEscape(url, false)))
   487  	if n.Attributes() != nil {
   488  		_ = w.WriteByte('"')
   489  		RenderAttributes(w, n, LinkAttributeFilter)
   490  		_ = w.WriteByte('>')
   491  	} else {
   492  		_, _ = w.WriteString(`">`)
   493  	}
   494  	_, _ = w.Write(util.EscapeHTML(label))
   495  	_, _ = w.WriteString(`</a>`)
   496  	return ast.WalkContinue, nil
   497  }
   498  
   499  // CodeAttributeFilter defines attribute names which code elements can have.
   500  var CodeAttributeFilter = GlobalAttributeFilter
   501  
   502  func (r *Renderer) renderCodeSpan(w util.BufWriter, source []byte, n ast.Node, entering bool) (ast.WalkStatus, error) {
   503  	if entering {
   504  		if n.Attributes() != nil {
   505  			_, _ = w.WriteString("<code")
   506  			RenderAttributes(w, n, CodeAttributeFilter)
   507  			_ = w.WriteByte('>')
   508  		} else {
   509  			_, _ = w.WriteString("<code>")
   510  		}
   511  		for c := n.FirstChild(); c != nil; c = c.NextSibling() {
   512  			segment := c.(*ast.Text).Segment
   513  			value := segment.Value(source)
   514  			if bytes.HasSuffix(value, []byte("\n")) {
   515  				r.Writer.RawWrite(w, value[:len(value)-1])
   516  				r.Writer.RawWrite(w, []byte(" "))
   517  			} else {
   518  				r.Writer.RawWrite(w, value)
   519  			}
   520  		}
   521  		return ast.WalkSkipChildren, nil
   522  	}
   523  	_, _ = w.WriteString("</code>")
   524  	return ast.WalkContinue, nil
   525  }
   526  
   527  // EmphasisAttributeFilter defines attribute names which emphasis elements can have.
   528  var EmphasisAttributeFilter = GlobalAttributeFilter
   529  
   530  func (r *Renderer) renderEmphasis(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   531  	n := node.(*ast.Emphasis)
   532  	tag := "em"
   533  	if n.Level == 2 {
   534  		tag = "strong"
   535  	}
   536  	if entering {
   537  		_ = w.WriteByte('<')
   538  		_, _ = w.WriteString(tag)
   539  		if n.Attributes() != nil {
   540  			RenderAttributes(w, n, EmphasisAttributeFilter)
   541  		}
   542  		_ = w.WriteByte('>')
   543  	} else {
   544  		_, _ = w.WriteString("</")
   545  		_, _ = w.WriteString(tag)
   546  		_ = w.WriteByte('>')
   547  	}
   548  	return ast.WalkContinue, nil
   549  }
   550  
   551  func (r *Renderer) renderLink(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   552  	n := node.(*ast.Link)
   553  	if entering {
   554  		_, _ = w.WriteString("<a href=\"")
   555  		if r.Unsafe || !IsDangerousURL(n.Destination) {
   556  			_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
   557  		}
   558  		_ = w.WriteByte('"')
   559  		if n.Title != nil {
   560  			_, _ = w.WriteString(` title="`)
   561  			r.Writer.Write(w, n.Title)
   562  			_ = w.WriteByte('"')
   563  		}
   564  		if n.Attributes() != nil {
   565  			RenderAttributes(w, n, LinkAttributeFilter)
   566  		}
   567  		_ = w.WriteByte('>')
   568  	} else {
   569  		_, _ = w.WriteString("</a>")
   570  	}
   571  	return ast.WalkContinue, nil
   572  }
   573  
   574  // ImageAttributeFilter defines attribute names which image elements can have.
   575  var ImageAttributeFilter = GlobalAttributeFilter.Extend(
   576  	[]byte("align"),
   577  	[]byte("border"),
   578  	[]byte("crossorigin"),
   579  	[]byte("decoding"),
   580  	[]byte("height"),
   581  	[]byte("importance"),
   582  	[]byte("intrinsicsize"),
   583  	[]byte("ismap"),
   584  	[]byte("loading"),
   585  	[]byte("referrerpolicy"),
   586  	[]byte("sizes"),
   587  	[]byte("srcset"),
   588  	[]byte("usemap"),
   589  	[]byte("width"),
   590  )
   591  
   592  func (r *Renderer) renderImage(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   593  	if !entering {
   594  		return ast.WalkContinue, nil
   595  	}
   596  	n := node.(*ast.Image)
   597  	_, _ = w.WriteString("<img src=\"")
   598  	if r.Unsafe || !IsDangerousURL(n.Destination) {
   599  		_, _ = w.Write(util.EscapeHTML(util.URLEscape(n.Destination, true)))
   600  	}
   601  	_, _ = w.WriteString(`" alt="`)
   602  	_, _ = w.Write(nodeToHTMLText(n, source))
   603  	_ = w.WriteByte('"')
   604  	if n.Title != nil {
   605  		_, _ = w.WriteString(` title="`)
   606  		r.Writer.Write(w, n.Title)
   607  		_ = w.WriteByte('"')
   608  	}
   609  	if n.Attributes() != nil {
   610  		RenderAttributes(w, n, ImageAttributeFilter)
   611  	}
   612  	if r.XHTML {
   613  		_, _ = w.WriteString(" />")
   614  	} else {
   615  		_, _ = w.WriteString(">")
   616  	}
   617  	return ast.WalkSkipChildren, nil
   618  }
   619  
   620  func (r *Renderer) renderRawHTML(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   621  	if !entering {
   622  		return ast.WalkSkipChildren, nil
   623  	}
   624  	if r.Unsafe {
   625  		n := node.(*ast.RawHTML)
   626  		l := n.Segments.Len()
   627  		for i := 0; i < l; i++ {
   628  			segment := n.Segments.At(i)
   629  			_, _ = w.Write(segment.Value(source))
   630  		}
   631  		return ast.WalkSkipChildren, nil
   632  	}
   633  	_, _ = w.WriteString("<!-- raw HTML omitted -->")
   634  	return ast.WalkSkipChildren, nil
   635  }
   636  
   637  func (r *Renderer) renderText(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   638  	if !entering {
   639  		return ast.WalkContinue, nil
   640  	}
   641  	n := node.(*ast.Text)
   642  	segment := n.Segment
   643  	if n.IsRaw() {
   644  		r.Writer.RawWrite(w, segment.Value(source))
   645  	} else {
   646  		value := segment.Value(source)
   647  		r.Writer.Write(w, value)
   648  		if n.HardLineBreak() || (n.SoftLineBreak() && r.HardWraps) {
   649  			if r.XHTML {
   650  				_, _ = w.WriteString("<br />\n")
   651  			} else {
   652  				_, _ = w.WriteString("<br>\n")
   653  			}
   654  		} else if n.SoftLineBreak() {
   655  			if r.EastAsianLineBreaks && len(value) != 0 {
   656  				sibling := node.NextSibling()
   657  				if sibling != nil && sibling.Kind() == ast.KindText {
   658  					if siblingText := sibling.(*ast.Text).Text(source); len(siblingText) != 0 {
   659  						thisLastRune := util.ToRune(value, len(value)-1)
   660  						siblingFirstRune, _ := utf8.DecodeRune(siblingText)
   661  						if !(util.IsEastAsianWideRune(thisLastRune) &&
   662  							util.IsEastAsianWideRune(siblingFirstRune)) {
   663  							_ = w.WriteByte('\n')
   664  						}
   665  					}
   666  				}
   667  			} else {
   668  				_ = w.WriteByte('\n')
   669  			}
   670  		}
   671  	}
   672  	return ast.WalkContinue, nil
   673  }
   674  
   675  func (r *Renderer) renderString(w util.BufWriter, source []byte, node ast.Node, entering bool) (ast.WalkStatus, error) {
   676  	if !entering {
   677  		return ast.WalkContinue, nil
   678  	}
   679  	n := node.(*ast.String)
   680  	if n.IsCode() {
   681  		_, _ = w.Write(n.Value)
   682  	} else {
   683  		if n.IsRaw() {
   684  			r.Writer.RawWrite(w, n.Value)
   685  		} else {
   686  			r.Writer.Write(w, n.Value)
   687  		}
   688  	}
   689  	return ast.WalkContinue, nil
   690  }
   691  
   692  var dataPrefix = []byte("data-")
   693  
   694  // RenderAttributes renders given node's attributes.
   695  // You can specify attribute names to render by the filter.
   696  // If filter is nil, RenderAttributes renders all attributes.
   697  func RenderAttributes(w util.BufWriter, node ast.Node, filter util.BytesFilter) {
   698  	for _, attr := range node.Attributes() {
   699  		if filter != nil && !filter.Contains(attr.Name) {
   700  			if !bytes.HasPrefix(attr.Name, dataPrefix) {
   701  				continue
   702  			}
   703  		}
   704  		_, _ = w.WriteString(" ")
   705  		_, _ = w.Write(attr.Name)
   706  		_, _ = w.WriteString(`="`)
   707  		// TODO: convert numeric values to strings
   708  		_, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
   709  		_ = w.WriteByte('"')
   710  	}
   711  }
   712  
   713  // A Writer interface writes textual contents to a writer.
   714  type Writer interface {
   715  	// Write writes the given source to writer with resolving references and unescaping
   716  	// backslash escaped characters.
   717  	Write(writer util.BufWriter, source []byte)
   718  
   719  	// RawWrite writes the given source to writer without resolving references and
   720  	// unescaping backslash escaped characters.
   721  	RawWrite(writer util.BufWriter, source []byte)
   722  
   723  	// SecureWrite writes the given source to writer with replacing insecure characters.
   724  	SecureWrite(writer util.BufWriter, source []byte)
   725  }
   726  
   727  var replacementCharacter = []byte("\ufffd")
   728  
   729  // A WriterConfig struct has configurations for the HTML based writers.
   730  type WriterConfig struct {
   731  	// EscapedSpace is an option that indicates that a '\' escaped half-space(0x20) should not be rendered.
   732  	EscapedSpace bool
   733  }
   734  
   735  // A WriterOption interface sets options for HTML based writers.
   736  type WriterOption func(*WriterConfig)
   737  
   738  // WithEscapedSpace is a WriterOption indicates that a '\' escaped half-space(0x20) should not be rendered.
   739  func WithEscapedSpace() WriterOption {
   740  	return func(c *WriterConfig) {
   741  		c.EscapedSpace = true
   742  	}
   743  }
   744  
   745  type defaultWriter struct {
   746  	WriterConfig
   747  }
   748  
   749  // NewWriter returns a new Writer.
   750  func NewWriter(opts ...WriterOption) Writer {
   751  	w := &defaultWriter{}
   752  	for _, opt := range opts {
   753  		opt(&w.WriterConfig)
   754  	}
   755  	return w
   756  }
   757  
   758  func escapeRune(writer util.BufWriter, r rune) {
   759  	if r < 256 {
   760  		v := util.EscapeHTMLByte(byte(r))
   761  		if v != nil {
   762  			_, _ = writer.Write(v)
   763  			return
   764  		}
   765  	}
   766  	_, _ = writer.WriteRune(util.ToValidRune(r))
   767  }
   768  
   769  func (d *defaultWriter) SecureWrite(writer util.BufWriter, source []byte) {
   770  	n := 0
   771  	l := len(source)
   772  	for i := 0; i < l; i++ {
   773  		if source[i] == '\u0000' {
   774  			_, _ = writer.Write(source[i-n : i])
   775  			n = 0
   776  			_, _ = writer.Write(replacementCharacter)
   777  			continue
   778  		}
   779  		n++
   780  	}
   781  	if n != 0 {
   782  		_, _ = writer.Write(source[l-n:])
   783  	}
   784  }
   785  
   786  func (d *defaultWriter) RawWrite(writer util.BufWriter, source []byte) {
   787  	n := 0
   788  	l := len(source)
   789  	for i := 0; i < l; i++ {
   790  		v := util.EscapeHTMLByte(source[i])
   791  		if v != nil {
   792  			_, _ = writer.Write(source[i-n : i])
   793  			n = 0
   794  			_, _ = writer.Write(v)
   795  			continue
   796  		}
   797  		n++
   798  	}
   799  	if n != 0 {
   800  		_, _ = writer.Write(source[l-n:])
   801  	}
   802  }
   803  
   804  func (d *defaultWriter) Write(writer util.BufWriter, source []byte) {
   805  	escaped := false
   806  	var ok bool
   807  	limit := len(source)
   808  	n := 0
   809  	for i := 0; i < limit; i++ {
   810  		c := source[i]
   811  		if escaped {
   812  			if util.IsPunct(c) {
   813  				d.RawWrite(writer, source[n:i-1])
   814  				n = i
   815  				escaped = false
   816  				continue
   817  			}
   818  			if d.EscapedSpace && c == ' ' {
   819  				d.RawWrite(writer, source[n:i-1])
   820  				n = i + 1
   821  				escaped = false
   822  				continue
   823  			}
   824  		}
   825  		if c == '\x00' {
   826  			d.RawWrite(writer, source[n:i])
   827  			d.RawWrite(writer, replacementCharacter)
   828  			n = i + 1
   829  			escaped = false
   830  			continue
   831  		}
   832  		if c == '&' {
   833  			pos := i
   834  			next := i + 1
   835  			if next < limit && source[next] == '#' {
   836  				nnext := next + 1
   837  				if nnext < limit {
   838  					nc := source[nnext]
   839  					// code point like #x22;
   840  					if nnext < limit && nc == 'x' || nc == 'X' {
   841  						start := nnext + 1
   842  						i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsHexDecimal)
   843  						if ok && i < limit && source[i] == ';' && i-start < 7 {
   844  							v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 16, 32)
   845  							d.RawWrite(writer, source[n:pos])
   846  							n = i + 1
   847  							escapeRune(writer, rune(v))
   848  							continue
   849  						}
   850  						// code point like #1234;
   851  					} else if nc >= '0' && nc <= '9' {
   852  						start := nnext
   853  						i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsNumeric)
   854  						if ok && i < limit && i-start < 8 && source[i] == ';' {
   855  							v, _ := strconv.ParseUint(util.BytesToReadOnlyString(source[start:i]), 10, 32)
   856  							d.RawWrite(writer, source[n:pos])
   857  							n = i + 1
   858  							escapeRune(writer, rune(v))
   859  							continue
   860  						}
   861  					}
   862  				}
   863  			} else {
   864  				start := next
   865  				i, ok = util.ReadWhile(source, [2]int{start, limit}, util.IsAlphaNumeric)
   866  				// entity reference
   867  				if ok && i < limit && source[i] == ';' {
   868  					name := util.BytesToReadOnlyString(source[start:i])
   869  					entity, ok := util.LookUpHTML5EntityByName(name)
   870  					if ok {
   871  						d.RawWrite(writer, source[n:pos])
   872  						n = i + 1
   873  						d.RawWrite(writer, entity.Characters)
   874  						continue
   875  					}
   876  				}
   877  			}
   878  			i = next - 1
   879  		}
   880  		if c == '\\' {
   881  			escaped = true
   882  			continue
   883  		}
   884  		escaped = false
   885  	}
   886  	d.RawWrite(writer, source[n:])
   887  }
   888  
   889  // DefaultWriter is a default instance of the Writer.
   890  var DefaultWriter = NewWriter()
   891  
   892  var bDataImage = []byte("data:image/")
   893  var bPng = []byte("png;")
   894  var bGif = []byte("gif;")
   895  var bJpeg = []byte("jpeg;")
   896  var bWebp = []byte("webp;")
   897  var bSvg = []byte("svg+xml;")
   898  var bJs = []byte("javascript:")
   899  var bVb = []byte("vbscript:")
   900  var bFile = []byte("file:")
   901  var bData = []byte("data:")
   902  
   903  // IsDangerousURL returns true if the given url seems a potentially dangerous url,
   904  // otherwise false.
   905  func IsDangerousURL(url []byte) bool {
   906  	if bytes.HasPrefix(url, bDataImage) && len(url) >= 11 {
   907  		v := url[11:]
   908  		if bytes.HasPrefix(v, bPng) || bytes.HasPrefix(v, bGif) ||
   909  			bytes.HasPrefix(v, bJpeg) || bytes.HasPrefix(v, bWebp) ||
   910  			bytes.HasPrefix(v, bSvg) {
   911  			return false
   912  		}
   913  		return true
   914  	}
   915  	return bytes.HasPrefix(url, bJs) || bytes.HasPrefix(url, bVb) ||
   916  		bytes.HasPrefix(url, bFile) || bytes.HasPrefix(url, bData)
   917  }
   918  
   919  func nodeToHTMLText(n ast.Node, source []byte) []byte {
   920  	var buf bytes.Buffer
   921  	for c := n.FirstChild(); c != nil; c = c.NextSibling() {
   922  		if s, ok := c.(*ast.String); ok && s.IsCode() {
   923  			buf.Write(s.Text(source))
   924  		} else if !c.HasChildren() {
   925  			buf.Write(util.EscapeHTML(c.Text(source)))
   926  		} else {
   927  			buf.Write(nodeToHTMLText(c, source))
   928  		}
   929  	}
   930  	return buf.Bytes()
   931  }
   932  

View as plain text