package html
import (
"bytes"
"fmt"
"strconv"
"unicode"
"unicode/utf8"
"github.com/yuin/goldmark/ast"
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/util"
)
type Config struct {
Writer Writer
HardWraps bool
EastAsianLineBreaks EastAsianLineBreaks
XHTML bool
Unsafe bool
}
func NewConfig () Config {
return Config {
Writer : DefaultWriter ,
HardWraps : false ,
EastAsianLineBreaks : EastAsianLineBreaksNone ,
XHTML : false ,
Unsafe : false ,
}
}
func (c *Config ) SetOption (name renderer .OptionName , value interface {}) {
switch name {
case optHardWraps :
c .HardWraps = value .(bool )
case optEastAsianLineBreaks :
c .EastAsianLineBreaks = value .(EastAsianLineBreaks )
case optXHTML :
c .XHTML = value .(bool )
case optUnsafe :
c .Unsafe = value .(bool )
case optTextWriter :
c .Writer = value .(Writer )
}
}
type Option interface {
SetHTMLOption (*Config )
}
const optTextWriter renderer .OptionName = "Writer"
type withWriter struct {
value Writer
}
func (o *withWriter ) SetConfig (c *renderer .Config ) {
c .Options [optTextWriter ] = o .value
}
func (o *withWriter ) SetHTMLOption (c *Config ) {
c .Writer = o .value
}
func WithWriter (writer Writer ) interface {
renderer .Option
Option
} {
return &withWriter {writer }
}
const optHardWraps renderer .OptionName = "HardWraps"
type withHardWraps struct {
}
func (o *withHardWraps ) SetConfig (c *renderer .Config ) {
c .Options [optHardWraps ] = true
}
func (o *withHardWraps ) SetHTMLOption (c *Config ) {
c .HardWraps = true
}
func WithHardWraps () interface {
renderer .Option
Option
} {
return &withHardWraps {}
}
const optEastAsianLineBreaks renderer .OptionName = "EastAsianLineBreaks"
type EastAsianLineBreaks int
const (
EastAsianLineBreaksNone EastAsianLineBreaks = iota
EastAsianLineBreaksSimple
EastAsianLineBreaksCSS3Draft
)
func (b EastAsianLineBreaks ) softLineBreak (thisLastRune rune , siblingFirstRune rune ) bool {
switch b {
case EastAsianLineBreaksNone :
return false
case EastAsianLineBreaksSimple :
return !(util .IsEastAsianWideRune (thisLastRune ) && util .IsEastAsianWideRune (siblingFirstRune ))
case EastAsianLineBreaksCSS3Draft :
return eastAsianLineBreaksCSS3DraftSoftLineBreak (thisLastRune , siblingFirstRune )
}
return false
}
func eastAsianLineBreaksCSS3DraftSoftLineBreak(thisLastRune rune , siblingFirstRune rune ) bool {
if thisLastRune == '\u200B' || siblingFirstRune == '\u200B' {
return false
}
thisLastRuneEastAsianWidth := util .EastAsianWidth (thisLastRune )
siblingFirstRuneEastAsianWidth := util .EastAsianWidth (siblingFirstRune )
if (thisLastRuneEastAsianWidth == "F" ||
thisLastRuneEastAsianWidth == "W" ||
thisLastRuneEastAsianWidth == "H" ) &&
(siblingFirstRuneEastAsianWidth == "F" ||
siblingFirstRuneEastAsianWidth == "W" ||
siblingFirstRuneEastAsianWidth == "H" ) {
return unicode .Is (unicode .Hangul , thisLastRune ) || unicode .Is (unicode .Hangul , siblingFirstRune )
}
if util .IsSpaceDiscardingUnicodeRune (thisLastRune ) ||
unicode .IsPunct (thisLastRune ) ||
thisLastRune == '\u3000' ||
util .IsSpaceDiscardingUnicodeRune (siblingFirstRune ) ||
unicode .IsPunct (siblingFirstRune ) ||
siblingFirstRune == '\u3000' {
return false
}
return true
}
type withEastAsianLineBreaks struct {
eastAsianLineBreaksStyle EastAsianLineBreaks
}
func (o *withEastAsianLineBreaks ) SetConfig (c *renderer .Config ) {
c .Options [optEastAsianLineBreaks ] = o .eastAsianLineBreaksStyle
}
func (o *withEastAsianLineBreaks ) SetHTMLOption (c *Config ) {
c .EastAsianLineBreaks = o .eastAsianLineBreaksStyle
}
func WithEastAsianLineBreaks (e EastAsianLineBreaks ) interface {
renderer .Option
Option
} {
return &withEastAsianLineBreaks {e }
}
const optXHTML renderer .OptionName = "XHTML"
type withXHTML struct {
}
func (o *withXHTML ) SetConfig (c *renderer .Config ) {
c .Options [optXHTML ] = true
}
func (o *withXHTML ) SetHTMLOption (c *Config ) {
c .XHTML = true
}
func WithXHTML () interface {
Option
renderer .Option
} {
return &withXHTML {}
}
const optUnsafe renderer .OptionName = "Unsafe"
type withUnsafe struct {
}
func (o *withUnsafe ) SetConfig (c *renderer .Config ) {
c .Options [optUnsafe ] = true
}
func (o *withUnsafe ) SetHTMLOption (c *Config ) {
c .Unsafe = true
}
func WithUnsafe () interface {
renderer .Option
Option
} {
return &withUnsafe {}
}
type Renderer struct {
Config
}
func NewRenderer (opts ...Option ) renderer .NodeRenderer {
r := &Renderer {
Config : NewConfig (),
}
for _ , opt := range opts {
opt .SetHTMLOption (&r .Config )
}
return r
}
func (r *Renderer ) RegisterFuncs (reg renderer .NodeRendererFuncRegisterer ) {
reg .Register (ast .KindDocument , r .renderDocument )
reg .Register (ast .KindHeading , r .renderHeading )
reg .Register (ast .KindBlockquote , r .renderBlockquote )
reg .Register (ast .KindCodeBlock , r .renderCodeBlock )
reg .Register (ast .KindFencedCodeBlock , r .renderFencedCodeBlock )
reg .Register (ast .KindHTMLBlock , r .renderHTMLBlock )
reg .Register (ast .KindList , r .renderList )
reg .Register (ast .KindListItem , r .renderListItem )
reg .Register (ast .KindParagraph , r .renderParagraph )
reg .Register (ast .KindTextBlock , r .renderTextBlock )
reg .Register (ast .KindThematicBreak , r .renderThematicBreak )
reg .Register (ast .KindAutoLink , r .renderAutoLink )
reg .Register (ast .KindCodeSpan , r .renderCodeSpan )
reg .Register (ast .KindEmphasis , r .renderEmphasis )
reg .Register (ast .KindImage , r .renderImage )
reg .Register (ast .KindLink , r .renderLink )
reg .Register (ast .KindRawHTML , r .renderRawHTML )
reg .Register (ast .KindText , r .renderText )
reg .Register (ast .KindString , r .renderString )
}
func (r *Renderer ) writeLines (w util .BufWriter , source []byte , n ast .Node ) {
l := n .Lines ().Len ()
for i := 0 ; i < l ; i ++ {
line := n .Lines ().At (i )
r .Writer .RawWrite (w , line .Value (source ))
}
}
var GlobalAttributeFilter = util .NewBytesFilter (
[]byte ("accesskey" ),
[]byte ("autocapitalize" ),
[]byte ("autofocus" ),
[]byte ("class" ),
[]byte ("contenteditable" ),
[]byte ("dir" ),
[]byte ("draggable" ),
[]byte ("enterkeyhint" ),
[]byte ("hidden" ),
[]byte ("id" ),
[]byte ("inert" ),
[]byte ("inputmode" ),
[]byte ("is" ),
[]byte ("itemid" ),
[]byte ("itemprop" ),
[]byte ("itemref" ),
[]byte ("itemscope" ),
[]byte ("itemtype" ),
[]byte ("lang" ),
[]byte ("part" ),
[]byte ("role" ),
[]byte ("slot" ),
[]byte ("spellcheck" ),
[]byte ("style" ),
[]byte ("tabindex" ),
[]byte ("title" ),
[]byte ("translate" ),
)
func (r *Renderer ) renderDocument (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
return ast .WalkContinue , nil
}
var HeadingAttributeFilter = GlobalAttributeFilter
func (r *Renderer ) renderHeading (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .Heading )
if entering {
_, _ = w .WriteString ("<h" )
_ = w .WriteByte ("0123456" [n .Level ])
if n .Attributes () != nil {
RenderAttributes (w , node , HeadingAttributeFilter )
}
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("</h" )
_ = w .WriteByte ("0123456" [n .Level ])
_, _ = w .WriteString (">\n" )
}
return ast .WalkContinue , nil
}
var BlockquoteAttributeFilter = GlobalAttributeFilter .Extend (
[]byte ("cite" ),
)
func (r *Renderer ) renderBlockquote (
w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if entering {
if n .Attributes () != nil {
_, _ = w .WriteString ("<blockquote" )
RenderAttributes (w , n , BlockquoteAttributeFilter )
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("<blockquote>\n" )
}
} else {
_, _ = w .WriteString ("</blockquote>\n" )
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderCodeBlock (w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if entering {
_, _ = w .WriteString ("<pre><code>" )
r .writeLines (w , source , n )
} else {
_, _ = w .WriteString ("</code></pre>\n" )
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderFencedCodeBlock (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .FencedCodeBlock )
if entering {
_, _ = w .WriteString ("<pre><code" )
language := n .Language (source )
if language != nil {
_, _ = w .WriteString (" class=\"language-" )
r .Writer .Write (w , language )
_, _ = w .WriteString ("\"" )
}
_ = w .WriteByte ('>' )
r .writeLines (w , source , n )
} else {
_, _ = w .WriteString ("</code></pre>\n" )
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderHTMLBlock (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .HTMLBlock )
if entering {
if r .Unsafe {
l := n .Lines ().Len ()
for i := 0 ; i < l ; i ++ {
line := n .Lines ().At (i )
r .Writer .SecureWrite (w , line .Value (source ))
}
} else {
_, _ = w .WriteString ("<!-- raw HTML omitted -->\n" )
}
} else {
if n .HasClosure () {
if r .Unsafe {
closure := n .ClosureLine
r .Writer .SecureWrite (w , closure .Value (source ))
} else {
_, _ = w .WriteString ("<!-- raw HTML omitted -->\n" )
}
}
}
return ast .WalkContinue , nil
}
var ListAttributeFilter = GlobalAttributeFilter .Extend (
[]byte ("start" ),
[]byte ("reversed" ),
[]byte ("type" ),
)
func (r *Renderer ) renderList (w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .List )
tag := "ul"
if n .IsOrdered () {
tag = "ol"
}
if entering {
_ = w .WriteByte ('<' )
_, _ = w .WriteString (tag )
if n .IsOrdered () && n .Start != 1 {
_, _ = fmt .Fprintf (w , " start=\"%d\"" , n .Start )
}
if n .Attributes () != nil {
RenderAttributes (w , n , ListAttributeFilter )
}
_, _ = w .WriteString (">\n" )
} else {
_, _ = w .WriteString ("</" )
_, _ = w .WriteString (tag )
_, _ = w .WriteString (">\n" )
}
return ast .WalkContinue , nil
}
var ListItemAttributeFilter = GlobalAttributeFilter .Extend (
[]byte ("value" ),
)
func (r *Renderer ) renderListItem (w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if entering {
if n .Attributes () != nil {
_, _ = w .WriteString ("<li" )
RenderAttributes (w , n , ListItemAttributeFilter )
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("<li>" )
}
fc := n .FirstChild ()
if fc != nil {
if _ , ok := fc .(*ast .TextBlock ); !ok {
_ = w .WriteByte ('\n' )
}
}
} else {
_, _ = w .WriteString ("</li>\n" )
}
return ast .WalkContinue , nil
}
var ParagraphAttributeFilter = GlobalAttributeFilter
func (r *Renderer ) renderParagraph (w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if entering {
if n .Attributes () != nil {
_, _ = w .WriteString ("<p" )
RenderAttributes (w , n , ParagraphAttributeFilter )
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("<p>" )
}
} else {
_, _ = w .WriteString ("</p>\n" )
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderTextBlock (w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if !entering {
if n .NextSibling () != nil && n .FirstChild () != nil {
_ = w .WriteByte ('\n' )
}
}
return ast .WalkContinue , nil
}
var ThematicAttributeFilter = GlobalAttributeFilter .Extend (
[]byte ("align" ),
[]byte ("color" ),
[]byte ("noshade" ),
[]byte ("size" ),
[]byte ("width" ),
)
func (r *Renderer ) renderThematicBreak (
w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if !entering {
return ast .WalkContinue , nil
}
_, _ = w .WriteString ("<hr" )
if n .Attributes () != nil {
RenderAttributes (w , n , ThematicAttributeFilter )
}
if r .XHTML {
_, _ = w .WriteString (" />\n" )
} else {
_, _ = w .WriteString (">\n" )
}
return ast .WalkContinue , nil
}
var LinkAttributeFilter = GlobalAttributeFilter .Extend (
[]byte ("download" ),
[]byte ("hreflang" ),
[]byte ("media" ),
[]byte ("ping" ),
[]byte ("referrerpolicy" ),
[]byte ("rel" ),
[]byte ("shape" ),
[]byte ("target" ),
)
func (r *Renderer ) renderAutoLink (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .AutoLink )
if !entering {
return ast .WalkContinue , nil
}
_, _ = w .WriteString (`<a href="` )
url := n .URL (source )
label := n .Label (source )
if n .AutoLinkType == ast .AutoLinkEmail && !bytes .HasPrefix (bytes .ToLower (url ), []byte ("mailto:" )) {
_, _ = w .WriteString ("mailto:" )
}
_, _ = w .Write (util .EscapeHTML (util .URLEscape (url , false )))
if n .Attributes () != nil {
_ = w .WriteByte ('"' )
RenderAttributes (w , n , LinkAttributeFilter )
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString (`">` )
}
_, _ = w .Write (util .EscapeHTML (label ))
_, _ = w .WriteString (`</a>` )
return ast .WalkContinue , nil
}
var CodeAttributeFilter = GlobalAttributeFilter
func (r *Renderer ) renderCodeSpan (w util .BufWriter , source []byte , n ast .Node , entering bool ) (ast .WalkStatus , error ) {
if entering {
if n .Attributes () != nil {
_, _ = w .WriteString ("<code" )
RenderAttributes (w , n , CodeAttributeFilter )
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("<code>" )
}
for c := n .FirstChild (); c != nil ; c = c .NextSibling () {
segment := c .(*ast .Text ).Segment
value := segment .Value (source )
if bytes .HasSuffix (value , []byte ("\n" )) {
r .Writer .RawWrite (w , value [:len (value )-1 ])
r .Writer .RawWrite (w , []byte (" " ))
} else {
r .Writer .RawWrite (w , value )
}
}
return ast .WalkSkipChildren , nil
}
_, _ = w .WriteString ("</code>" )
return ast .WalkContinue , nil
}
var EmphasisAttributeFilter = GlobalAttributeFilter
func (r *Renderer ) renderEmphasis (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .Emphasis )
tag := "em"
if n .Level == 2 {
tag = "strong"
}
if entering {
_ = w .WriteByte ('<' )
_, _ = w .WriteString (tag )
if n .Attributes () != nil {
RenderAttributes (w , n , EmphasisAttributeFilter )
}
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("</" )
_, _ = w .WriteString (tag )
_ = w .WriteByte ('>' )
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderLink (w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
n := node .(*ast .Link )
if entering {
_, _ = w .WriteString ("<a href=\"" )
if r .Unsafe || !IsDangerousURL (n .Destination ) {
_, _ = w .Write (util .EscapeHTML (util .URLEscape (n .Destination , true )))
}
_ = w .WriteByte ('"' )
if n .Title != nil {
_, _ = w .WriteString (` title="` )
r .Writer .Write (w , n .Title )
_ = w .WriteByte ('"' )
}
if n .Attributes () != nil {
RenderAttributes (w , n , LinkAttributeFilter )
}
_ = w .WriteByte ('>' )
} else {
_, _ = w .WriteString ("</a>" )
}
return ast .WalkContinue , nil
}
var ImageAttributeFilter = GlobalAttributeFilter .Extend (
[]byte ("align" ),
[]byte ("border" ),
[]byte ("crossorigin" ),
[]byte ("decoding" ),
[]byte ("height" ),
[]byte ("importance" ),
[]byte ("intrinsicsize" ),
[]byte ("ismap" ),
[]byte ("loading" ),
[]byte ("referrerpolicy" ),
[]byte ("sizes" ),
[]byte ("srcset" ),
[]byte ("usemap" ),
[]byte ("width" ),
)
func (r *Renderer ) renderImage (w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
if !entering {
return ast .WalkContinue , nil
}
n := node .(*ast .Image )
_, _ = w .WriteString ("<img src=\"" )
if r .Unsafe || !IsDangerousURL (n .Destination ) {
_, _ = w .Write (util .EscapeHTML (util .URLEscape (n .Destination , true )))
}
_, _ = w .WriteString (`" alt="` )
r .renderAttribute (w , source , n )
_ = w .WriteByte ('"' )
if n .Title != nil {
_, _ = w .WriteString (` title="` )
r .Writer .Write (w , n .Title )
_ = w .WriteByte ('"' )
}
if n .Attributes () != nil {
RenderAttributes (w , n , ImageAttributeFilter )
}
if r .XHTML {
_, _ = w .WriteString (" />" )
} else {
_, _ = w .WriteString (">" )
}
return ast .WalkSkipChildren , nil
}
func (r *Renderer ) renderRawHTML (
w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
if !entering {
return ast .WalkSkipChildren , nil
}
if r .Unsafe {
n := node .(*ast .RawHTML )
l := n .Segments .Len ()
for i := 0 ; i < l ; i ++ {
segment := n .Segments .At (i )
_, _ = w .Write (segment .Value (source ))
}
return ast .WalkSkipChildren , nil
}
_, _ = w .WriteString ("<!-- raw HTML omitted -->" )
return ast .WalkSkipChildren , nil
}
func (r *Renderer ) renderText (w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
if !entering {
return ast .WalkContinue , nil
}
n := node .(*ast .Text )
segment := n .Segment
if n .IsRaw () {
r .Writer .RawWrite (w , segment .Value (source ))
} else {
value := segment .Value (source )
r .Writer .Write (w , value )
if n .HardLineBreak () || (n .SoftLineBreak () && r .HardWraps ) {
if r .XHTML {
_, _ = w .WriteString ("<br />\n" )
} else {
_, _ = w .WriteString ("<br>\n" )
}
} else if n .SoftLineBreak () {
if r .EastAsianLineBreaks != EastAsianLineBreaksNone && len (value ) != 0 {
sibling := node .NextSibling ()
if sibling != nil && sibling .Kind () == ast .KindText {
if siblingText := sibling .(*ast .Text ).Text (source ); len (siblingText ) != 0 {
thisLastRune := util .ToRune (value , len (value )-1 )
siblingFirstRune , _ := utf8 .DecodeRune (siblingText )
if r .EastAsianLineBreaks .softLineBreak (thisLastRune , siblingFirstRune ) {
_ = w .WriteByte ('\n' )
}
}
}
} else {
_ = w .WriteByte ('\n' )
}
}
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderString (w util .BufWriter , source []byte , node ast .Node , entering bool ) (ast .WalkStatus , error ) {
if !entering {
return ast .WalkContinue , nil
}
n := node .(*ast .String )
if n .IsCode () {
_, _ = w .Write (n .Value )
} else {
if n .IsRaw () {
r .Writer .RawWrite (w , n .Value )
} else {
r .Writer .Write (w , n .Value )
}
}
return ast .WalkContinue , nil
}
func (r *Renderer ) renderAttribute (w util .BufWriter , source []byte , n ast .Node ) {
for c := n .FirstChild (); c != nil ; c = c .NextSibling () {
if s , ok := c .(*ast .String ); ok {
_, _ = r .renderString (w , source , s , true )
} else if t , ok := c .(*ast .String ); ok {
_, _ = r .renderText (w , source , t , true )
} else if !c .HasChildren () {
r .Writer .Write (w , c .Text (source ))
if t , ok := c .(*ast .Text ); ok && t .SoftLineBreak () {
_ = w .WriteByte ('\n' )
}
} else {
r .renderAttribute (w , source , c )
}
}
}
var dataPrefix = []byte ("data-" )
func RenderAttributes (w util .BufWriter , node ast .Node , filter util .BytesFilter ) {
for _ , attr := range node .Attributes () {
if filter != nil && !filter .Contains (attr .Name ) {
if !bytes .HasPrefix (attr .Name , dataPrefix ) {
continue
}
}
_, _ = w .WriteString (" " )
_, _ = w .Write (attr .Name )
_, _ = w .WriteString (`="` )
var value []byte
switch typed := attr .Value .(type ) {
case []byte :
value = typed
case string :
value = util .StringToReadOnlyBytes (typed )
}
_, _ = w .Write (util .EscapeHTML (value ))
_ = w .WriteByte ('"' )
}
}
type Writer interface {
Write (writer util .BufWriter , source []byte )
RawWrite (writer util .BufWriter , source []byte )
SecureWrite (writer util .BufWriter , source []byte )
}
var replacementCharacter = []byte ("\ufffd" )
type WriterConfig struct {
EscapedSpace bool
}
type WriterOption func (*WriterConfig )
func WithEscapedSpace () WriterOption {
return func (c *WriterConfig ) {
c .EscapedSpace = true
}
}
type defaultWriter struct {
WriterConfig
}
func NewWriter (opts ...WriterOption ) Writer {
w := &defaultWriter {}
for _ , opt := range opts {
opt (&w .WriterConfig )
}
return w
}
func escapeRune(writer util .BufWriter , r rune ) {
if r < 256 {
v := util .EscapeHTMLByte (byte (r ))
if v != nil {
_, _ = writer .Write (v )
return
}
}
_, _ = writer .WriteRune (util .ToValidRune (r ))
}
func (d *defaultWriter ) SecureWrite (writer util .BufWriter , source []byte ) {
n := 0
l := len (source )
for i := 0 ; i < l ; i ++ {
if source [i ] == '\u0000' {
_, _ = writer .Write (source [i -n : i ])
n = 0
_, _ = writer .Write (replacementCharacter )
continue
}
n ++
}
if n != 0 {
_, _ = writer .Write (source [l -n :])
}
}
func (d *defaultWriter ) RawWrite (writer util .BufWriter , source []byte ) {
n := 0
l := len (source )
for i := 0 ; i < l ; i ++ {
v := util .EscapeHTMLByte (source [i ])
if v != nil {
_, _ = writer .Write (source [i -n : i ])
n = 0
_, _ = writer .Write (v )
continue
}
n ++
}
if n != 0 {
_, _ = writer .Write (source [l -n :])
}
}
func (d *defaultWriter ) Write (writer util .BufWriter , source []byte ) {
escaped := false
var ok bool
limit := len (source )
n := 0
for i := 0 ; i < limit ; i ++ {
c := source [i ]
if escaped {
if util .IsPunct (c ) {
d .RawWrite (writer , source [n :i -1 ])
n = i
escaped = false
continue
}
if d .EscapedSpace && c == ' ' {
d .RawWrite (writer , source [n :i -1 ])
n = i + 1
escaped = false
continue
}
}
if c == '\x00' {
d .RawWrite (writer , source [n :i ])
d .RawWrite (writer , replacementCharacter )
n = i + 1
escaped = false
continue
}
if c == '&' {
pos := i
next := i + 1
if next < limit && source [next ] == '#' {
nnext := next + 1
if nnext < limit {
nc := source [nnext ]
if nnext < limit && nc == 'x' || nc == 'X' {
start := nnext + 1
i , ok = util .ReadWhile (source , [2 ]int {start , limit }, util .IsHexDecimal )
if ok && i < limit && source [i ] == ';' && i -start < 7 {
v , _ := strconv .ParseUint (util .BytesToReadOnlyString (source [start :i ]), 16 , 32 )
d .RawWrite (writer , source [n :pos ])
n = i + 1
escapeRune (writer , rune (v ))
continue
}
} else if nc >= '0' && nc <= '9' {
start := nnext
i , ok = util .ReadWhile (source , [2 ]int {start , limit }, util .IsNumeric )
if ok && i < limit && i -start < 8 && source [i ] == ';' {
v , _ := strconv .ParseUint (util .BytesToReadOnlyString (source [start :i ]), 10 , 32 )
d .RawWrite (writer , source [n :pos ])
n = i + 1
escapeRune (writer , rune (v ))
continue
}
}
}
} else {
start := next
i , ok = util .ReadWhile (source , [2 ]int {start , limit }, util .IsAlphaNumeric )
if ok && i < limit && source [i ] == ';' {
name := util .BytesToReadOnlyString (source [start :i ])
entity , ok := util .LookUpHTML5EntityByName (name )
if ok {
d .RawWrite (writer , source [n :pos ])
n = i + 1
d .RawWrite (writer , entity .Characters )
continue
}
}
}
i = next - 1
}
if c == '\\' {
escaped = true
continue
}
escaped = false
}
d .RawWrite (writer , source [n :])
}
var DefaultWriter = NewWriter ()
var bDataImage = []byte ("data:image/" )
var bPng = []byte ("png;" )
var bGif = []byte ("gif;" )
var bJpeg = []byte ("jpeg;" )
var bWebp = []byte ("webp;" )
var bSvg = []byte ("svg+xml;" )
var bJs = []byte ("javascript:" )
var bVb = []byte ("vbscript:" )
var bFile = []byte ("file:" )
var bData = []byte ("data:" )
func hasPrefix(s , prefix []byte ) bool {
return len (s ) >= len (prefix ) && bytes .Equal (bytes .ToLower (s [0 :len (prefix )]), bytes .ToLower (prefix ))
}
func IsDangerousURL (url []byte ) bool {
if hasPrefix (url , bDataImage ) && len (url ) >= 11 {
v := url [11 :]
if hasPrefix (v , bPng ) || hasPrefix (v , bGif ) ||
hasPrefix (v , bJpeg ) || hasPrefix (v , bWebp ) ||
hasPrefix (v , bSvg ) {
return false
}
return true
}
return hasPrefix (url , bJs ) || hasPrefix (url , bVb ) ||
hasPrefix (url , bFile ) || hasPrefix (url , bData )
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .