-
Notifications
You must be signed in to change notification settings - Fork 3
/
static.go
135 lines (121 loc) · 3.79 KB
/
static.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package mars
import (
"fmt"
"os"
fpath "path/filepath"
"reflect"
"strings"
"syscall"
)
type Static struct {
*Controller
}
func init() {
RegisterController((*Static)(nil),
[]*MethodType{
{
Name: "ServeFresh",
Args: []*MethodArg{
{Name: "prefix", Type: reflect.TypeOf((*string)(nil))},
{Name: "filepath", Type: reflect.TypeOf((*string)(nil))},
},
},
{
Name: "Serve",
Args: []*MethodArg{
{Name: "prefix", Type: reflect.TypeOf((*string)(nil))},
{Name: "filepath", Type: reflect.TypeOf((*string)(nil))},
},
},
},
)
}
// This 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 mars.
//
// 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) ServeFresh(prefix, filepath string) Result {
// Fix for #503.
prefix = c.Params.Fixed.Get("prefix")
if prefix == "" {
return c.NotFound("")
}
return serve(c, prefix, filepath, -1)
}
func (c Static) Serve(prefix, filepath string) Result {
// Fix for #503.
prefix = c.Params.Fixed.Get("prefix")
if prefix == "" {
return c.NotFound("")
}
return serve(c, prefix, filepath, int(MaxAge.Seconds()))
}
// This method allows static serving of application files in a verified manner.
func serve(c Static, prefix, filepath string, maxAge int) Result {
var basePath string
if !fpath.IsAbs(prefix) {
basePath = 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) {
WARN.Printf("Attempted to read file outside of base path: %s", 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 {
WARN.Printf("File not found (%s): %s ", fname, err)
return c.NotFound("File not found")
}
ERROR.Printf("Error trying to get fileinfo for '%s': %s", fname, err)
return c.RenderError(err)
}
// Disallow directory listing
if finfo.Mode().IsDir() {
WARN.Printf("Attempted directory listing of %s", fname)
return c.Forbidden("Directory listing not allowed")
}
// Open request file path
file, err := os.Open(fname)
if err != nil {
if os.IsNotExist(err) {
WARN.Printf("File not found (%s): %s ", fname, err)
return c.NotFound("File not found")
}
ERROR.Printf("Error opening '%s': %s", fname, err)
return c.RenderError(err)
}
if maxAge > 0 {
c.Response.Out.Header().Add("Cache-Control", fmt.Sprintf("max-age=%d, must-revalidate", maxAge))
}
return c.RenderFile(file, Inline)
}