Add API Framework Revel Source Files
[iec.git] / src / foundation / api / revel / util.go
1 // Copyright (c) 2012-2016 The Revel Framework Authors, All rights reserved.
2 // Revel Framework source code and usage is governed by a MIT style
3 // license that can be found in the LICENSE file.
4
5 package revel
6
7 import (
8         "bytes"
9         "fmt"
10         "io"
11         "io/ioutil"
12         "net"
13         "net/http"
14         "net/url"
15         "os"
16         "path/filepath"
17         "reflect"
18         "regexp"
19         "strings"
20
21         "github.com/revel/config"
22 )
23
24 const (
25         // DefaultFileContentType Revel's default response content type
26         DefaultFileContentType = "application/octet-stream"
27 )
28
29 var (
30         cookieKeyValueParser = regexp.MustCompile("\x00([^:]*):([^\x00]*)\x00")
31         HdrForwardedFor      = http.CanonicalHeaderKey("X-Forwarded-For")
32         HdrRealIP            = http.CanonicalHeaderKey("X-Real-Ip")
33         utilLog              = RevelLog.New("section", "util")
34         mimeConfig           *config.Context
35 )
36
37 // ExecutableTemplate adds some more methods to the default Template.
38 type ExecutableTemplate interface {
39         Execute(io.Writer, interface{}) error
40 }
41
42 // ExecuteTemplate execute a template and returns the result as a string.
43 func ExecuteTemplate(tmpl ExecutableTemplate, data interface{}) string {
44         var b bytes.Buffer
45         if err := tmpl.Execute(&b, data); err != nil {
46                 utilLog.Error("ExecuteTemplate: Execute failed", "error", err)
47         }
48         return b.String()
49 }
50
51 // MustReadLines reads the lines of the given file.  Panics in the case of error.
52 func MustReadLines(filename string) []string {
53         r, err := ReadLines(filename)
54         if err != nil {
55                 panic(err)
56         }
57         return r
58 }
59
60 // ReadLines reads the lines of the given file.  Panics in the case of error.
61 func ReadLines(filename string) ([]string, error) {
62         dataBytes, err := ioutil.ReadFile(filename)
63         if err != nil {
64                 return nil, err
65         }
66         return strings.Split(string(dataBytes), "\n"), nil
67 }
68
69 func ContainsString(list []string, target string) bool {
70         for _, el := range list {
71                 if el == target {
72                         return true
73                 }
74         }
75         return false
76 }
77
78 // FindMethod returns the reflect.Method, given a Receiver type and Func value.
79 func FindMethod(recvType reflect.Type, funcVal reflect.Value) *reflect.Method {
80         // It is not possible to get the name of the method from the Func.
81         // Instead, compare it to each method of the Controller.
82         for i := 0; i < recvType.NumMethod(); i++ {
83                 method := recvType.Method(i)
84                 if method.Func.Pointer() == funcVal.Pointer() {
85                         return &method
86                 }
87         }
88         return nil
89 }
90
91 // ParseKeyValueCookie takes the raw (escaped) cookie value and parses out key values.
92 func ParseKeyValueCookie(val string, cb func(key, val string)) {
93         val, _ = url.QueryUnescape(val)
94         if matches := cookieKeyValueParser.FindAllStringSubmatch(val, -1); matches != nil {
95                 for _, match := range matches {
96                         cb(match[1], match[2])
97                 }
98         }
99 }
100
101 // LoadMimeConfig load mime-types.conf on init.
102 func LoadMimeConfig() {
103         var err error
104         mimeConfig, err = config.LoadContext("mime-types.conf", ConfPaths)
105         if err != nil {
106                 utilLog.Fatal("Failed to load mime type config:", "error", err)
107         }
108 }
109
110 // ContentTypeByFilename returns a MIME content type based on the filename's extension.
111 // If no appropriate one is found, returns "application/octet-stream" by default.
112 // Additionally, specifies the charset as UTF-8 for text/* types.
113 func ContentTypeByFilename(filename string) string {
114         dot := strings.LastIndex(filename, ".")
115         if dot == -1 || dot+1 >= len(filename) {
116                 return DefaultFileContentType
117         }
118
119         extension := filename[dot+1:]
120         contentType := mimeConfig.StringDefault(extension, "")
121         if contentType == "" {
122                 return DefaultFileContentType
123         }
124
125         if strings.HasPrefix(contentType, "text/") {
126                 return contentType + "; charset=utf-8"
127         }
128
129         return contentType
130 }
131
132 // DirExists returns true if the given path exists and is a directory.
133 func DirExists(filename string) bool {
134         fileInfo, err := os.Stat(filename)
135         return err == nil && fileInfo.IsDir()
136 }
137
138 func FirstNonEmpty(strs ...string) string {
139         for _, str := range strs {
140                 if len(str) > 0 {
141                         return str
142                 }
143         }
144         return ""
145 }
146
147 // Equal is a helper for comparing value equality, following these rules:
148 //  - Values with equivalent types are compared with reflect.DeepEqual
149 //  - int, uint, and float values are compared without regard to the type width.
150 //    for example, Equal(int32(5), int64(5)) == true
151 //  - strings and byte slices are converted to strings before comparison.
152 //  - else, return false.
153 func Equal(a, b interface{}) bool {
154         if reflect.TypeOf(a) == reflect.TypeOf(b) {
155                 return reflect.DeepEqual(a, b)
156         }
157         switch a.(type) {
158         case int, int8, int16, int32, int64:
159                 switch b.(type) {
160                 case int, int8, int16, int32, int64:
161                         return reflect.ValueOf(a).Int() == reflect.ValueOf(b).Int()
162                 }
163         case uint, uint8, uint16, uint32, uint64:
164                 switch b.(type) {
165                 case uint, uint8, uint16, uint32, uint64:
166                         return reflect.ValueOf(a).Uint() == reflect.ValueOf(b).Uint()
167                 }
168         case float32, float64:
169                 switch b.(type) {
170                 case float32, float64:
171                         return reflect.ValueOf(a).Float() == reflect.ValueOf(b).Float()
172                 }
173         case string:
174                 switch b.(type) {
175                 case []byte:
176                         return a.(string) == string(b.([]byte))
177                 }
178         case []byte:
179                 switch b.(type) {
180                 case string:
181                         return b.(string) == string(a.([]byte))
182                 }
183         }
184         return false
185 }
186
187 // ClientIP method returns client IP address from HTTP request.
188 //
189 // Note: Set property "app.behind.proxy" to true only if Revel is running
190 // behind proxy like nginx, haproxy, apache, etc. Otherwise
191 // you may get inaccurate Client IP address. Revel parses the
192 // IP address in the order of X-Forwarded-For, X-Real-IP.
193 //
194 // By default revel will get http.Request's RemoteAddr
195 func ClientIP(r *Request) string {
196         if Config.BoolDefault("app.behind.proxy", false) {
197                 // Header X-Forwarded-For
198                 if fwdFor := strings.TrimSpace(r.GetHttpHeader(HdrForwardedFor)); fwdFor != "" {
199                         index := strings.Index(fwdFor, ",")
200                         if index == -1 {
201                                 return fwdFor
202                         }
203                         return fwdFor[:index]
204                 }
205
206                 // Header X-Real-Ip
207                 if realIP := strings.TrimSpace(r.GetHttpHeader(HdrRealIP)); realIP != "" {
208                         return realIP
209                 }
210         }
211
212         if remoteAddr, _, err := net.SplitHostPort(r.RemoteAddr); err == nil {
213                 return remoteAddr
214         }
215
216         return ""
217 }
218
219 // Walk method extends filepath.Walk to also follow symlinks.
220 // Always returns the path of the file or directory.
221 func Walk(root string, walkFn filepath.WalkFunc) error {
222         return fsWalk(root, root, walkFn)
223 }
224
225 // createDir method creates nested directories if not exists
226 func createDir(path string) error {
227         if _, err := os.Stat(path); err != nil {
228                 if os.IsNotExist(err) {
229                         if err = os.MkdirAll(path, 0755); err != nil {
230                                 return fmt.Errorf("Failed to create directory '%v': %v", path, err)
231                         }
232                 } else {
233                         return fmt.Errorf("Failed to create directory '%v': %v", path, err)
234                 }
235         }
236         return nil
237 }
238
239 func fsWalk(fname string, linkName string, walkFn filepath.WalkFunc) error {
240         fsWalkFunc := func(path string, info os.FileInfo, err error) error {
241                 if err != nil {
242                         return err
243                 }
244
245                 var name string
246                 name, err = filepath.Rel(fname, path)
247                 if err != nil {
248                         return err
249                 }
250
251                 path = filepath.Join(linkName, name)
252
253                 if err == nil && info.Mode()&os.ModeSymlink == os.ModeSymlink {
254                         var symlinkPath string
255                         symlinkPath, err = filepath.EvalSymlinks(path)
256                         if err != nil {
257                                 return err
258                         }
259
260                         // https://github.com/golang/go/blob/master/src/path/filepath/path.go#L392
261                         info, err = os.Lstat(symlinkPath)
262
263                         if err != nil {
264                                 return walkFn(path, info, err)
265                         }
266
267                         if info.IsDir() {
268                                 return fsWalk(symlinkPath, path, walkFn)
269                         }
270                 }
271
272                 return walkFn(path, info, err)
273         }
274         err := filepath.Walk(fname, fsWalkFunc)
275         return err
276 }
277
278 func init() {
279         OnAppStart(LoadMimeConfig)
280 }