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
15 type Config struct {
16 Writer Writer
17 HardWraps bool
18 EastAsianLineBreaks bool
19 XHTML bool
20 Unsafe bool
21 }
22
23
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
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
51 type Option interface {
52 SetHTMLOption(*Config)
53 }
54
55
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
71
72 func WithWriter(writer Writer) interface {
73 renderer.Option
74 Option
75 } {
76 return &withWriter{writer}
77 }
78
79
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
94
95 func WithHardWraps() interface {
96 renderer.Option
97 Option
98 } {
99 return &withHardWraps{}
100 }
101
102
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
117
118 func WithEastAsianLineBreaks() interface {
119 renderer.Option
120 Option
121 } {
122 return &withEastAsianLineBreaks{}
123 }
124
125
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
140
141 func WithXHTML() interface {
142 Option
143 renderer.Option
144 } {
145 return &withXHTML{}
146 }
147
148
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
163
164 func WithUnsafe() interface {
165 renderer.Option
166 Option
167 } {
168 return &withUnsafe{}
169 }
170
171
172
173 type Renderer struct {
174 Config
175 }
176
177
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
190 func (r *Renderer) RegisterFuncs(reg renderer.NodeRendererFuncRegisterer) {
191
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
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
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
257 return ast.WalkContinue, nil
258 }
259
260
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
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
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
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
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
438 var ThematicAttributeFilter = GlobalAttributeFilter.Extend(
439 []byte("align"),
440 []byte("color"),
441 []byte("noshade"),
442 []byte("size"),
443 []byte("width"),
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
463 var LinkAttributeFilter = GlobalAttributeFilter.Extend(
464 []byte("download"),
465
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
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
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
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
695
696
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
708 _, _ = w.Write(util.EscapeHTML(attr.Value.([]byte)))
709 _ = w.WriteByte('"')
710 }
711 }
712
713
714 type Writer interface {
715
716
717 Write(writer util.BufWriter, source []byte)
718
719
720
721 RawWrite(writer util.BufWriter, source []byte)
722
723
724 SecureWrite(writer util.BufWriter, source []byte)
725 }
726
727 var replacementCharacter = []byte("\ufffd")
728
729
730 type WriterConfig struct {
731
732 EscapedSpace bool
733 }
734
735
736 type WriterOption func(*WriterConfig)
737
738
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
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
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
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
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
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
904
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