// Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved. // Revel Framework source code and usage is governed by a MIT style // license that can be found in the LICENSE file. package controllers import ( "os" fpath "path/filepath" "strings" "syscall" "github.com/revel/revel" ) // Static file serving controller type Static struct { *revel.Controller } // Serve method handles requests for files. The supplied prefix may be absolute // or relative. If the prefix is relative it is assumed to be relative to the // application directory. The filepath may either be just a file or an // additional filepath to search for the given file. This response may return // the following responses in the event of an error or invalid request; // 403(Forbidden): If the prefix filepath combination results in a directory. // 404(Not found): If the prefix and filepath combination results in a non-existent file. // 500(Internal Server Error): There are a few edge cases that would likely indicate some configuration error outside of revel. // // Note that when defining routes in routes/conf the parameters must not have // spaces around the comma. // Bad: Static.Serve("public/img", "favicon.png") // Good: Static.Serve("public/img","favicon.png") // // Examples: // Serving a directory // Route (conf/routes): // GET /public/{<.*>filepath} Static.Serve("public") // Request: // public/js/sessvars.js // Calls // Static.Serve("public","js/sessvars.js") // // Serving a file // Route (conf/routes): // GET /favicon.ico Static.Serve("public/img","favicon.png") // Request: // favicon.ico // Calls: // Static.Serve("public/img", "favicon.png") func (c Static) Serve(prefix, filepath string) revel.Result { // Fix for #503. prefix = c.Params.Fixed.Get("prefix") if prefix == "" { return c.NotFound("") } return serve(c, prefix, filepath) } // ServeModule method allows modules to serve binary files. The parameters are the same // as Static.Serve with the additional module name pre-pended to the list of // arguments. func (c Static) ServeModule(moduleName, prefix, filepath string) revel.Result { // Fix for #503. prefix = c.Params.Fixed.Get("prefix") if prefix == "" { return c.NotFound("") } var basePath string for _, module := range revel.Modules { if module.Name == moduleName { basePath = module.Path } } absPath := fpath.Join(basePath, fpath.FromSlash(prefix)) return serve(c, absPath, filepath) } // This method allows static serving of application files in a verified manner. func serve(c Static, prefix, filepath string) revel.Result { var basePath string if !fpath.IsAbs(prefix) { basePath = revel.BasePath } basePathPrefix := fpath.Join(basePath, fpath.FromSlash(prefix)) fname := fpath.Join(basePathPrefix, fpath.FromSlash(filepath)) // Verify the request file path is within the application's scope of access if !strings.HasPrefix(fname, basePathPrefix) { c.Log.Warn("Attempted to read file outside of base path", "file", fname) return c.NotFound("") } // Verify file path is accessible finfo, err := os.Stat(fname) if err != nil { if os.IsNotExist(err) || err.(*os.PathError).Err == syscall.ENOTDIR { c.Log.Warn("File not found ", "file", fname, "error", err) return c.NotFound("File not found") } c.Log.Error("Error trying to get fileinfo for", "file", fname, "error", err) return c.RenderError(err) } // Disallow directory listing if finfo.Mode().IsDir() { c.Log.Warn("Attempted directory listing of ", "dir", fname) return c.Forbidden("Directory listing not allowed") } // Open request file path file, err := os.Open(fname) if err != nil { if os.IsNotExist(err) { c.Log.Warn("File not found", "file", fname, "error", err) return c.NotFound("File not found") } c.Log.Error("Error opening", "file", fname, "error", err) return c.RenderError(err) } return c.RenderFile(file, revel.Inline) }