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 )