diff options
| author | Taras Madan <tarasmadan@google.com> | 2023-02-22 22:16:50 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2023-02-24 12:47:23 +0100 |
| commit | 4165372ec8fd142475a4e35fd0cf4f8042132208 (patch) | |
| tree | 21cd62211b4dd80bee469054c5b65db77342333c /vendor/github.com/google/safehtml/style.go | |
| parent | 2b3ed821a493b8936c8bacfa6f8b4f1c90a00855 (diff) | |
dependencies: update
set go min requirements to 1.19
update dependencies
update vendor
Diffstat (limited to 'vendor/github.com/google/safehtml/style.go')
| -rw-r--r-- | vendor/github.com/google/safehtml/style.go | 304 |
1 files changed, 304 insertions, 0 deletions
diff --git a/vendor/github.com/google/safehtml/style.go b/vendor/github.com/google/safehtml/style.go new file mode 100644 index 000000000..c11ac9d96 --- /dev/null +++ b/vendor/github.com/google/safehtml/style.go @@ -0,0 +1,304 @@ +// Copyright (c) 2017 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 or at +// https://developers.google.com/open-source/licenses/bsd + +package safehtml + +import ( + "bytes" + "fmt" + "regexp" + "strings" +) + +// A Style is an immutable string-like type which represents a sequence of CSS +// declarations (property_name1: property_value1; property_name2: property_value2; ...) +// and guarantees that its value will not cause untrusted script execution +// (cross-site scripting) when evaluated as CSS in a browser. +// +// Style's string representation can safely be: +// * Interpolated as the content of a quoted HTML style attribute. However, the +// Style string must be HTML-attribute-escaped before interpolation. +// * Interpolated as the content of a {}-wrapped block within a StyleSheet. +// '<' runes in the Style string must be CSS-escaped before interpolation. +// The Style string is also guaranteed not to be able to introduce new +// properties or elide existing ones. +// * Interpolated as the content of a {}-wrapped block within an HTML <style> +// element. '<' runes in the Style string must be CSS-escaped before interpolation. +// * Assigned to the style property of a DOM node. The Style string should not +// be escaped before being assigned to the property. +// +// In addition, values of this type are composable, that is, for any two Style +// values |style1| and |style2|, style1.style() + style2.style() is itself a +// value that satisfies the Style type constraint. +type Style struct { + // We declare a Style not as a string but as a struct wrapping a string + // to prevent construction of Style values through string conversion. + str string +} + +// StyleFromConstant constructs a Style with its underlying style set to the +// given style, which must be an untyped string constant, and panics if the +// style string does not pass basic syntax checks. +// +// Users of this function must ensure themselves that the style: +// * Does not contain unsafe CSS. +// * Does not contain literal angle brackets. Otherwise, it could be unsafe to +// place a Style into the contents of a <style> element where it can't be +// HTML escaped (see http://www.w3.org/International/questions/qa-escapes). +// For example, if the Style containing +// "font: 'foo <style/><script>evil</script>'" was interpolated within a +// <style> tag, it would then break out of the style context into HTML. +// * Does not end in a property value or property name context. +// For example, a value of "background:url(\"" or "font-" does not satisfy +// the Style type contract. This rule is enforced to ensure composability: +// concatenating two incomplete strings that themselves do not contain unsafe +// CSS can result in an overall string that does. For example, if +// "javascript:evil())\"" is appended to "background:url(\"", the resulting +// string may result in the execution of a malicious script. +// +// The style may, however, contain literal single or double quotes (for example, +// in the "content" property). Therefore, the entire style string must be +// escaped when used in a style attribute. +// +// The following example values comply with Style's type contract: +// width: 1em; +// height:1em; +// width: 1em;height: 1em; +// background:url('http://url'); +// +// In addition, the empty string is safe for use in a style attribute. +// +// The following example values do NOT comply with this type's contract: +// background: red --- missing a trailing semi-colon +// background: --- missing a value and a trailing semi-colon +// 1em --- missing an attribute name, which provides context +// for the value +// +// See also http://www.w3.org/TR/css3-syntax/. +func StyleFromConstant(style stringConstant) Style { + // TODO: implement UTF-8 interchange-validity checks and blocking of newlines + // (including Unicode ones) and other whitespace characters (\t, \f) for Style and other safe types + // in this package. + if strings.ContainsAny(string(style), "<>") { + panic(fmt.Sprintf("style string %q contains angle brackets", style)) + } + if !strings.HasSuffix(string(style), ";") { + panic(fmt.Sprintf("style string %q must end with ';'", style)) + } + if !strings.Contains(string(style), ":") { + panic(fmt.Sprintf("style string %q must contain at least one ':' to specify a property-value pair", style)) + } + return Style{string(style)} +} + +// String returns the string form of the Style. +func (s Style) String() string { + return s.str +} + +// StyleProperties contains property values for CSS properties whose names are +// the hyphen-separated form of the field names. These values will be validated +// by StyleFromProperties before being included in a Style. +// +// For example, BackgroundPosition contains the value for the +// "background-position" property, and Display contains the value for the "display" +// property. +// +type StyleProperties struct { + // BackgroundImageURLs contains URL values for the background-image property. + // These values val_1, val_2, ..., val_n will be passed through URLSanitized and CSS-escaped in + // StyleFromProperties, then interpolated into to a comma-separated list of CSS URLs of the form + // url("val_1"), url("val_2"), ..., url("val_n") + // See https://www.w3.org/TR/CSS2/syndata.html#value-def-uri and https://drafts.csswg.org/css-backgrounds-3/#layering. + BackgroundImageURLs []string + // FontFamily values are used, comma-separated, as the font-family property. + // * Names starting with a Latin alphabet runes and containing only Latin alphabets and hyphens will be included unquoted. + // * Names enclosed in double quote literals (e.g. `"21st Century"`) will be CSS-escaped without the outermost quotes, + // then included within double quotes. + // * All other names will be CSS-escaped, and included within double quotes. + // See https://drafts.csswg.org/css-fonts-3/#font-family-prop. + FontFamily []string + // Display must consist of only ASCII alphabetic or '-' runes. + // Non-conforming values will be replaced by InnocuousPropertyValue in + // StyleFromProperties. + Display string + // The following values can only contain allowed runes, that is, alphanumerics, + // space, tab, and the set [+-.!#%_/*]. In addition, comment markers "//", "/*", + // and "*/" are disallowed. Non-conforming values will be replaced by + // InnocuousPropertyValue in StyleFromProperties. + BackgroundColor string + BackgroundPosition string + BackgroundRepeat string + BackgroundSize string + Color string + Height string + Width string + Left string + Right string + Top string + Bottom string + FontWeight string + Padding string + // Note: this property might allow clickjacking, but the risk is limited without + // the ability to set the position property to "absolute" or "fixed". + ZIndex string +} + +// identifierPattern matches a subset of valid <ident-token> values defined in +// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram. This pattern matches all generic family name +// keywords defined in https://drafts.csswg.org/css-fonts-3/#family-name-value. +var identifierPattern = regexp.MustCompile(`^[a-zA-Z][-a-zA-Z]+$`) + +// StyleFromProperties constructs a Style containining properties whose values +// are set in properties. The contents of the returned Style will be of the form +// property_1:val_1;property2:val_2; ... ;property_n:val_n; +// This syntax is defined in https://www.w3.org/TR/css-style-attr/. +// +// All property values are validated and, if necessary, modified to ensure that their +// inclusion in a HTML style attribute does not result in untrusted script execution, +// the addition of new properties, or the removal of existing properties. Please refer +// to the StyleProperties documentation for validation rules. +// +// The constructed Style is guaranteed to fulfill its type contract, but is not +// guaranteed to be semantically valid CSS. +func StyleFromProperties(properties StyleProperties) Style { + // TODO: if this boilerplate code grows large, consider generating property names from Field names using reflection. + var buf bytes.Buffer + if len(properties.BackgroundImageURLs) > 0 { + buf.WriteString("background-image:") + for i, url := range properties.BackgroundImageURLs { + if i > 0 { + buf.WriteString(", ") + } + fmt.Fprintf(&buf, "url(\"%s\")", cssEscapeString(URLSanitized(url).String())) + } + buf.WriteString(";") + } + if len(properties.FontFamily) > 0 { + buf.WriteString("font-family:") + for i, name := range properties.FontFamily { + if i > 0 { + buf.WriteString(", ") + } + if identifierPattern.MatchString(name) { + buf.WriteString(name) + continue + } + unescaped := name + if len(name) >= 3 && strings.HasPrefix(name, `"`) && strings.HasSuffix(name, `"`) { + unescaped = name[1 : len(name)-1] + } + fmt.Fprintf(&buf, `"%s"`, cssEscapeString(unescaped)) + } + buf.WriteByte(';') + } + if properties.Display != "" { + fmt.Fprintf(&buf, "display:%s;", filter(properties.Display, safeEnumPropertyValuePattern)) + } + if properties.BackgroundColor != "" { + fmt.Fprintf(&buf, "background-color:%s;", filter(properties.BackgroundColor, safeRegularPropertyValuePattern)) + } + if properties.BackgroundPosition != "" { + fmt.Fprintf(&buf, "background-position:%s;", filter(properties.BackgroundPosition, safeRegularPropertyValuePattern)) + } + if properties.BackgroundRepeat != "" { + fmt.Fprintf(&buf, "background-repeat:%s;", filter(properties.BackgroundRepeat, safeRegularPropertyValuePattern)) + } + if properties.BackgroundSize != "" { + fmt.Fprintf(&buf, "background-size:%s;", filter(properties.BackgroundSize, safeRegularPropertyValuePattern)) + } + if properties.Color != "" { + fmt.Fprintf(&buf, "color:%s;", filter(properties.Color, safeRegularPropertyValuePattern)) + } + if properties.Height != "" { + fmt.Fprintf(&buf, "height:%s;", filter(properties.Height, safeRegularPropertyValuePattern)) + } + if properties.Width != "" { + fmt.Fprintf(&buf, "width:%s;", filter(properties.Width, safeRegularPropertyValuePattern)) + } + if properties.Left != "" { + fmt.Fprintf(&buf, "left:%s;", filter(properties.Left, safeRegularPropertyValuePattern)) + } + if properties.Right != "" { + fmt.Fprintf(&buf, "right:%s;", filter(properties.Right, safeRegularPropertyValuePattern)) + } + if properties.Top != "" { + fmt.Fprintf(&buf, "top:%s;", filter(properties.Top, safeRegularPropertyValuePattern)) + } + if properties.Bottom != "" { + fmt.Fprintf(&buf, "bottom:%s;", filter(properties.Bottom, safeRegularPropertyValuePattern)) + } + if properties.FontWeight != "" { + fmt.Fprintf(&buf, "font-weight:%s;", filter(properties.FontWeight, safeRegularPropertyValuePattern)) + } + if properties.Padding != "" { + fmt.Fprintf(&buf, "padding:%s;", filter(properties.Padding, safeRegularPropertyValuePattern)) + } + if properties.ZIndex != "" { + fmt.Fprintf(&buf, "z-index:%s;", filter(properties.ZIndex, safeRegularPropertyValuePattern)) + } + + return Style{buf.String()} +} + +// InnocuousPropertyValue is an innocuous property generated by filter when its input unsafe. +const InnocuousPropertyValue = "zGoSafezInvalidPropertyValue" + +// safeRegularPropertyValuePattern matches strings that are safe to use as property values. +// Specifically, it matches string where every '*' or '/' is followed by end-of-text or a safe rune +// (i.e. alphanumberics or runes in the set [+-.!#%_ \t]). This regex ensures that the following +// are disallowed: +// * "/*" and "*/", which are CSS comment markers. +// * "//", even though this is not a comment marker in the CSS specification. Disallowing +// this string minimizes the chance that browser peculiarities or parsing bugs will allow +// sanitization to be bypassed. +// * '(' and ')', which can be used to call functions. +// * ',', since it can be used to inject extra values into a property. +// * Runes which could be matched on CSS error recovery of a previously malformed token, such as '@' +// and ':'. See http://www.w3.org/TR/css3-syntax/#error-handling. +var safeRegularPropertyValuePattern = regexp.MustCompile(`^(?:[*/]?(?:[0-9a-zA-Z+-.!#%_ \t]|$))*$`) + +// safeEnumPropertyValuePattern matches strings that are safe to use as enumerated property values. +// Specifically, it matches strings that contain only alphabetic and '-' runes. +var safeEnumPropertyValuePattern = regexp.MustCompile(`^[a-zA-Z-]*$`) + +// filter returns value if it matches pattern. Otherwise, it returns InnocuousPropertyValue. +func filter(value string, pattern *regexp.Regexp) string { + if !pattern.MatchString(value) { + return InnocuousPropertyValue + } + return value +} + +// cssEscapeString escapes s so that it is safe to put between "" to form a CSS <string-token>. +// See syntax at https://www.w3.org/TR/css-syntax-3/#string-token-diagram. +// +// On top of the escape sequences required in <string-token>, this function also escapes +// control runes to minimize the risk of these runes triggering browser-specific bugs. +func cssEscapeString(s string) string { + var b bytes.Buffer + b.Grow(len(s)) + // TODO: consider optmizations (e.g. ranging over bytes, batching writes of contiguous sequences of unescaped runes) if + // performance becomes an issue. + for _, c := range s { + switch { + case c == '\u0000': + // Replace the NULL byte according to https://www.w3.org/TR/css-syntax-3/#input-preprocessing. + // We take this extra precaution in case the user agent fails to handle NULL properly. + b.WriteString("\uFFFD") + case c == '<', // Prevents breaking out of a style element with `</style>`. Escape this in case the Style user forgets to. + c == '"', c == '\\', // Must be CSS-escaped in <string-token>. U+000A line feed is handled in the next case. + c <= '\u001F', c == '\u007F', // C0 control codes + c >= '\u0080' && c <= '\u009F', // C1 control codes + c == '\u2028', c == '\u2029': // Unicode newline characters + // See CSS escape sequence syntax at https://www.w3.org/TR/css-syntax-3/#escape-diagram. + fmt.Fprintf(&b, "\\%06X", c) + default: + b.WriteRune(c) + } + } + return b.String() +} |
