mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-11-04 09:02:25 -06:00 
			
		
		
		
	
		
			
	
	
		
			274 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			274 lines
		
	
	
	
		
			7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 
								 | 
							
								// Copyright 2011 The Go Authors. All rights reserved.
							 | 
						||
| 
								 | 
							
								// Use of this source code is governed by a BSD-style
							 | 
						||
| 
								 | 
							
								// license that can be found in the LICENSE file.
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								package html
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import (
							 | 
						||
| 
								 | 
							
									"bufio"
							 | 
						||
| 
								 | 
							
									"errors"
							 | 
						||
| 
								 | 
							
									"fmt"
							 | 
						||
| 
								 | 
							
									"io"
							 | 
						||
| 
								 | 
							
									"strings"
							 | 
						||
| 
								 | 
							
								)
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								type writer interface {
							 | 
						||
| 
								 | 
							
									io.Writer
							 | 
						||
| 
								 | 
							
									io.ByteWriter
							 | 
						||
| 
								 | 
							
									WriteString(string) (int, error)
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Render renders the parse tree n to the given writer.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Rendering is done on a 'best effort' basis: calling Parse on the output of
							 | 
						||
| 
								 | 
							
								// Render will always result in something similar to the original tree, but it
							 | 
						||
| 
								 | 
							
								// is not necessarily an exact clone unless the original tree was 'well-formed'.
							 | 
						||
| 
								 | 
							
								// 'Well-formed' is not easily specified; the HTML5 specification is
							 | 
						||
| 
								 | 
							
								// complicated.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Calling Parse on arbitrary input typically results in a 'well-formed' parse
							 | 
						||
| 
								 | 
							
								// tree. However, it is possible for Parse to yield a 'badly-formed' parse tree.
							 | 
						||
| 
								 | 
							
								// For example, in a 'well-formed' parse tree, no <a> element is a child of
							 | 
						||
| 
								 | 
							
								// another <a> element: parsing "<a><a>" results in two sibling elements.
							 | 
						||
| 
								 | 
							
								// Similarly, in a 'well-formed' parse tree, no <a> element is a child of a
							 | 
						||
| 
								 | 
							
								// <table> element: parsing "<p><table><a>" results in a <p> with two sibling
							 | 
						||
| 
								 | 
							
								// children; the <a> is reparented to the <table>'s parent. However, calling
							 | 
						||
| 
								 | 
							
								// Parse on "<a><table><a>" does not return an error, but the result has an <a>
							 | 
						||
| 
								 | 
							
								// element with an <a> child, and is therefore not 'well-formed'.
							 | 
						||
| 
								 | 
							
								//
							 | 
						||
| 
								 | 
							
								// Programmatically constructed trees are typically also 'well-formed', but it
							 | 
						||
| 
								 | 
							
								// is possible to construct a tree that looks innocuous but, when rendered and
							 | 
						||
| 
								 | 
							
								// re-parsed, results in a different tree. A simple example is that a solitary
							 | 
						||
| 
								 | 
							
								// text node would become a tree containing <html>, <head> and <body> elements.
							 | 
						||
| 
								 | 
							
								// Another example is that the programmatic equivalent of "a<head>b</head>c"
							 | 
						||
| 
								 | 
							
								// becomes "<html><head><head/><body>abc</body></html>".
							 | 
						||
| 
								 | 
							
								func Render(w io.Writer, n *Node) error {
							 | 
						||
| 
								 | 
							
									if x, ok := w.(writer); ok {
							 | 
						||
| 
								 | 
							
										return render(x, n)
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									buf := bufio.NewWriter(w)
							 | 
						||
| 
								 | 
							
									if err := render(buf, n); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return buf.Flush()
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// plaintextAbort is returned from render1 when a <plaintext> element
							 | 
						||
| 
								 | 
							
								// has been rendered. No more end tags should be rendered after that.
							 | 
						||
| 
								 | 
							
								var plaintextAbort = errors.New("html: internal error (plaintext abort)")
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func render(w writer, n *Node) error {
							 | 
						||
| 
								 | 
							
									err := render1(w, n)
							 | 
						||
| 
								 | 
							
									if err == plaintextAbort {
							 | 
						||
| 
								 | 
							
										err = nil
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return err
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								func render1(w writer, n *Node) error {
							 | 
						||
| 
								 | 
							
									// Render non-element nodes; these are the easy cases.
							 | 
						||
| 
								 | 
							
									switch n.Type {
							 | 
						||
| 
								 | 
							
									case ErrorNode:
							 | 
						||
| 
								 | 
							
										return errors.New("html: cannot render an ErrorNode node")
							 | 
						||
| 
								 | 
							
									case TextNode:
							 | 
						||
| 
								 | 
							
										return escape(w, n.Data)
							 | 
						||
| 
								 | 
							
									case DocumentNode:
							 | 
						||
| 
								 | 
							
										for c := n.FirstChild; c != nil; c = c.NextSibling {
							 | 
						||
| 
								 | 
							
											if err := render1(w, c); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return nil
							 | 
						||
| 
								 | 
							
									case ElementNode:
							 | 
						||
| 
								 | 
							
										// No-op.
							 | 
						||
| 
								 | 
							
									case CommentNode:
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString("<!--"); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString(n.Data); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString("-->"); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return nil
							 | 
						||
| 
								 | 
							
									case DoctypeNode:
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString("<!DOCTYPE "); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString(n.Data); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if n.Attr != nil {
							 | 
						||
| 
								 | 
							
											var p, s string
							 | 
						||
| 
								 | 
							
											for _, a := range n.Attr {
							 | 
						||
| 
								 | 
							
												switch a.Key {
							 | 
						||
| 
								 | 
							
												case "public":
							 | 
						||
| 
								 | 
							
													p = a.Val
							 | 
						||
| 
								 | 
							
												case "system":
							 | 
						||
| 
								 | 
							
													s = a.Val
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											if p != "" {
							 | 
						||
| 
								 | 
							
												if _, err := w.WriteString(" PUBLIC "); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												if err := writeQuoted(w, p); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												if s != "" {
							 | 
						||
| 
								 | 
							
													if err := w.WriteByte(' '); err != nil {
							 | 
						||
| 
								 | 
							
														return err
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
													if err := writeQuoted(w, s); err != nil {
							 | 
						||
| 
								 | 
							
														return err
							 | 
						||
| 
								 | 
							
													}
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											} else if s != "" {
							 | 
						||
| 
								 | 
							
												if _, err := w.WriteString(" SYSTEM "); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
												if err := writeQuoted(w, s); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										return w.WriteByte('>')
							 | 
						||
| 
								 | 
							
									case RawNode:
							 | 
						||
| 
								 | 
							
										_, err := w.WriteString(n.Data)
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										return errors.New("html: unknown node type")
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Render the <xxx> opening tag.
							 | 
						||
| 
								 | 
							
									if err := w.WriteByte('<'); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if _, err := w.WriteString(n.Data); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									for _, a := range n.Attr {
							 | 
						||
| 
								 | 
							
										if err := w.WriteByte(' '); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if a.Namespace != "" {
							 | 
						||
| 
								 | 
							
											if _, err := w.WriteString(a.Namespace); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											if err := w.WriteByte(':'); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString(a.Key); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if _, err := w.WriteString(`="`); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if err := escape(w, a.Val); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if err := w.WriteByte('"'); err != nil {
							 | 
						||
| 
								 | 
							
											return err
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if voidElements[n.Data] {
							 | 
						||
| 
								 | 
							
										if n.FirstChild != nil {
							 | 
						||
| 
								 | 
							
											return fmt.Errorf("html: void element <%s> has child nodes", n.Data)
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										_, err := w.WriteString("/>")
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if err := w.WriteByte('>'); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Add initial newline where there is danger of a newline beging ignored.
							 | 
						||
| 
								 | 
							
									if c := n.FirstChild; c != nil && c.Type == TextNode && strings.HasPrefix(c.Data, "\n") {
							 | 
						||
| 
								 | 
							
										switch n.Data {
							 | 
						||
| 
								 | 
							
										case "pre", "listing", "textarea":
							 | 
						||
| 
								 | 
							
											if err := w.WriteByte('\n'); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Render any child nodes.
							 | 
						||
| 
								 | 
							
									switch n.Data {
							 | 
						||
| 
								 | 
							
									case "iframe", "noembed", "noframes", "noscript", "plaintext", "script", "style", "xmp":
							 | 
						||
| 
								 | 
							
										for c := n.FirstChild; c != nil; c = c.NextSibling {
							 | 
						||
| 
								 | 
							
											if c.Type == TextNode {
							 | 
						||
| 
								 | 
							
												if _, err := w.WriteString(c.Data); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											} else {
							 | 
						||
| 
								 | 
							
												if err := render1(w, c); err != nil {
							 | 
						||
| 
								 | 
							
													return err
							 | 
						||
| 
								 | 
							
												}
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										if n.Data == "plaintext" {
							 | 
						||
| 
								 | 
							
											// Don't render anything else. <plaintext> must be the
							 | 
						||
| 
								 | 
							
											// last element in the file, with no closing tag.
							 | 
						||
| 
								 | 
							
											return plaintextAbort
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									default:
							 | 
						||
| 
								 | 
							
										for c := n.FirstChild; c != nil; c = c.NextSibling {
							 | 
						||
| 
								 | 
							
											if err := render1(w, c); err != nil {
							 | 
						||
| 
								 | 
							
												return err
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									// Render the </xxx> closing tag.
							 | 
						||
| 
								 | 
							
									if _, err := w.WriteString("</"); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if _, err := w.WriteString(n.Data); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return w.WriteByte('>')
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// writeQuoted writes s to w surrounded by quotes. Normally it will use double
							 | 
						||
| 
								 | 
							
								// quotes, but if s contains a double quote, it will use single quotes.
							 | 
						||
| 
								 | 
							
								// It is used for writing the identifiers in a doctype declaration.
							 | 
						||
| 
								 | 
							
								// In valid HTML, they can't contain both types of quotes.
							 | 
						||
| 
								 | 
							
								func writeQuoted(w writer, s string) error {
							 | 
						||
| 
								 | 
							
									var q byte = '"'
							 | 
						||
| 
								 | 
							
									if strings.Contains(s, `"`) {
							 | 
						||
| 
								 | 
							
										q = '\''
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if err := w.WriteByte(q); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if _, err := w.WriteString(s); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									if err := w.WriteByte(q); err != nil {
							 | 
						||
| 
								 | 
							
										return err
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
									return nil
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								// Section 12.1.2, "Elements", gives this list of void elements. Void elements
							 | 
						||
| 
								 | 
							
								// are those that can't have any contents.
							 | 
						||
| 
								 | 
							
								var voidElements = map[string]bool{
							 | 
						||
| 
								 | 
							
									"area":   true,
							 | 
						||
| 
								 | 
							
									"base":   true,
							 | 
						||
| 
								 | 
							
									"br":     true,
							 | 
						||
| 
								 | 
							
									"col":    true,
							 | 
						||
| 
								 | 
							
									"embed":  true,
							 | 
						||
| 
								 | 
							
									"hr":     true,
							 | 
						||
| 
								 | 
							
									"img":    true,
							 | 
						||
| 
								 | 
							
									"input":  true,
							 | 
						||
| 
								 | 
							
									"keygen": true, // "keygen" has been removed from the spec, but are kept here for backwards compatibility.
							 | 
						||
| 
								 | 
							
									"link":   true,
							 | 
						||
| 
								 | 
							
									"meta":   true,
							 | 
						||
| 
								 | 
							
									"param":  true,
							 | 
						||
| 
								 | 
							
									"source": true,
							 | 
						||
| 
								 | 
							
									"track":  true,
							 | 
						||
| 
								 | 
							
									"wbr":    true,
							 | 
						||
| 
								 | 
							
								}
							 |