Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

svg.gno

3.02 Kb · 114 lines
  1package safe
  2
  3import (
  4	"errors"
  5	"strconv"
  6	"strings"
  7	"unicode/utf8"
  8)
  9
 10// forbiddenSVG lists constructs that can run code, load something from the
 11// network or redefine the document. Engines draw with shapes and paths;
 12// none of these is needed for that (RA M-7).
 13var forbiddenSVG = []string{
 14	"<script", "javascript:", "vbscript:", "<foreignobject", "<iframe",
 15	"<object", "<embed", "<image", "<!entity", "<!doctype", "<?xml-stylesheet",
 16	"@import", "&#", // numeric character references would hide a scheme from this scan
 17}
 18
 19// AssertSVG checks an engine's raw SVG output: size, envelope, the forbidden
 20// constructs above, inline event handlers, and any href or url() that does
 21// not point inside the document. The frontend shows art only through <img>,
 22// which never runs scripts; this check is the defence in depth for every
 23// other place a data: URI ends up (RA M-7, design §5.4).
 24func AssertSVG(svg string, maxBytes int) error {
 25	if len(svg) == 0 {
 26		return errors.New("svg: empty")
 27	}
 28	if len(svg) > maxBytes {
 29		return errors.New("svg: larger than " + strconv.Itoa(maxBytes) + " bytes")
 30	}
 31	if !utf8.ValidString(svg) {
 32		return errors.New("svg: invalid UTF-8")
 33	}
 34	if !strings.HasPrefix(svg, "<svg") || !strings.HasSuffix(svg, "</svg>") {
 35		return errors.New("svg: must start with <svg and end with </svg>")
 36	}
 37	low := strings.ToLower(svg)
 38	for _, bad := range forbiddenSVG {
 39		if strings.Contains(low, bad) {
 40			return errors.New("svg: forbidden construct " + strconv.Quote(bad))
 41		}
 42	}
 43	if hasEventHandler(low) {
 44		return errors.New("svg: inline event handlers are not allowed")
 45	}
 46	if hasExternalRef(low, "href") || hasExternalRef(low, "url(") {
 47		return errors.New("svg: references must stay inside the document (#id)")
 48	}
 49	return nil
 50}
 51
 52// hasEventHandler finds an attribute named on<letters> (onload=, onclick =).
 53func hasEventHandler(low string) bool {
 54	for i := 0; i+2 < len(low); i++ {
 55		if low[i] != 'o' || low[i+1] != 'n' {
 56			continue
 57		}
 58		if i > 0 && !isAttrBoundary(low[i-1]) {
 59			continue
 60		}
 61		j := i + 2
 62		for j < len(low) && low[j] >= 'a' && low[j] <= 'z' {
 63			j++
 64		}
 65		if j == i+2 {
 66			continue
 67		}
 68		for j < len(low) && isSpace(low[j]) {
 69			j++
 70		}
 71		if j < len(low) && low[j] == '=' {
 72			return true
 73		}
 74	}
 75	return false
 76}
 77
 78// hasExternalRef reports an href attribute or url( whose target does not
 79// start with '#'. key is "href" (also matches xlink:href) or "url(".
 80func hasExternalRef(low, key string) bool {
 81	rest := low
 82	for {
 83		i := strings.Index(rest, key)
 84		if i < 0 {
 85			return false
 86		}
 87		j := i + len(key)
 88		if key == "href" {
 89			for j < len(rest) && isSpace(rest[j]) {
 90				j++
 91			}
 92			if j >= len(rest) || rest[j] != '=' {
 93				rest = rest[i+len(key):]
 94				continue
 95			}
 96			j++
 97		}
 98		for j < len(rest) && (isSpace(rest[j]) || rest[j] == '"' || rest[j] == '\'') {
 99			j++
100		}
101		if j >= len(rest) || rest[j] != '#' {
102			return true
103		}
104		rest = rest[j:]
105	}
106}
107
108func isAttrBoundary(c byte) bool {
109	return isSpace(c) || c == '"' || c == '\'' || c == '/'
110}
111
112func isSpace(c byte) bool {
113	return c == ' ' || c == '\t' || c == '\n' || c == '\r'
114}