forked from caddyserver/caddy-lua
/
lua.go
201 lines (173 loc) · 4.21 KB
/
lua.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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
package lua
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net/http"
"os"
"path/filepath"
"github.com/mholt/caddy/config/setup"
"github.com/mholt/caddy/middleware"
"github.com/mholt/caddy/middleware/browse"
"github.com/yuin/gopher-lua"
)
func Setup(c *setup.Controller) (middleware.Middleware, error) {
root := c.Root
rules, err := parse(c)
if err != nil {
return nil, err
}
return func(next middleware.Handler) middleware.Handler {
return &Handler{
Next: next,
Rules: rules,
Root: root,
FileSys: http.Dir(root),
}
}, nil
}
type Handler struct {
Next middleware.Handler
Rules []Rule
Root string // site root
FileSys http.FileSystem
}
func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
for _, rule := range h.Rules {
if !middleware.Path(r.URL.Path).Matches(rule.BasePath) {
continue
}
// Check for index file
fpath := r.URL.Path
if idx, ok := middleware.IndexFile(h.FileSys, fpath, browse.IndexPages); ok {
fpath = idx
}
// TODO: Check extension. If .lua, assume whole file is Lua script.
file, err := h.FileSys.Open(filepath.Join(h.Root, fpath))
if err != nil {
if os.IsNotExist(err) {
return http.StatusNotFound, nil
} else if os.IsPermission(err) {
return http.StatusForbidden, nil
}
return http.StatusInternalServerError, err
}
defer file.Close()
contents, err := ioutil.ReadAll(file)
if err != nil {
return http.StatusInternalServerError, err
}
var out bytes.Buffer
if err := Interpret(&out, contents); err != nil {
return http.StatusInternalServerError, err
}
// Write the combined text to the http.ResponseWriter
w.Write(out.Bytes())
return http.StatusOK, nil
}
return h.Next.ServeHTTP(w, r)
}
// Interpret reads a source, executes any Lua, and writes the results.
//
// This assumes that the reader has Lua embedded in `<?lua ... ?>` sections.
func Interpret(out io.Writer, src []byte) error {
L := lua.NewState()
defer L.Close()
var luaOut bytes.Buffer
var luaIn bytes.Buffer
// TODO: If a user uses any concurrent processing here, do we
// need to add a lock to the buffer?
L.SetGlobal("print", L.NewFunction(func(L *lua.LState) int {
top := L.GetTop()
for i := 1; i <= top; i++ {
luaOut.WriteString(L.Get(i).String())
if i != top {
luaOut.WriteString(" ")
}
}
luaOut.WriteString("\n")
return 0
}))
inCode := false
line := 1
for i := 0; i < len(src); i++ {
if src[i] == '\n' {
line++
}
if inCode {
if isEnd(i, src) {
//fmt.Println("Sending to Lua interpreter:", luaIn.String())
i++ // Skip two characters: ? and >
if err := L.DoString(luaIn.String()); err != nil {
// TODO: Need to make it easy to tell that this is a
// parse error.
return fmt.Errorf("Lua Error (Line %d): %s", line, err)
}
out.Write(luaOut.Bytes())
luaIn.Reset()
luaOut.Reset()
inCode = false
} else {
luaIn.WriteByte(src[i])
}
} else {
if isStart(i, src) {
i += 4
inCode = true
} else if _, err := out.Write([]byte{src[i]}); err != nil {
return err
}
}
}
// Handle the case where a file ends inside of a <?lua block.
// Mimic PHP's behavior.
if inCode && luaIn.Len() > 0 {
fmt.Printf("sending to Lua interpreter: %s", luaIn.String())
if err := L.DoString(luaIn.String()); err != nil {
// TODO: Need to make it easy to tell that this is a
// parse error.
return fmt.Errorf("Lua Error (Line %d): %s", line, err)
}
out.Write(luaOut.Bytes())
}
return nil
}
var startSeq = []byte{'<', '?', 'l', 'u', 'a'}
func isStart(start int, slice []byte) bool {
if start+5 >= len(slice) {
return false
}
for i := 0; i < 5; i++ {
if startSeq[i] != slice[start+i] {
return false
}
}
return true
}
func isEnd(start int, slice []byte) bool {
if start+1 >= len(slice) {
return false
}
if slice[start] == '?' && slice[start+1] == '>' {
return true
}
return false
}
func parse(c *setup.Controller) ([]Rule, error) {
var rules []Rule
for c.Next() {
r := Rule{BasePath: "/"}
if c.NextArg() {
r.BasePath = c.Val()
}
if c.NextArg() {
return rules, c.ArgErr()
}
rules = append(rules, r)
}
return rules, nil
}
type Rule struct {
BasePath string // base request path to match
}