// Package html implements renderer that outputs HTMLs.
package html import ( ) // A Config struct has configurations for the HTML based renderers. type Config struct { Writer Writer HardWraps bool EastAsianLineBreaks EastAsianLineBreaks XHTML bool Unsafe bool } // NewConfig returns a new Config with defaults. func () Config { return Config{ Writer: DefaultWriter, HardWraps: false, EastAsianLineBreaks: EastAsianLineBreaksNone, XHTML: false, Unsafe: false, } } // SetOption implements renderer.NodeRenderer.SetOption. func ( *Config) ( renderer.OptionName, interface{}) { switch { case optHardWraps: .HardWraps = .(bool) case optEastAsianLineBreaks: .EastAsianLineBreaks = .(EastAsianLineBreaks) case optXHTML: .XHTML = .(bool) case optUnsafe: .Unsafe = .(bool) case optTextWriter: .Writer = .(Writer) } } // An Option interface sets options for HTML based renderers. type Option interface { SetHTMLOption(*Config) } // TextWriter is an option name used in WithWriter. const optTextWriter renderer.OptionName = "Writer" type withWriter struct { value Writer } func ( *withWriter) ( *renderer.Config) { .Options[optTextWriter] = .value } func ( *withWriter) ( *Config) { .Writer = .value } // WithWriter is a functional option that allow you to set the given writer to // the renderer. func ( Writer) interface { renderer.Option Option } { return &withWriter{} } // HardWraps is an option name used in WithHardWraps. const optHardWraps renderer.OptionName = "HardWraps" type withHardWraps struct { } func ( *withHardWraps) ( *renderer.Config) { .Options[optHardWraps] = true } func ( *withHardWraps) ( *Config) { .HardWraps = true } // WithHardWraps is a functional option that indicates whether softline breaks // should be rendered as '<br>'. func () interface { renderer.Option Option } { return &withHardWraps{} } // EastAsianLineBreaks is an option name used in WithEastAsianLineBreaks. const optEastAsianLineBreaks renderer.OptionName = "EastAsianLineBreaks" // A EastAsianLineBreaks is a style of east asian line breaks. type EastAsianLineBreaks int const ( //EastAsianLineBreaksNone renders line breaks as it is. EastAsianLineBreaksNone EastAsianLineBreaks = iota // EastAsianLineBreaksSimple follows east_asian_line_breaks in Pandoc. EastAsianLineBreaksSimple // EastAsianLineBreaksCSS3Draft follows CSS text level3 "Segment Break Transformation Rules" with some enhancements. EastAsianLineBreaksCSS3Draft ) func ( EastAsianLineBreaks) ( rune, rune) bool { switch { case EastAsianLineBreaksNone: return false case EastAsianLineBreaksSimple: return !(util.IsEastAsianWideRune() && util.IsEastAsianWideRune()) case EastAsianLineBreaksCSS3Draft: return eastAsianLineBreaksCSS3DraftSoftLineBreak(, ) } return false } func eastAsianLineBreaksCSS3DraftSoftLineBreak( rune, rune) bool { // Implements CSS text level3 Segment Break Transformation Rules with some enhancements. // References: // - https://www.w3.org/TR/2020/WD-css-text-3-20200429/#line-break-transform // - https://github.com/w3c/csswg-drafts/issues/5086 // Rule1: // If the character immediately before or immediately after the segment break is // the zero-width space character (U+200B), then the break is removed, leaving behind the zero-width space. if == '\u200B' || == '\u200B' { return false } // Rule2: // Otherwise, if the East Asian Width property of both the character before and after the segment break is // F, W, or H (not A), and neither side is Hangul, then the segment break is removed. := util.EastAsianWidth() := util.EastAsianWidth() if ( == "F" || == "W" || == "H") && ( == "F" || == "W" || == "H") { return unicode.Is(unicode.Hangul, ) || unicode.Is(unicode.Hangul, ) } // Rule3: // Otherwise, if either the character before or after the segment break belongs to // the space-discarding character set and it is a Unicode Punctuation (P*) or U+3000, // then the segment break is removed. if util.IsSpaceDiscardingUnicodeRune() || unicode.IsPunct() || == '\u3000' || util.IsSpaceDiscardingUnicodeRune() || unicode.IsPunct() || == '\u3000' { return false } // Rule4: // Otherwise, the segment break is converted to a space (U+0020). return true } type withEastAsianLineBreaks struct { eastAsianLineBreaksStyle EastAsianLineBreaks } func ( *withEastAsianLineBreaks) ( *renderer.Config) { .Options[optEastAsianLineBreaks] = .eastAsianLineBreaksStyle } func ( *withEastAsianLineBreaks) ( *Config) { .EastAsianLineBreaks = .eastAsianLineBreaksStyle } // WithEastAsianLineBreaks is a functional option that indicates whether softline breaks // between east asian wide characters should be ignored. func ( EastAsianLineBreaks) interface { renderer.Option Option } { return &withEastAsianLineBreaks{} } // XHTML is an option name used in WithXHTML. const optXHTML renderer.OptionName = "XHTML" type withXHTML struct { } func ( *withXHTML) ( *renderer.Config) { .Options[optXHTML] = true } func ( *withXHTML) ( *Config) { .XHTML = true } // WithXHTML is a functional option indicates that nodes should be rendered in // xhtml instead of HTML5. func () interface { Option renderer.Option } { return &withXHTML{} } // Unsafe is an option name used in WithUnsafe. const optUnsafe renderer.OptionName = "Unsafe" type withUnsafe struct { } func ( *withUnsafe) ( *renderer.Config) { .Options[optUnsafe] = true } func ( *withUnsafe) ( *Config) { .Unsafe = true } // WithUnsafe is a functional option that renders dangerous contents // (raw htmls and potentially dangerous links) as it is. func () interface { renderer.Option Option } { return &withUnsafe{} } // A Renderer struct is an implementation of renderer.NodeRenderer that renders // nodes as (X)HTML. type Renderer struct { Config } // NewRenderer returns a new Renderer with given options. func ( ...Option) renderer.NodeRenderer { := &Renderer{ Config: NewConfig(), } for , := range { .SetHTMLOption(&.Config) } return } // RegisterFuncs implements NodeRenderer.RegisterFuncs . func ( *Renderer) ( renderer.NodeRendererFuncRegisterer) { // blocks .Register(ast.KindDocument, .renderDocument) .Register(ast.KindHeading, .renderHeading) .Register(ast.KindBlockquote, .renderBlockquote) .Register(ast.KindCodeBlock, .renderCodeBlock) .Register(ast.KindFencedCodeBlock, .renderFencedCodeBlock) .Register(ast.KindHTMLBlock, .renderHTMLBlock) .Register(ast.KindList, .renderList) .Register(ast.KindListItem, .renderListItem) .Register(ast.KindParagraph, .renderParagraph) .Register(ast.KindTextBlock, .renderTextBlock) .Register(ast.KindThematicBreak, .renderThematicBreak) // inlines .Register(ast.KindAutoLink, .renderAutoLink) .Register(ast.KindCodeSpan, .renderCodeSpan) .Register(ast.KindEmphasis, .renderEmphasis) .Register(ast.KindImage, .renderImage) .Register(ast.KindLink, .renderLink) .Register(ast.KindRawHTML, .renderRawHTML) .Register(ast.KindText, .renderText) .Register(ast.KindString, .renderString) } func ( *Renderer) ( util.BufWriter, []byte, ast.Node) { := .Lines().Len() for := 0; < ; ++ { := .Lines().At() .Writer.RawWrite(, .Value()) } } // GlobalAttributeFilter defines attribute names which any elements can have. 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 ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { // nothing to do return ast.WalkContinue, nil } // HeadingAttributeFilter defines attribute names which heading elements can have. var HeadingAttributeFilter = GlobalAttributeFilter func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.Heading) if { _, _ = .WriteString("<h") _ = .WriteByte("0123456"[.Level]) if .Attributes() != nil { RenderAttributes(, , HeadingAttributeFilter) } _ = .WriteByte('>') } else { _, _ = .WriteString("</h") _ = .WriteByte("0123456"[.Level]) _, _ = .WriteString(">\n") } return ast.WalkContinue, nil } // BlockquoteAttributeFilter defines attribute names which blockquote elements can have. var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend( []byte("cite"), ) func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if { if .Attributes() != nil { _, _ = .WriteString("<blockquote") RenderAttributes(, , BlockquoteAttributeFilter) _ = .WriteByte('>') } else { _, _ = .WriteString("<blockquote>\n") } } else { _, _ = .WriteString("</blockquote>\n") } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if { _, _ = .WriteString("<pre><code>") .writeLines(, , ) } else { _, _ = .WriteString("</code></pre>\n") } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.FencedCodeBlock) if { _, _ = .WriteString("<pre><code") := .Language() if != nil { _, _ = .WriteString(" class=\"language-") .Writer.Write(, ) _, _ = .WriteString("\"") } _ = .WriteByte('>') .writeLines(, , ) } else { _, _ = .WriteString("</code></pre>\n") } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.HTMLBlock) if { if .Unsafe { := .Lines().Len() for := 0; < ; ++ { := .Lines().At() .Writer.SecureWrite(, .Value()) } } else { _, _ = .WriteString("<!-- raw HTML omitted -->\n") } } else { if .HasClosure() { if .Unsafe { := .ClosureLine .Writer.SecureWrite(, .Value()) } else { _, _ = .WriteString("<!-- raw HTML omitted -->\n") } } } return ast.WalkContinue, nil } // ListAttributeFilter defines attribute names which list elements can have. var ListAttributeFilter = GlobalAttributeFilter.Extend( []byte("start"), []byte("reversed"), []byte("type"), ) func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.List) := "ul" if .IsOrdered() { = "ol" } if { _ = .WriteByte('<') _, _ = .WriteString() if .IsOrdered() && .Start != 1 { _, _ = fmt.Fprintf(, " start=\"%d\"", .Start) } if .Attributes() != nil { RenderAttributes(, , ListAttributeFilter) } _, _ = .WriteString(">\n") } else { _, _ = .WriteString("</") _, _ = .WriteString() _, _ = .WriteString(">\n") } return ast.WalkContinue, nil } // ListItemAttributeFilter defines attribute names which list item elements can have. var ListItemAttributeFilter = GlobalAttributeFilter.Extend( []byte("value"), ) func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if { if .Attributes() != nil { _, _ = .WriteString("<li") RenderAttributes(, , ListItemAttributeFilter) _ = .WriteByte('>') } else { _, _ = .WriteString("<li>") } := .FirstChild() if != nil { if , := .(*ast.TextBlock); ! { _ = .WriteByte('\n') } } } else { _, _ = .WriteString("</li>\n") } return ast.WalkContinue, nil } // ParagraphAttributeFilter defines attribute names which paragraph elements can have. var ParagraphAttributeFilter = GlobalAttributeFilter func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if { if .Attributes() != nil { _, _ = .WriteString("<p") RenderAttributes(, , ParagraphAttributeFilter) _ = .WriteByte('>') } else { _, _ = .WriteString("<p>") } } else { _, _ = .WriteString("</p>\n") } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if ! { if .NextSibling() != nil && .FirstChild() != nil { _ = .WriteByte('\n') } } return ast.WalkContinue, nil } // ThematicAttributeFilter defines attribute names which hr elements can have. var ThematicAttributeFilter = GlobalAttributeFilter.Extend( []byte("align"), // [Deprecated] []byte("color"), // [Not Standardized] []byte("noshade"), // [Deprecated] []byte("size"), // [Deprecated] []byte("width"), // [Deprecated] ) func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if ! { return ast.WalkContinue, nil } _, _ = .WriteString("<hr") if .Attributes() != nil { RenderAttributes(, , ThematicAttributeFilter) } if .XHTML { _, _ = .WriteString(" />\n") } else { _, _ = .WriteString(">\n") } return ast.WalkContinue, nil } // LinkAttributeFilter defines attribute names which link elements can have. var LinkAttributeFilter = GlobalAttributeFilter.Extend( []byte("download"), // []byte("href"), []byte("hreflang"), []byte("media"), []byte("ping"), []byte("referrerpolicy"), []byte("rel"), []byte("shape"), []byte("target"), ) func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.AutoLink) if ! { return ast.WalkContinue, nil } _, _ = .WriteString(`<a href="`) := .URL() := .Label() if .AutoLinkType == ast.AutoLinkEmail && !bytes.HasPrefix(bytes.ToLower(), []byte("mailto:")) { _, _ = .WriteString("mailto:") } _, _ = .Write(util.EscapeHTML(util.URLEscape(, false))) if .Attributes() != nil { _ = .WriteByte('"') RenderAttributes(, , LinkAttributeFilter) _ = .WriteByte('>') } else { _, _ = .WriteString(`">`) } _, _ = .Write(util.EscapeHTML()) _, _ = .WriteString(`</a>`) return ast.WalkContinue, nil } // CodeAttributeFilter defines attribute names which code elements can have. var CodeAttributeFilter = GlobalAttributeFilter func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if { if .Attributes() != nil { _, _ = .WriteString("<code") RenderAttributes(, , CodeAttributeFilter) _ = .WriteByte('>') } else { _, _ = .WriteString("<code>") } for := .FirstChild(); != nil; = .NextSibling() { := .(*ast.Text).Segment := .Value() if bytes.HasSuffix(, []byte("\n")) { .Writer.RawWrite(, [:len()-1]) .Writer.RawWrite(, []byte(" ")) } else { .Writer.RawWrite(, ) } } return ast.WalkSkipChildren, nil } _, _ = .WriteString("</code>") return ast.WalkContinue, nil } // EmphasisAttributeFilter defines attribute names which emphasis elements can have. var EmphasisAttributeFilter = GlobalAttributeFilter func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.Emphasis) := "em" if .Level == 2 { = "strong" } if { _ = .WriteByte('<') _, _ = .WriteString() if .Attributes() != nil { RenderAttributes(, , EmphasisAttributeFilter) } _ = .WriteByte('>') } else { _, _ = .WriteString("</") _, _ = .WriteString() _ = .WriteByte('>') } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { := .(*ast.Link) if { _, _ = .WriteString("<a href=\"") if .Unsafe || !IsDangerousURL(.Destination) { _, _ = .Write(util.EscapeHTML(util.URLEscape(.Destination, true))) } _ = .WriteByte('"') if .Title != nil { _, _ = .WriteString(` title="`) .Writer.Write(, .Title) _ = .WriteByte('"') } if .Attributes() != nil { RenderAttributes(, , LinkAttributeFilter) } _ = .WriteByte('>') } else { _, _ = .WriteString("</a>") } return ast.WalkContinue, nil } // ImageAttributeFilter defines attribute names which image elements can have. 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 ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if ! { return ast.WalkContinue, nil } := .(*ast.Image) _, _ = .WriteString("<img src=\"") if .Unsafe || !IsDangerousURL(.Destination) { _, _ = .Write(util.EscapeHTML(util.URLEscape(.Destination, true))) } _, _ = .WriteString(`" alt="`) .renderAttribute(, , ) _ = .WriteByte('"') if .Title != nil { _, _ = .WriteString(` title="`) .Writer.Write(, .Title) _ = .WriteByte('"') } if .Attributes() != nil { RenderAttributes(, , ImageAttributeFilter) } if .XHTML { _, _ = .WriteString(" />") } else { _, _ = .WriteString(">") } return ast.WalkSkipChildren, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if ! { return ast.WalkSkipChildren, nil } if .Unsafe { := .(*ast.RawHTML) := .Segments.Len() for := 0; < ; ++ { := .Segments.At() _, _ = .Write(.Value()) } return ast.WalkSkipChildren, nil } _, _ = .WriteString("<!-- raw HTML omitted -->") return ast.WalkSkipChildren, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if ! { return ast.WalkContinue, nil } := .(*ast.Text) := .Segment if .IsRaw() { .Writer.RawWrite(, .Value()) } else { := .Value() .Writer.Write(, ) if .HardLineBreak() || (.SoftLineBreak() && .HardWraps) { if .XHTML { _, _ = .WriteString("<br />\n") } else { _, _ = .WriteString("<br>\n") } } else if .SoftLineBreak() { if .EastAsianLineBreaks != EastAsianLineBreaksNone && len() != 0 { := .NextSibling() if != nil && .Kind() == ast.KindText { if := .(*ast.Text).Text(); len() != 0 { := util.ToRune(, len()-1) , := utf8.DecodeRune() if .EastAsianLineBreaks.softLineBreak(, ) { _ = .WriteByte('\n') } } } } else { _ = .WriteByte('\n') } } } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node, bool) (ast.WalkStatus, error) { if ! { return ast.WalkContinue, nil } := .(*ast.String) if .IsCode() { _, _ = .Write(.Value) } else { if .IsRaw() { .Writer.RawWrite(, .Value) } else { .Writer.Write(, .Value) } } return ast.WalkContinue, nil } func ( *Renderer) ( util.BufWriter, []byte, ast.Node) { for := .FirstChild(); != nil; = .NextSibling() { if , := .(*ast.String); { _, _ = .renderString(, , , true) } else if , := .(*ast.String); { _, _ = .renderText(, , , true) } else if !.HasChildren() { .Writer.Write(, .Text()) if , := .(*ast.Text); && .SoftLineBreak() { _ = .WriteByte('\n') } } else { .(, , ) } } } var dataPrefix = []byte("data-") // RenderAttributes renders given node's attributes. // You can specify attribute names to render by the filter. // If filter is nil, RenderAttributes renders all attributes. func ( util.BufWriter, ast.Node, util.BytesFilter) { for , := range .Attributes() { if != nil && !.Contains(.Name) { if !bytes.HasPrefix(.Name, dataPrefix) { continue } } _, _ = .WriteString(" ") _, _ = .Write(.Name) _, _ = .WriteString(`="`) // TODO: convert numeric values to strings var []byte switch typed := .Value.(type) { case []byte: = case string: = util.StringToReadOnlyBytes() } _, _ = .Write(util.EscapeHTML()) _ = .WriteByte('"') } } // A Writer interface writes textual contents to a writer. type Writer interface { // Write writes the given source to writer with resolving references and unescaping // backslash escaped characters. Write(writer util.BufWriter, source []byte) // RawWrite writes the given source to writer without resolving references and // unescaping backslash escaped characters. RawWrite(writer util.BufWriter, source []byte) // SecureWrite writes the given source to writer with replacing insecure characters. SecureWrite(writer util.BufWriter, source []byte) } var replacementCharacter = []byte("\ufffd") // A WriterConfig struct has configurations for the HTML based writers. type WriterConfig struct { // EscapedSpace is an option that indicates that a '\' escaped half-space(0x20) should not be rendered. EscapedSpace bool } // A WriterOption interface sets options for HTML based writers. type WriterOption func(*WriterConfig) // WithEscapedSpace is a WriterOption indicates that a '\' escaped half-space(0x20) should not be rendered. func () WriterOption { return func( *WriterConfig) { .EscapedSpace = true } } type defaultWriter struct { WriterConfig } // NewWriter returns a new Writer. func ( ...WriterOption) Writer { := &defaultWriter{} for , := range { (&.WriterConfig) } return } func escapeRune( util.BufWriter, rune) { if < 256 { := util.EscapeHTMLByte(byte()) if != nil { _, _ = .Write() return } } _, _ = .WriteRune(util.ToValidRune()) } func ( *defaultWriter) ( util.BufWriter, []byte) { := 0 := len() for := 0; < ; ++ { if [] == '\u0000' { _, _ = .Write([- : ]) = 0 _, _ = .Write(replacementCharacter) continue } ++ } if != 0 { _, _ = .Write([-:]) } } func ( *defaultWriter) ( util.BufWriter, []byte) { := 0 := len() for := 0; < ; ++ { := util.EscapeHTMLByte([]) if != nil { _, _ = .Write([- : ]) = 0 _, _ = .Write() continue } ++ } if != 0 { _, _ = .Write([-:]) } } func ( *defaultWriter) ( util.BufWriter, []byte) { := false var bool := len() := 0 for := 0; < ; ++ { := [] if { if util.IsPunct() { .RawWrite(, [:-1]) = = false continue } if .EscapedSpace && == ' ' { .RawWrite(, [:-1]) = + 1 = false continue } } if == '\x00' { .RawWrite(, [:]) .RawWrite(, replacementCharacter) = + 1 = false continue } if == '&' { := := + 1 if < && [] == '#' { := + 1 if < { := [] // code point like #x22; if < && == 'x' || == 'X' { := + 1 , = util.ReadWhile(, [2]int{, }, util.IsHexDecimal) if && < && [] == ';' && - < 7 { , := strconv.ParseUint(util.BytesToReadOnlyString([:]), 16, 32) .RawWrite(, [:]) = + 1 escapeRune(, rune()) continue } // code point like #1234; } else if >= '0' && <= '9' { := , = util.ReadWhile(, [2]int{, }, util.IsNumeric) if && < && - < 8 && [] == ';' { , := strconv.ParseUint(util.BytesToReadOnlyString([:]), 10, 32) .RawWrite(, [:]) = + 1 escapeRune(, rune()) continue } } } } else { := , = util.ReadWhile(, [2]int{, }, util.IsAlphaNumeric) // entity reference if && < && [] == ';' { := util.BytesToReadOnlyString([:]) , := util.LookUpHTML5EntityByName() if { .RawWrite(, [:]) = + 1 .RawWrite(, .Characters) continue } } } = - 1 } if == '\\' { = true continue } = false } .RawWrite(, [:]) } // DefaultWriter is a default instance of the Writer. 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(, []byte) bool { return len() >= len() && bytes.Equal(bytes.ToLower([0:len()]), bytes.ToLower()) } // IsDangerousURL returns true if the given url seems a potentially dangerous url, // otherwise false. func ( []byte) bool { if hasPrefix(, bDataImage) && len() >= 11 { := [11:] if hasPrefix(, bPng) || hasPrefix(, bGif) || hasPrefix(, bJpeg) || hasPrefix(, bWebp) || hasPrefix(, bSvg) { return false } return true } return hasPrefix(, bJs) || hasPrefix(, bVb) || hasPrefix(, bFile) || hasPrefix(, bData) }