// 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 revel import ( "bytes" "fmt" "io" "io/ioutil" "net" "net/http" "net/url" "os" "path/filepath" "reflect" "regexp" "strings" "github.com/revel/config" ) const ( // DefaultFileContentType Revel's default response content type DefaultFileContentType = "application/octet-stream" ) var ( cookieKeyValueParser = regexp.MustCompile("\x00([^:]*):([^\x00]*)\x00") HdrForwardedFor = http.CanonicalHeaderKey("X-Forwarded-For") HdrRealIP = http.CanonicalHeaderKey("X-Real-Ip") utilLog = RevelLog.New("section", "util") mimeConfig *config.Context ) // ExecutableTemplate adds some more methods to the default Template. type ExecutableTemplate interface { Execute(io.Writer, interface{}) error } // ExecuteTemplate execute a template and returns the result as a string. func ExecuteTemplate(tmpl ExecutableTemplate, data interface{}) string { var b bytes.Buffer if err := tmpl.Execute(&b, data); err != nil { utilLog.Error("ExecuteTemplate: Execute failed", "error", err) } return b.String() } // MustReadLines reads the lines of the given file. Panics in the case of error. func MustReadLines(filename string) []string { r, err := ReadLines(filename) if err != nil { panic(err) } return r } // ReadLines reads the lines of the given file. Panics in the case of error. func ReadLines(filename string) ([]string, error) { dataBytes, err := ioutil.ReadFile(filename) if err != nil { return nil, err } return strings.Split(string(dataBytes), "\n"), nil } func ContainsString(list []string, target string) bool { for _, el := range list { if el == target { return true } } return false } // FindMethod returns the reflect.Method, given a Receiver type and Func value. func FindMethod(recvType reflect.Type, funcVal reflect.Value) *reflect.Method { // It is not possible to get the name of the method from the Func. // Instead, compare it to each method of the Controller. for i := 0; i < recvType.NumMethod(); i++ { method := recvType.Method(i) if method.Func.Pointer() == funcVal.Pointer() { return &method } } return nil } // ParseKeyValueCookie takes the raw (escaped) cookie value and parses out key values. func ParseKeyValueCookie(val string, cb func(key, val string)) { val, _ = url.QueryUnescape(val) if matches := cookieKeyValueParser.FindAllStringSubmatch(val, -1); matches != nil { for _, match := range matches { cb(match[1], match[2]) } } } // LoadMimeConfig load mime-types.conf on init. func LoadMimeConfig() { var err error mimeConfig, err = config.LoadContext("mime-types.conf", ConfPaths) if err != nil { utilLog.Fatal("Failed to load mime type config:", "error", err) } } // ContentTypeByFilename returns a MIME content type based on the filename's extension. // If no appropriate one is found, returns "application/octet-stream" by default. // Additionally, specifies the charset as UTF-8 for text/* types. func ContentTypeByFilename(filename string) string { dot := strings.LastIndex(filename, ".") if dot == -1 || dot+1 >= len(filename) { return DefaultFileContentType } extension := filename[dot+1:] contentType := mimeConfig.StringDefault(extension, "") if contentType == "" { return DefaultFileContentType } if strings.HasPrefix(contentType, "text/") { return contentType + "; charset=utf-8" } return contentType } // DirExists returns true if the given path exists and is a directory. func DirExists(filename string) bool { fileInfo, err := os.Stat(filename) return err == nil && fileInfo.IsDir() } func FirstNonEmpty(strs ...string) string { for _, str := range strs { if len(str) > 0 { return str } } return "" } // Equal is a helper for comparing value equality, following these rules: // - Values with equivalent types are compared with reflect.DeepEqual // - int, uint, and float values are compared without regard to the type width. // for example, Equal(int32(5), int64(5)) == true // - strings and byte slices are converted to strings before comparison. // - else, return false. func Equal(a, b interface{}) bool { if reflect.TypeOf(a) == reflect.TypeOf(b) { return reflect.DeepEqual(a, b) } switch a.(type) { case int, int8, int16, int32, int64: switch b.(type) { case int, int8, int16, int32, int64: return reflect.ValueOf(a).Int() == reflect.ValueOf(b).Int() } case uint, uint8, uint16, uint32, uint64: switch b.(type) { case uint, uint8, uint16, uint32, uint64: return reflect.ValueOf(a).Uint() == reflect.ValueOf(b).Uint() } case float32, float64: switch b.(type) { case float32, float64: return reflect.ValueOf(a).Float() == reflect.ValueOf(b).Float() } case string: switch b.(type) { case []byte: return a.(string) == string(b.([]byte)) } case []byte: switch b.(type) { case string: return b.(string) == string(a.([]byte)) } } return false } // ClientIP method returns client IP address from HTTP request. // // Note: Set property "app.behind.proxy" to true only if Revel is running // behind proxy like nginx, haproxy, apache, etc. Otherwise // you may get inaccurate Client IP address. Revel parses the // IP address in the order of X-Forwarded-For, X-Real-IP. // // By default revel will get http.Request's RemoteAddr func ClientIP(r *Request) string { if Config.BoolDefault("app.behind.proxy", false) { // Header X-Forwarded-For if fwdFor := strings.TrimSpace(r.GetHttpHeader(HdrForwardedFor)); fwdFor != "" { index := strings.Index(fwdFor, ",") if index == -1 { return fwdFor } return fwdFor[:index] } // Header X-Real-Ip if realIP := strings.TrimSpace(r.GetHttpHeader(HdrRealIP)); realIP != "" { return realIP } } if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil { return remoteAddr } return "" } // Walk method extends filepath.Walk to also follow symlinks. // Always returns the path of the file or directory. func Walk(root string, walkFn filepath.WalkFunc) error { return fsWalk(root, root, walkFn) } // createDir method creates nested directories if not exists func createDir(path string) error { if _, err := os.Stat(path); err != nil { if os.IsNotExist(err) { if err = os.MkdirAll(path, 0755); err != nil { return fmt.Errorf("Failed to create directory '%v': %v", path, err) } } else { return fmt.Errorf("Failed to create directory '%v': %v", path, err) } } return nil } func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error { fsWalkFunc := func(path string, info os.FileInfo, err error) error { if err != nil { return err } var name string name, err = filepath.Rel(fname, path) if err != nil { return err } path = filepath.Join(linkName, name) if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink { var symlinkPath string symlinkPath, err = filepath.EvalSymlinks(path) if err != nil { return err } // https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392 info, err = os.Lstat(symlinkPath) if err != nil { return walkFn(path, info, err) } if info.IsDir() { return fsWalk(symlinkPath, path, walkFn) } } return walkFn(path, info, err) } err := filepath.Walk(fname, fsWalkFunc) return err } func init() { OnAppStart(LoadMimeConfig) }