aboutsummaryrefslogtreecommitdiff
path: root/vendor/github.com/yuin/goldmark/renderer/html
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/yuin/goldmark/renderer/html')
-rw-r--r--vendor/github.com/yuin/goldmark/renderer/html/html.go932
1 files changed, 932 insertions, 0 deletions
diff --git a/vendor/github.com/yuin/goldmark/renderer/html/html.go b/vendor/github.com/yuin/goldmark/renderer/html/html.go
new file mode 100644
index 0000000..7bf2ab8
--- /dev/null
+++ b/vendor/github.com/yuin/goldmark/renderer/html/html.go
@@ -0,0 +1,932 @@
1package html
2
3import (
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.
15type 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.
24func 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.
35func (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.
51type Option interface {
52 SetHTMLOption(*Config)
53}
54
55// TextWriter is an option name used in WithWriter.
56const optTextWriter renderer.OptionName = "Writer"
57
58type withWriter struct {
59 value Writer
60}
61
62func (o *withWriter) SetConfig(c *renderer.Config) {
63 c.Options[optTextWriter] = o.value
64}
65
66func (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.
72func 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.
80const optHardWraps renderer.OptionName = "HardWraps"
81
82type withHardWraps struct {
83}
84
85func (o *withHardWraps) SetConfig(c *renderer.Config) {
86 c.Options[optHardWraps] = true
87}
88
89func (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>'.
95func WithHardWraps() interface {
96 renderer.Option
97 Option
98} {
99 return &withHardWraps{}
100}
101
102// EastAsianLineBreaks is an option name used in WithEastAsianLineBreaks.
103const optEastAsianLineBreaks renderer.OptionName = "EastAsianLineBreaks"
104
105type withEastAsianLineBreaks struct {
106}
107
108func (o *withEastAsianLineBreaks) SetConfig(c *renderer.Config) {
109 c.Options[optEastAsianLineBreaks] = true
110}
111
112func (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.
118func WithEastAsianLineBreaks() interface {
119 renderer.Option
120 Option
121} {
122 return &withEastAsianLineBreaks{}
123}
124
125// XHTML is an option name used in WithXHTML.
126const optXHTML renderer.OptionName = "XHTML"
127
128type withXHTML struct {
129}
130
131func (o *withXHTML) SetConfig(c *renderer.Config) {
132 c.Options[optXHTML] = true
133}
134
135func (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.
141func WithXHTML() interface {
142 Option
143 renderer.Option
144} {
145 return &withXHTML{}
146}
147
148// Unsafe is an option name used in WithUnsafe.
149const optUnsafe renderer.OptionName = "Unsafe"
150
151type withUnsafe struct {
152}
153
154func (o *withUnsafe) SetConfig(c *renderer.Config) {
155 c.Options[optUnsafe] = true
156}
157
158func (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.
164func 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.
173type Renderer struct {
174 Config
175}
176
177// NewRenderer returns a new Renderer with given options.
178func 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 .
190func (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
217func (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.
226var 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
256func (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
262var HeadingAttributeFilter = GlobalAttributeFilter
263
264func (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
282var BlockquoteAttributeFilter = GlobalAttributeFilter.Extend(
283 []byte("cite"),
284)
285
286func (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
301func (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
311func (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
329func (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.
355var ListAttributeFilter = GlobalAttributeFilter.Extend(
356 []byte("start"),
357 []byte("reversed"),
358 []byte("type"),
359)
360
361func (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.
386var ListItemAttributeFilter = GlobalAttributeFilter.Extend(
387 []byte("value"),
388)
389
390func (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.
412var ParagraphAttributeFilter = GlobalAttributeFilter
413
414func (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
429func (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.
439var 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
447func (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.
464var 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
476func (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.
501var CodeAttributeFilter = GlobalAttributeFilter
502
503func (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.
529var EmphasisAttributeFilter = GlobalAttributeFilter
530
531func (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
552func (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.
576var 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
593func (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
621func (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
638func (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
676func (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
693var 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.
698func 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.
715type 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
728var replacementCharacter = []byte("\ufffd")
729
730// A WriterConfig struct has configurations for the HTML based writers.
731type 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.
737type WriterOption func(*WriterConfig)
738
739// WithEscapedSpace is a WriterOption indicates that a '\' escaped half-space(0x20) should not be rendered.
740func WithEscapedSpace() WriterOption {
741 return func(c *WriterConfig) {
742 c.EscapedSpace = true
743 }
744}
745
746type defaultWriter struct {
747 WriterConfig
748}
749
750// NewWriter returns a new Writer.
751func NewWriter(opts ...WriterOption) Writer {
752 w := &defaultWriter{}
753 for _, opt := range opts {
754 opt(&w.WriterConfig)
755 }
756 return w
757}
758
759func 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
770func (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
787func (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
805func (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.
891var DefaultWriter = NewWriter()
892
893var bDataImage = []byte("data:image/")
894var bPng = []byte("png;")
895var bGif = []byte("gif;")
896var bJpeg = []byte("jpeg;")
897var bWebp = []byte("webp;")
898var bSvg = []byte("svg+xml;")
899var bJs = []byte("javascript:")
900var bVb = []byte("vbscript:")
901var bFile = []byte("file:")
902var bData = []byte("data:")
903
904// IsDangerousURL returns true if the given url seems a potentially dangerous url,
905// otherwise false.
906func 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
920func 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}