github.com/insionng/yougam@v0.0.0-20170714101924-2bc18d833463/libraries/flosch/pongo2.v3/template_sets.go (about)

     1  package pongo2
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"os"
     8  	"path/filepath"
     9  	"sync"
    10  )
    11  
    12  // A template set allows you to create your own group of templates with their own global context (which is shared
    13  // among all members of the set), their own configuration (like a specific base directory) and their own sandbox.
    14  // It's useful for a separation of different kind of templates (e. g. web templates vs. mail templates).
    15  type TemplateSet struct {
    16  	name string
    17  
    18  	// Globals will be provided to all templates created within this template set
    19  	Globals Context
    20  
    21  	// If debug is true (default false), ExecutionContext.Logf() will work and output to STDOUT. Furthermore,
    22  	// FromCache() won't cache the templates. Make sure to synchronize the access to it in case you're changing this
    23  	// variable during program execution (and template compilation/execution).
    24  	Debug bool
    25  
    26  	// Base directory: If you set the base directory (string is non-empty), all filename lookups in tags/filters are
    27  	// relative to this directory. If it's empty, all lookups are relative to the current filename which is importing.
    28  	baseDirectory string
    29  
    30  	// Sandbox features
    31  	// - Limit access to directories (using SandboxDirectories)
    32  	// - Disallow access to specific tags and/or filters (using BanTag() and BanFilter())
    33  	//
    34  	// You can limit file accesses (for all tags/filters which are using pongo2's file resolver technique)
    35  	// to these sandbox directories. All default pongo2 filters/tags are respecting these restrictions.
    36  	// For example, if you only have your base directory in the list, a {% ssi "/etc/passwd" %} will not work.
    37  	// No items in SandboxDirectories means no restrictions at all.
    38  	//
    39  	// For efficiency reasons you can ban tags/filters only *before* you have added your first
    40  	// template to the set (restrictions are statically checked). After you added one, it's not possible anymore
    41  	// (for your personal security).
    42  	//
    43  	// SandboxDirectories can be changed at runtime. Please synchronize the access to it if you need to change it
    44  	// after you've added your first template to the set. You *must* use this match pattern for your directories:
    45  	// http://yougam/libraries/pkg/path/filepath/#Match
    46  	SandboxDirectories   []string
    47  	firstTemplateCreated bool
    48  	bannedTags           map[string]bool
    49  	bannedFilters        map[string]bool
    50  
    51  	// Template cache (for FromCache())
    52  	templateCache      map[string]*Template
    53  	templateCacheMutex sync.Mutex
    54  }
    55  
    56  // Create your own template sets to separate different kind of templates (e. g. web from mail templates) with
    57  // different globals or other configurations (like base directories).
    58  func NewSet(name string) *TemplateSet {
    59  	return &TemplateSet{
    60  		name:          name,
    61  		Globals:       make(Context),
    62  		bannedTags:    make(map[string]bool),
    63  		bannedFilters: make(map[string]bool),
    64  		templateCache: make(map[string]*Template),
    65  	}
    66  }
    67  
    68  // Use this function to set your template set's base directory. This directory will be used for any relative
    69  // path in filters, tags and From*-functions to determine your template.
    70  func (set *TemplateSet) SetBaseDirectory(name string) error {
    71  	// Make the path absolute
    72  	if !filepath.IsAbs(name) {
    73  		abs, err := filepath.Abs(name)
    74  		if err != nil {
    75  			return err
    76  		}
    77  		name = abs
    78  	}
    79  
    80  	// Check for existence
    81  	fi, err := os.Stat(name)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	if !fi.IsDir() {
    86  		return fmt.Errorf("The given path '%s' is not a directory.")
    87  	}
    88  
    89  	set.baseDirectory = name
    90  	return nil
    91  }
    92  
    93  func (set *TemplateSet) BaseDirectory() string {
    94  	return set.baseDirectory
    95  }
    96  
    97  // Ban a specific tag for this template set. See more in the documentation for TemplateSet.
    98  func (set *TemplateSet) BanTag(name string) {
    99  	_, has := tags[name]
   100  	if !has {
   101  		panic(fmt.Sprintf("Tag '%s' not found.", name))
   102  	}
   103  	if set.firstTemplateCreated {
   104  		panic("You cannot ban any tags after you've added your first template to your template set.")
   105  	}
   106  	_, has = set.bannedTags[name]
   107  	if has {
   108  		panic(fmt.Sprintf("Tag '%s' is already banned.", name))
   109  	}
   110  	set.bannedTags[name] = true
   111  }
   112  
   113  // Ban a specific filter for this template set. See more in the documentation for TemplateSet.
   114  func (set *TemplateSet) BanFilter(name string) {
   115  	_, has := filters[name]
   116  	if !has {
   117  		panic(fmt.Sprintf("Filter '%s' not found.", name))
   118  	}
   119  	if set.firstTemplateCreated {
   120  		panic("You cannot ban any filters after you've added your first template to your template set.")
   121  	}
   122  	_, has = set.bannedFilters[name]
   123  	if has {
   124  		panic(fmt.Sprintf("Filter '%s' is already banned.", name))
   125  	}
   126  	set.bannedFilters[name] = true
   127  }
   128  
   129  // FromCache() is a convenient method to cache templates. It is thread-safe
   130  // and will only compile the template associated with a filename once.
   131  // If TemplateSet.Debug is true (for example during development phase),
   132  // FromCache() will not cache the template and instead recompile it on any
   133  // call (to make changes to a template live instantaneously).
   134  // Like FromFile(), FromCache() takes a relative path to a set base directory.
   135  // Sandbox restrictions apply (if given).
   136  func (set *TemplateSet) FromCache(filename string) (*Template, error) {
   137  	if set.Debug {
   138  		// Recompile on any request
   139  		return set.FromFile(filename)
   140  	} else {
   141  		// Cache the template
   142  		cleaned_filename := set.resolveFilename(nil, filename)
   143  
   144  		set.templateCacheMutex.Lock()
   145  		defer set.templateCacheMutex.Unlock()
   146  
   147  		tpl, has := set.templateCache[cleaned_filename]
   148  
   149  		// Cache miss
   150  		if !has {
   151  			tpl, err := set.FromFile(cleaned_filename)
   152  			if err != nil {
   153  				return nil, err
   154  			}
   155  			set.templateCache[cleaned_filename] = tpl
   156  			return tpl, nil
   157  		}
   158  
   159  		// Cache hit
   160  		return tpl, nil
   161  	}
   162  }
   163  
   164  // Loads  a template from string and returns a Template instance.
   165  func (set *TemplateSet) FromString(tpl string) (*Template, error) {
   166  	set.firstTemplateCreated = true
   167  
   168  	return newTemplateString(set, tpl)
   169  }
   170  
   171  // Loads a template from a filename and returns a Template instance.
   172  // If a base directory is set, the filename must be either relative to it
   173  // or be an absolute path. Sandbox restrictions (SandboxDirectories) apply
   174  // if given.
   175  func (set *TemplateSet) FromFile(filename string) (*Template, error) {
   176  	set.firstTemplateCreated = true
   177  
   178  	buf, err := ioutil.ReadFile(set.resolveFilename(nil, filename))
   179  	if err != nil {
   180  		return nil, &Error{
   181  			Filename: filename,
   182  			Sender:   "fromfile",
   183  			ErrorMsg: err.Error(),
   184  		}
   185  	}
   186  	return newTemplate(set, filename, false, string(buf))
   187  }
   188  
   189  // Shortcut; renders a template string directly. Panics when providing a
   190  // malformed template or an error occurs during execution.
   191  func (set *TemplateSet) RenderTemplateString(s string, ctx Context) string {
   192  	set.firstTemplateCreated = true
   193  
   194  	tpl := Must(set.FromString(s))
   195  	result, err := tpl.Execute(ctx)
   196  	if err != nil {
   197  		panic(err)
   198  	}
   199  	return result
   200  }
   201  
   202  // Shortcut; renders a template file directly. Panics when providing a
   203  // malformed template or an error occurs during execution.
   204  func (set *TemplateSet) RenderTemplateFile(fn string, ctx Context) string {
   205  	set.firstTemplateCreated = true
   206  
   207  	tpl := Must(set.FromFile(fn))
   208  	result, err := tpl.Execute(ctx)
   209  	if err != nil {
   210  		panic(err)
   211  	}
   212  	return result
   213  }
   214  
   215  func (set *TemplateSet) logf(format string, args ...interface{}) {
   216  	if set.Debug {
   217  		logger.Printf(fmt.Sprintf("[template set: %s] %s", set.name, format), args...)
   218  	}
   219  }
   220  
   221  // Resolves a filename relative to the base directory. Absolute paths are allowed.
   222  // If sandbox restrictions are given (SandboxDirectories), they will be respected and checked.
   223  // On sandbox restriction violation, resolveFilename() panics.
   224  func (set *TemplateSet) resolveFilename(tpl *Template, filename string) (resolved_path string) {
   225  	if len(set.SandboxDirectories) > 0 {
   226  		defer func() {
   227  			// Remove any ".." or other crap
   228  			resolved_path = filepath.Clean(resolved_path)
   229  
   230  			// Make the path absolute
   231  			abs_path, err := filepath.Abs(resolved_path)
   232  			if err != nil {
   233  				panic(err)
   234  			}
   235  			resolved_path = abs_path
   236  
   237  			// Check against the sandbox directories (once one pattern matches, we're done and can allow it)
   238  			for _, pattern := range set.SandboxDirectories {
   239  				matched, err := filepath.Match(pattern, resolved_path)
   240  				if err != nil {
   241  					panic("Wrong sandbox directory match pattern (see http://yougam/libraries/pkg/path/filepath/#Match).")
   242  				}
   243  				if matched {
   244  					// OK!
   245  					return
   246  				}
   247  			}
   248  
   249  			// No pattern matched, we have to log+deny the request
   250  			set.logf("Access attempt outside of the sandbox directories (blocked): '%s'", resolved_path)
   251  			resolved_path = ""
   252  		}()
   253  	}
   254  
   255  	if filepath.IsAbs(filename) {
   256  		return filename
   257  	}
   258  
   259  	if set.baseDirectory == "" {
   260  		if tpl != nil {
   261  			if tpl.is_tpl_string {
   262  				return filename
   263  			}
   264  			base := filepath.Dir(tpl.name)
   265  			return filepath.Join(base, filename)
   266  		}
   267  		return filename
   268  	} else {
   269  		return filepath.Join(set.baseDirectory, filename)
   270  	}
   271  }
   272  
   273  // Logging function (internally used)
   274  func logf(format string, items ...interface{}) {
   275  	if debug {
   276  		logger.Printf(format, items...)
   277  	}
   278  }
   279  
   280  var (
   281  	debug  bool // internal debugging
   282  	logger = log.New(os.Stdout, "[pongo2] ", log.LstdFlags)
   283  
   284  	// Creating a default set
   285  	DefaultSet = NewSet("default")
   286  
   287  	// Methods on the default set
   288  	FromString           = DefaultSet.FromString
   289  	FromFile             = DefaultSet.FromFile
   290  	FromCache            = DefaultSet.FromCache
   291  	RenderTemplateString = DefaultSet.RenderTemplateString
   292  	RenderTemplateFile   = DefaultSet.RenderTemplateFile
   293  
   294  	// Globals for the default set
   295  	Globals = DefaultSet.Globals
   296  )