658 lines
22 KiB
Go
658 lines
22 KiB
Go
/*
|
|
*
|
|
* Copyright 2022 Google LLC
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* https://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
// Package yamltemplate is a drop-in-replacement for using text/template to produce YAML, that adds automatic detection for YAML injection
|
|
package yamltemplate
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"io/fs"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"reflect"
|
|
"text/template"
|
|
"text/template/parse"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/google/safetext/common"
|
|
)
|
|
|
|
// ErrInvalidYAMLTemplate indicates the requested template is not valid YAML.
|
|
var ErrInvalidYAMLTemplate error = errors.New("Invalid YAML Template")
|
|
|
|
// ErrYAMLInjection indicates the inputs resulted in YAML injection.
|
|
var ErrYAMLInjection error = errors.New("YAML Injection Detected")
|
|
|
|
// ExecError is the custom error type returned when Execute has an
|
|
// error evaluating its template. (If a write error occurs, the actual
|
|
// error is returned; it will not be of type ExecError.)
|
|
type ExecError = template.ExecError
|
|
|
|
// FuncMap is the type of the map defining the mapping from names to functions.
|
|
// Each function must have either a single return value, or two return values of
|
|
// which the second has type error. In that case, if the second (error)
|
|
// return value evaluates to non-nil during execution, execution terminates and
|
|
// Execute returns that error.
|
|
//
|
|
// Errors returned by Execute wrap the underlying error; call errors.As to
|
|
// uncover them.
|
|
//
|
|
// When template execution invokes a function with an argument list, that list
|
|
// must be assignable to the function's parameter types. Functions meant to
|
|
// apply to arguments of arbitrary type can use parameters of type interface{} or
|
|
// of type reflect.Value. Similarly, functions meant to return a result of arbitrary
|
|
// type can return interface{} or reflect.Value.
|
|
type FuncMap = template.FuncMap
|
|
|
|
// Template is the representation of a parsed template. The *parse.Tree
|
|
// field is exported only for use by html/template and should be treated
|
|
// as unexported by all other clients.
|
|
type Template struct {
|
|
unsafeTemplate *template.Template
|
|
}
|
|
|
|
// New allocates a new, undefined template with the given name.
|
|
func New(name string) *Template {
|
|
return &Template{unsafeTemplate: template.New(name).Funcs(common.FuncMap)}
|
|
}
|
|
|
|
const yamlSpecialCharacters = "{}[]&*#?|-.<>=!%@:\"'`,\r\n"
|
|
|
|
func mapOrArray(in interface{}) bool {
|
|
return in != nil && (reflect.TypeOf(in).Kind() == reflect.Map || reflect.TypeOf(in).Kind() == reflect.Slice || reflect.TypeOf(in).Kind() == reflect.Array)
|
|
}
|
|
|
|
func allKeysMatch(base interface{}, a interface{}, b interface{}) bool {
|
|
if base == nil {
|
|
return a == nil && b == nil
|
|
}
|
|
|
|
switch reflect.TypeOf(base).Kind() {
|
|
case reflect.Ptr:
|
|
if reflect.TypeOf(a).Kind() != reflect.Ptr || reflect.TypeOf(b).Kind() != reflect.Ptr {
|
|
return false
|
|
}
|
|
|
|
if !allKeysMatch(reflect.ValueOf(base).Elem().Interface(), reflect.ValueOf(a).Elem().Interface(), reflect.ValueOf(b).Elem().Interface()) {
|
|
return true
|
|
}
|
|
case reflect.Map:
|
|
if reflect.TypeOf(a).Kind() != reflect.Map || reflect.TypeOf(b).Kind() != reflect.Map {
|
|
return false
|
|
}
|
|
|
|
if reflect.ValueOf(a).Len() != reflect.ValueOf(base).Len() || reflect.ValueOf(b).Len() != reflect.ValueOf(base).Len() {
|
|
return false
|
|
}
|
|
|
|
basei := reflect.ValueOf(base).MapRange()
|
|
for basei.Next() {
|
|
av := reflect.ValueOf(a).MapIndex(basei.Key())
|
|
bv := reflect.ValueOf(b).MapIndex(basei.Key())
|
|
if !av.IsValid() || !bv.IsValid() ||
|
|
!allKeysMatch(basei.Value().Interface(), av.Interface(), bv.Interface()) {
|
|
return false
|
|
}
|
|
}
|
|
case reflect.Slice, reflect.Array:
|
|
if reflect.TypeOf(a).Kind() != reflect.Slice && reflect.TypeOf(a).Kind() != reflect.Array &&
|
|
reflect.TypeOf(b).Kind() != reflect.Slice && reflect.TypeOf(b).Kind() != reflect.Array {
|
|
return false
|
|
}
|
|
|
|
if reflect.ValueOf(a).Len() != reflect.ValueOf(base).Len() || reflect.ValueOf(b).Len() != reflect.ValueOf(base).Len() {
|
|
return false
|
|
}
|
|
|
|
for i := 0; i < reflect.ValueOf(base).Len(); i++ {
|
|
if !allKeysMatch(reflect.ValueOf(base).Index(i).Interface(), reflect.ValueOf(a).Index(i).Interface(), reflect.ValueOf(b).Index(i).Interface()) {
|
|
return false
|
|
}
|
|
}
|
|
case reflect.Struct:
|
|
n := reflect.ValueOf(base).NumField()
|
|
for i := 0; i < n; i++ {
|
|
baseit := reflect.TypeOf(base).Field(i)
|
|
ait := reflect.TypeOf(a).Field(i)
|
|
bit := reflect.TypeOf(b).Field(i)
|
|
|
|
if baseit.Name != ait.Name || baseit.Name != bit.Name {
|
|
return false
|
|
}
|
|
|
|
// Only compare public members (private members cannot be overwritten by text/template)
|
|
decodedName, _ := utf8.DecodeRuneInString(baseit.Name)
|
|
if unicode.IsUpper(decodedName) {
|
|
basei := reflect.ValueOf(base).Field(i)
|
|
ai := reflect.ValueOf(a).Field(i)
|
|
bi := reflect.ValueOf(b).Field(i)
|
|
|
|
if !allKeysMatch(basei.Interface(), ai.Interface(), bi.Interface()) {
|
|
return false
|
|
}
|
|
}
|
|
}
|
|
case reflect.String:
|
|
// Baseline type of string was chosen arbitrarily, so just check that there isn't a new a map or slice/array injected, which would change the structure of the YAML
|
|
if mapOrArray(a) || mapOrArray(b) {
|
|
return false
|
|
}
|
|
default:
|
|
if reflect.TypeOf(a) != reflect.TypeOf(base) || reflect.TypeOf(b) != reflect.TypeOf(base) {
|
|
return false
|
|
}
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
func unmarshalYaml(data []byte) ([]interface{}, error) {
|
|
r := make([]interface{}, 0)
|
|
|
|
decoder := yaml.NewDecoder(bytes.NewReader(data))
|
|
for {
|
|
var t interface{}
|
|
err := decoder.Decode(&t)
|
|
|
|
if err == io.EOF {
|
|
break
|
|
} else if err == nil {
|
|
r = append(r, t)
|
|
} else {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// Mutation algorithm
|
|
func mutateString(s string) string {
|
|
// Longest possible output string is 2x the original
|
|
out := make([]rune, len(s)*2)
|
|
|
|
i := 0
|
|
for _, r := range s {
|
|
out[i] = r
|
|
i++
|
|
|
|
// Don't repeat quoting-related characters so as to not allow YAML context change in the mutation result
|
|
if r != '\\' && r != '\'' && r != '"' {
|
|
out[i] = r
|
|
i++
|
|
}
|
|
}
|
|
|
|
return string(out[:i])
|
|
}
|
|
|
|
// Execute applies a parsed template to the specified data object,
|
|
// and writes the output to wr.
|
|
// If an error occurs executing the template or writing its output,
|
|
// execution stops, but partial results may already have been written to
|
|
// the output writer.
|
|
// A template may be executed safely in parallel, although if parallel
|
|
// executions share a Writer the output may be interleaved.
|
|
//
|
|
// If data is a reflect.Value, the template applies to the concrete
|
|
// value that the reflect.Value holds, as in fmt.Print.
|
|
func (t *Template) Execute(wr io.Writer, data interface{}) (err error) {
|
|
if data == nil {
|
|
return t.unsafeTemplate.Execute(wr, data)
|
|
}
|
|
|
|
// An attacker may be able to cause type confusion or nil dereference panic during allKeysMatch
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
err = ErrYAMLInjection
|
|
}
|
|
}()
|
|
|
|
// Calculate requested result first
|
|
var requestedResult bytes.Buffer
|
|
|
|
if err := t.unsafeTemplate.Execute(&requestedResult, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
// Fast path for if there are no YAML special characters in the input strings
|
|
if !common.ContainsStringsWithSpecialCharacters(data, yamlSpecialCharacters) {
|
|
// Note: We assume the result was valid YAML, and don't check for ErrInvalidYAMLTemplate
|
|
requestedResult.WriteTo(wr)
|
|
return nil
|
|
}
|
|
|
|
walked, err := t.unsafeTemplate.Clone()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
walked.Tree = walked.Tree.Copy()
|
|
|
|
common.WalkApplyFuncToNonDeclaractiveActions(walked, walked.Tree.Root)
|
|
|
|
// Get baseline
|
|
var baselineResult bytes.Buffer
|
|
if err = common.ExecuteWithCallback(walked, common.BaselineString, &baselineResult, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
parsedBaselineResult, err := unmarshalYaml(baselineResult.Bytes())
|
|
if err != nil {
|
|
return ErrInvalidYAMLTemplate
|
|
}
|
|
|
|
// If baseline was valid, request must also be valid YAML for no injection to have occurred
|
|
parsedRequestedResult, err := unmarshalYaml(requestedResult.Bytes())
|
|
if err != nil {
|
|
return ErrYAMLInjection
|
|
}
|
|
|
|
// Mutate the input
|
|
var mutatedResult bytes.Buffer
|
|
if err = common.ExecuteWithCallback(walked, mutateString, &mutatedResult, data); err != nil {
|
|
return err
|
|
}
|
|
|
|
parsedMutatedResult, err := unmarshalYaml(mutatedResult.Bytes())
|
|
if err != nil {
|
|
return ErrYAMLInjection
|
|
}
|
|
|
|
// Compare results
|
|
if !allKeysMatch(parsedBaselineResult, parsedRequestedResult, parsedMutatedResult) {
|
|
return ErrYAMLInjection
|
|
}
|
|
|
|
requestedResult.WriteTo(wr)
|
|
return nil
|
|
}
|
|
|
|
// Name returns the name of the template.
|
|
func (t *Template) Name() string {
|
|
return t.unsafeTemplate.Name()
|
|
}
|
|
|
|
// New allocates a new, undefined template associated with the given one and with the same
|
|
// delimiters. The association, which is transitive, allows one template to
|
|
// invoke another with a {{template}} action.
|
|
//
|
|
// Because associated templates share underlying data, template construction
|
|
// cannot be done safely in parallel. Once the templates are constructed, they
|
|
// can be executed in parallel.
|
|
func (t *Template) New(name string) *Template {
|
|
return &Template{unsafeTemplate: t.unsafeTemplate.New(name).Funcs(common.FuncMap)}
|
|
}
|
|
|
|
// Clone returns a duplicate of the template, including all associated
|
|
// templates. The actual representation is not copied, but the name space of
|
|
// associated templates is, so further calls to Parse in the copy will add
|
|
// templates to the copy but not to the original. Clone can be used to prepare
|
|
// common templates and use them with variant definitions for other templates
|
|
// by adding the variants after the clone is made.
|
|
func (t *Template) Clone() (*Template, error) {
|
|
nt, err := t.unsafeTemplate.Clone()
|
|
return &Template{unsafeTemplate: nt}, err
|
|
}
|
|
|
|
// AddParseTree associates the argument parse tree with the template t, giving
|
|
// it the specified name. If the template has not been defined, this tree becomes
|
|
// its definition. If it has been defined and already has that name, the existing
|
|
// definition is replaced; otherwise a new template is created, defined, and returned.
|
|
func (t *Template) AddParseTree(name string, tree *parse.Tree) (*Template, error) {
|
|
nt, err := t.unsafeTemplate.AddParseTree(name, tree)
|
|
|
|
if nt != t.unsafeTemplate {
|
|
return &Template{unsafeTemplate: nt}, err
|
|
}
|
|
return t, err
|
|
}
|
|
|
|
// Option sets options for the template. Options are described by
|
|
// strings, either a simple string or "key=value". There can be at
|
|
// most one equals sign in an option string. If the option string
|
|
// is unrecognized or otherwise invalid, Option panics.
|
|
//
|
|
// Known options:
|
|
//
|
|
// missingkey: Control the behavior during execution if a map is
|
|
// indexed with a key that is not present in the map.
|
|
//
|
|
// "missingkey=default" or "missingkey=invalid"
|
|
// The default behavior: Do nothing and continue execution.
|
|
// If printed, the result of the index operation is the string
|
|
// "<no value>".
|
|
// "missingkey=zero"
|
|
// The operation returns the zero value for the map type's element.
|
|
// "missingkey=error"
|
|
// Execution stops immediately with an error.
|
|
func (t *Template) Option(opt ...string) *Template {
|
|
for _, s := range opt {
|
|
t.unsafeTemplate.Option(s)
|
|
}
|
|
return t
|
|
}
|
|
|
|
// Templates returns a slice of defined templates associated with t.
|
|
func (t *Template) Templates() []*Template {
|
|
s := t.unsafeTemplate.Templates()
|
|
|
|
var ns []*Template
|
|
for _, nt := range s {
|
|
ns = append(ns, &Template{unsafeTemplate: nt})
|
|
}
|
|
|
|
return ns
|
|
}
|
|
|
|
// ExecuteTemplate applies the template associated with t that has the given name
|
|
// to the specified data object and writes the output to wr.
|
|
// If an error occurs executing the template or writing its output,
|
|
// execution stops, but partial results may already have been written to
|
|
// the output writer.
|
|
// A template may be executed safely in parallel, although if parallel
|
|
// executions share a Writer the output may be interleaved.
|
|
func (t *Template) ExecuteTemplate(wr io.Writer, name string, data interface{}) error {
|
|
tmpl := t.Lookup(name)
|
|
if tmpl == nil {
|
|
return fmt.Errorf("template: no template %q associated with template %q", name, t.Name())
|
|
}
|
|
return tmpl.Execute(wr, data)
|
|
}
|
|
|
|
// Delims sets the action delimiters to the specified strings, to be used in
|
|
// subsequent calls to Parse, ParseFiles, or ParseGlob. Nested template
|
|
// definitions will inherit the settings. An empty delimiter stands for the
|
|
// corresponding default: {{ or }}.
|
|
// The return value is the template, so calls can be chained.
|
|
func (t *Template) Delims(left, right string) *Template {
|
|
t.unsafeTemplate.Delims(left, right)
|
|
return t
|
|
}
|
|
|
|
// DefinedTemplates returns a string listing the defined templates,
|
|
// prefixed by the string "; defined templates are: ". If there are none,
|
|
// it returns the empty string. For generating an error message here
|
|
// and in html/template.
|
|
func (t *Template) DefinedTemplates() string {
|
|
return t.unsafeTemplate.DefinedTemplates()
|
|
}
|
|
|
|
// Funcs adds the elements of the argument map to the template's function map.
|
|
// It must be called before the template is parsed.
|
|
// It panics if a value in the map is not a function with appropriate return
|
|
// type or if the name cannot be used syntactically as a function in a template.
|
|
// It is legal to overwrite elements of the map. The return value is the template,
|
|
// so calls can be chained.
|
|
func (t *Template) Funcs(funcMap FuncMap) *Template {
|
|
t.unsafeTemplate.Funcs(funcMap)
|
|
return t
|
|
}
|
|
|
|
// Lookup returns the template with the given name that is associated with t.
|
|
// It returns nil if there is no such template or the template has no definition.
|
|
func (t *Template) Lookup(name string) *Template {
|
|
nt := t.unsafeTemplate.Lookup(name)
|
|
|
|
if nt == nil {
|
|
return nil
|
|
}
|
|
|
|
if nt != t.unsafeTemplate {
|
|
return &Template{unsafeTemplate: nt}
|
|
}
|
|
|
|
return t
|
|
}
|
|
|
|
// Parse parses text as a template body for t.
|
|
// Named template definitions ({{define ...}} or {{block ...}} statements) in text
|
|
// define additional templates associated with t and are removed from the
|
|
// definition of t itself.
|
|
//
|
|
// Templates can be redefined in successive calls to Parse.
|
|
// A template definition with a body containing only white space and comments
|
|
// is considered empty and will not replace an existing template's body.
|
|
// This allows using Parse to add new named template definitions without
|
|
// overwriting the main template body.
|
|
func (t *Template) Parse(text string) (*Template, error) {
|
|
nt, err := t.unsafeTemplate.Parse(text)
|
|
|
|
if nt != t.unsafeTemplate {
|
|
return &Template{unsafeTemplate: nt}, err
|
|
}
|
|
|
|
return t, err
|
|
}
|
|
|
|
// Must is a helper that wraps a call to a function returning (*Template, error)
|
|
// and panics if the error is non-nil. It is intended for use in variable
|
|
// initializations such as
|
|
//
|
|
// var t = template.Must(template.New("name").Parse("text"))
|
|
func Must(t *Template, err error) *Template {
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
return t
|
|
}
|
|
|
|
func readFileOS(file string) (name string, b []byte, err error) {
|
|
name = filepath.Base(file)
|
|
b, err = os.ReadFile(file)
|
|
return
|
|
}
|
|
|
|
func readFileFS(fsys fs.FS) func(string) (string, []byte, error) {
|
|
return func(file string) (name string, b []byte, err error) {
|
|
name = path.Base(file)
|
|
b, err = fs.ReadFile(fsys, file)
|
|
return
|
|
}
|
|
}
|
|
|
|
func parseFiles(t *Template, readFile func(string) (string, []byte, error), filenames ...string) (*Template, error) {
|
|
if len(filenames) == 0 {
|
|
// Not really a problem, but be consistent.
|
|
return nil, fmt.Errorf("template: no files named in call to ParseFiles")
|
|
}
|
|
for _, filename := range filenames {
|
|
name, b, err := readFile(filename)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s := string(b)
|
|
// First template becomes return value if not already defined,
|
|
// and we use that one for subsequent New calls to associate
|
|
// all the templates together. Also, if this file has the same name
|
|
// as t, this file becomes the contents of t, so
|
|
// t, err := New(name).Funcs(xxx).ParseFiles(name)
|
|
// works. Otherwise we create a new template associated with t.
|
|
var tmpl *Template
|
|
if t == nil {
|
|
t = New(name)
|
|
}
|
|
if name == t.Name() {
|
|
tmpl = t
|
|
} else {
|
|
tmpl = t.New(name)
|
|
}
|
|
_, err = tmpl.Parse(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return t, nil
|
|
}
|
|
|
|
// parseGlob is the implementation of the function and method ParseGlob.
|
|
func parseGlob(t *Template, pattern string) (*Template, error) {
|
|
filenames, err := filepath.Glob(pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(filenames) == 0 {
|
|
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
|
}
|
|
return parseFiles(t, readFileOS, filenames...)
|
|
}
|
|
|
|
func parseFS(t *Template, fsys fs.FS, patterns []string) (*Template, error) {
|
|
var filenames []string
|
|
for _, pattern := range patterns {
|
|
list, err := fs.Glob(fsys, pattern)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(list) == 0 {
|
|
return nil, fmt.Errorf("template: pattern matches no files: %#q", pattern)
|
|
}
|
|
filenames = append(filenames, list...)
|
|
}
|
|
return parseFiles(t, readFileFS(fsys), filenames...)
|
|
}
|
|
|
|
// ParseFiles creates a new Template and parses the template definitions from
|
|
// the named files. The returned template's name will have the base name and
|
|
// parsed contents of the first file. There must be at least one file.
|
|
// If an error occurs, parsing stops and the returned *Template is nil.
|
|
//
|
|
// When parsing multiple files with the same name in different directories,
|
|
// the last one mentioned will be the one that results.
|
|
// For instance, ParseFiles("a/foo", "b/foo") stores "b/foo" as the template
|
|
// named "foo", while "a/foo" is unavailable.
|
|
func ParseFiles(filenames ...string) (*Template, error) {
|
|
return parseFiles(nil, readFileOS, filenames...)
|
|
}
|
|
|
|
// ParseFiles parses the named files and associates the resulting templates with
|
|
// t. If an error occurs, parsing stops and the returned template is nil;
|
|
// otherwise it is t. There must be at least one file.
|
|
// Since the templates created by ParseFiles are named by the base
|
|
// names of the argument files, t should usually have the name of one
|
|
// of the (base) names of the files. If it does not, depending on t's
|
|
// contents before calling ParseFiles, t.Execute may fail. In that
|
|
// case use t.ExecuteTemplate to execute a valid template.
|
|
//
|
|
// When parsing multiple files with the same name in different directories,
|
|
// the last one mentioned will be the one that results.
|
|
func (t *Template) ParseFiles(filenames ...string) (*Template, error) {
|
|
// Ensure template is inited
|
|
t.Option()
|
|
|
|
return parseFiles(t, readFileOS, filenames...)
|
|
}
|
|
|
|
// ParseGlob creates a new Template and parses the template definitions from
|
|
// the files identified by the pattern. The files are matched according to the
|
|
// semantics of filepath.Match, and the pattern must match at least one file.
|
|
// The returned template will have the (base) name and (parsed) contents of the
|
|
// first file matched by the pattern. ParseGlob is equivalent to calling
|
|
// ParseFiles with the list of files matched by the pattern.
|
|
//
|
|
// When parsing multiple files with the same name in different directories,
|
|
// the last one mentioned will be the one that results.
|
|
func ParseGlob(pattern string) (*Template, error) {
|
|
return parseGlob(nil, pattern)
|
|
}
|
|
|
|
// ParseGlob parses the template definitions in the files identified by the
|
|
// pattern and associates the resulting templates with t. The files are matched
|
|
// according to the semantics of filepath.Match, and the pattern must match at
|
|
// least one file. ParseGlob is equivalent to calling t.ParseFiles with the
|
|
// list of files matched by the pattern.
|
|
//
|
|
// When parsing multiple files with the same name in different directories,
|
|
// the last one mentioned will be the one that results.
|
|
func (t *Template) ParseGlob(pattern string) (*Template, error) {
|
|
// Ensure template is inited
|
|
t.Option()
|
|
|
|
return parseGlob(t, pattern)
|
|
}
|
|
|
|
// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys
|
|
// instead of the host operating system's file system.
|
|
// It accepts a list of glob patterns.
|
|
// (Note that most file names serve as glob patterns matching only themselves.)
|
|
func ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
|
|
return parseFS(nil, fsys, patterns)
|
|
}
|
|
|
|
// ParseFS is like ParseFiles or ParseGlob but reads from the file system fsys
|
|
// instead of the host operating system's file system.
|
|
// It accepts a list of glob patterns.
|
|
// (Note that most file names serve as glob patterns matching only themselves.)
|
|
func (t *Template) ParseFS(fsys fs.FS, patterns ...string) (*Template, error) {
|
|
// Ensure template is inited
|
|
t.Option()
|
|
|
|
return parseFS(t, fsys, patterns)
|
|
}
|
|
|
|
// HTMLEscape writes to w the escaped HTML equivalent of the plain text data b.
|
|
func HTMLEscape(w io.Writer, b []byte) {
|
|
template.HTMLEscape(w, b)
|
|
}
|
|
|
|
// HTMLEscapeString returns the escaped HTML equivalent of the plain text data s.
|
|
func HTMLEscapeString(s string) string {
|
|
return template.HTMLEscapeString(s)
|
|
}
|
|
|
|
// HTMLEscaper returns the escaped HTML equivalent of the textual
|
|
// representation of its arguments.
|
|
func HTMLEscaper(args ...interface{}) string {
|
|
return template.HTMLEscaper(args)
|
|
}
|
|
|
|
// IsTrue reports whether the value is 'true', in the sense of not the zero of its type,
|
|
// and whether the value has a meaningful truth value. This is the definition of
|
|
// truth used by if and other such actions.
|
|
func IsTrue(val interface{}) (truth, ok bool) {
|
|
return template.IsTrue(val)
|
|
}
|
|
|
|
// JSEscape writes to w the escaped JavaScript equivalent of the plain text data b.
|
|
func JSEscape(w io.Writer, b []byte) {
|
|
template.JSEscape(w, b)
|
|
}
|
|
|
|
// JSEscapeString returns the escaped JavaScript equivalent of the plain text data s.
|
|
func JSEscapeString(s string) string {
|
|
return template.JSEscapeString(s)
|
|
}
|
|
|
|
// JSEscaper returns the escaped JavaScript equivalent of the textual
|
|
// representation of its arguments.
|
|
func JSEscaper(args ...interface{}) string {
|
|
return template.JSEscaper(args)
|
|
}
|
|
|
|
// URLQueryEscaper returns the escaped value of the textual representation of
|
|
// its arguments in a form suitable for embedding in a URL query.
|
|
func URLQueryEscaper(args ...interface{}) string {
|
|
return template.URLQueryEscaper(args)
|
|
}
|