-
-
Notifications
You must be signed in to change notification settings - Fork 135
/
basic.go
391 lines (345 loc) · 11.3 KB
/
basic.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
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
package engine
import (
"bytes"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"time"
"github.com/russross/blackfriday/v2"
log "github.com/sirupsen/logrus"
"github.com/xyproto/algernon/lua/convert"
"github.com/xyproto/algernon/utils"
"github.com/xyproto/gopher-lua"
)
// FutureStatus is useful when redirecting in combination with writing to a
// buffer before writing to a client. May contain more fields in the future.
type FutureStatus struct {
code int // Buffered HTTP status code
}
// LoadBasicSystemFunctions loads functions related to logging, markdown and the
// current server directory into the given Lua state
func (ac *Config) LoadBasicSystemFunctions(L *lua.LState) {
// Return the version string
L.SetGlobal("version", L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(ac.versionString))
return 1 // number of results
}))
// Log text with the "Info" log type
L.SetGlobal("log", L.NewFunction(func(L *lua.LState) int {
buf := convert.Arguments2buffer(L, false)
// Log the combined text
log.Info(buf.String())
return 0 // number of results
}))
// Log text with the "Warn" log type
L.SetGlobal("warn", L.NewFunction(func(L *lua.LState) int {
buf := convert.Arguments2buffer(L, false)
// Log the combined text
log.Warn(buf.String())
return 0 // number of results
}))
// Log text with the "Error" log type
L.SetGlobal("err", L.NewFunction(func(L *lua.LState) int {
buf := convert.Arguments2buffer(L, false)
// Log the combined text
log.Error(buf.String())
return 0 // number of results
}))
// Sleep for the given number of seconds (can be a float)
L.SetGlobal("sleep", L.NewFunction(func(L *lua.LState) int {
// Extract the correct number of nanoseconds
duration := time.Duration(float64(L.ToNumber(1)) * 1000000000.0)
// Wait and block the current thread of execution.
time.Sleep(duration)
return 0
}))
// Return the current unixtime, with an attempt at nanosecond resolution
L.SetGlobal("unixnano", L.NewFunction(func(L *lua.LState) int {
// Extract the correct number of nanoseconds
L.Push(lua.LNumber(time.Now().UnixNano()))
return 1 // number of results
}))
// Convert Markdown to HTML
L.SetGlobal("markdown", L.NewFunction(func(L *lua.LState) int {
// Retrieve all the function arguments as a bytes.Buffer
buf := convert.Arguments2buffer(L, true)
// Convert the buffer to markdown and output the translated string
html := strings.TrimSpace(string(blackfriday.Run(buf.Bytes())))
L.Push(lua.LString(html))
return 1 // number of results
}))
// Get the full filename of a given file that is in the directory
// where the server is running (root directory for the server).
// If no filename is given, the directory where the server is
// currently running is returned.
L.SetGlobal("serverdir", L.NewFunction(func(L *lua.LState) int {
serverdir, err := os.Getwd()
if err != nil {
// Could not retrieve a directory
serverdir = ""
} else if L.GetTop() == 1 {
// Also include a separator and a filename
fn := L.ToString(1)
serverdir = filepath.Join(serverdir, fn)
}
L.Push(lua.LString(serverdir))
return 1 // number of results
}))
}
// LoadBasicWeb loads functions related to handling requests, outputting data to
// the browser, setting headers, pretty printing and dealing with the directory
// where files are being served, into the given Lua state.
func (ac *Config) LoadBasicWeb(w http.ResponseWriter, req *http.Request, L *lua.LState, filename string, flushFunc func(), httpStatus *FutureStatus) {
// Print text to the web page that is being served. Add a newline.
L.SetGlobal("print", L.NewFunction(func(L *lua.LState) int {
var buf bytes.Buffer
top := L.GetTop()
for i := 1; i <= top; i++ {
buf.WriteString(L.Get(i).String())
if i != top {
buf.WriteString("\t")
}
}
// Final newline
buf.WriteString("\n")
// Write the combined text to the http.ResponseWriter
buf.WriteTo(w)
return 0 // number of results
}))
// Pretty print text to the web page that is being served. Add a newline.
L.SetGlobal("pprint", L.NewFunction(func(L *lua.LState) int {
var buf bytes.Buffer
top := L.GetTop()
for i := 1; i <= top; i++ {
convert.PprintToWriter(&buf, L.Get(i))
if i != top {
buf.WriteString("\t")
}
}
// Final newline
buf.WriteString("\n")
// Write the combined text to the http.ResponseWriter
buf.WriteTo(w)
return 0 // number of results
}))
// Pretty print to string
L.SetGlobal("ppstr", L.NewFunction(func(L *lua.LState) int {
var buf bytes.Buffer
top := L.GetTop()
for i := 1; i <= top; i++ {
convert.PprintToWriter(&buf, L.Get(i))
if i != top {
buf.WriteString("\t")
}
}
// Return the string
L.Push(lua.LString(buf.String()))
return 1 // number of results
}))
// Flush the ResponseWriter.
// Needed in debug mode, where ResponseWriter is buffered.
L.SetGlobal("flush", L.NewFunction(func(L *lua.LState) int {
if flushFunc != nil {
flushFunc()
}
return 0 // number of results
}))
// Set the Content-Type for the page
L.SetGlobal("content", L.NewFunction(func(L *lua.LState) int {
lv := L.ToString(1)
w.Header().Add("Content-Type", lv)
return 0 // number of results
}))
// Return the current URL Path
L.SetGlobal("urlpath", L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(req.URL.Path))
return 1 // number of results
}))
// Return the current HTTP method (GET, POST etc)
L.SetGlobal("method", L.NewFunction(func(L *lua.LState) int {
L.Push(lua.LString(req.Method))
return 1 // number of results
}))
// Return the HTTP headers as a table
L.SetGlobal("headers", L.NewFunction(func(L *lua.LState) int {
luaTable := L.NewTable()
for key := range req.Header {
L.RawSet(luaTable, lua.LString(key), lua.LString(req.Header.Get(key)))
}
if req.Host != "" {
L.RawSet(luaTable, lua.LString("Host"), lua.LString(req.Host))
}
L.Push(luaTable)
return 1 // number of results
}))
// Return the HTTP header in the request, for a given key/string
L.SetGlobal("header", L.NewFunction(func(L *lua.LState) int {
key := L.ToString(1)
value := req.Header.Get(key)
L.Push(lua.LString(value))
return 1 // number of results
}))
// Set the HTTP header in the request, for a given key and value
L.SetGlobal("setheader", L.NewFunction(func(L *lua.LState) int {
key := L.ToString(1)
value := L.ToString(2)
w.Header().Set(key, value)
return 0 // number of results
}))
// Return the HTTP body in the request
L.SetGlobal("body", L.NewFunction(func(L *lua.LState) int {
body, err := ioutil.ReadAll(req.Body)
var result lua.LString
if err != nil {
result = lua.LString("")
} else {
result = lua.LString(string(body))
}
L.Push(result)
return 1 // number of results
}))
// Set the HTTP status code (must come before print)
L.SetGlobal("status", L.NewFunction(func(L *lua.LState) int {
code := int(L.ToNumber(1))
if httpStatus != nil {
httpStatus.code = code
}
w.WriteHeader(code)
return 0 // number of results
}))
// Throw an error/exception in Lua
L.SetGlobal("throw", L.GetGlobal("error"))
// Set a HTTP status code and print a message (optional)
L.SetGlobal("error", L.NewFunction(func(L *lua.LState) int {
code := int(L.ToNumber(1))
if httpStatus != nil {
httpStatus.code = code
}
w.WriteHeader(code)
if L.GetTop() == 2 {
message := L.ToString(2)
fmt.Fprint(w, message)
}
return 0 // number of results
}))
// Get the full filename of a given file that is in the directory
// of the script that is about to be run. If no filename is given,
// the directory of the script is returned.
L.SetGlobal("scriptdir", L.NewFunction(func(L *lua.LState) int {
scriptpath, err := filepath.Abs(filename)
if err != nil {
scriptpath = filename
}
scriptdir := filepath.Dir(scriptpath)
scriptpath = scriptdir
top := L.GetTop()
if top == 1 {
// Also include a separator and a filename
fn := L.ToString(1)
scriptpath = filepath.Join(scriptdir, fn)
}
// Now have the correct absolute scriptpath
L.Push(lua.LString(scriptpath))
return 1 // number of results
}))
// Given a filename, return the URL path
L.SetGlobal("file2url", L.NewFunction(func(L *lua.LState) int {
fn := L.ToString(1)
targetpath := strings.TrimPrefix(filepath.Join(filepath.Dir(filename), fn), ac.serverDirOrFilename)
if utils.Pathsep != "/" {
// For operating systems that use another path separator for files than for URLs
targetpath = strings.Replace(targetpath, utils.Pathsep, "/", utils.EveryInstance)
}
withSlashPrefix := path.Join("/", targetpath)
L.Push(lua.LString(withSlashPrefix))
return 1 // number of results
}))
// Retrieve a table with keys and values from the form in the request
L.SetGlobal("formdata", L.NewFunction(func(L *lua.LState) int {
// Place the form data in a map
m := make(map[string]string)
req.ParseForm()
for key, values := range req.Form {
m[key] = values[0]
}
// Convert the map to a table and return it
L.Push(convert.Map2table(L, m))
return 1 // number of results
}))
// Retrieve a table with keys and values from the URL in the request
L.SetGlobal("urldata", L.NewFunction(func(L *lua.LState) int {
var valueMap url.Values
var err error
if L.GetTop() == 1 {
// If given an argument
rawurl := L.ToString(1)
valueMap, err = url.ParseQuery(rawurl)
// Log error as warning if there are issues.
// An empty Value map will then be used.
if err != nil {
log.Error(err)
// return 0
}
} else {
// If not given an argument
valueMap = req.URL.Query() // map[string][]string
}
// Place the Value data in a map, using the first values
// if there are many values for a given key.
m := make(map[string]string)
for key, values := range valueMap {
m[key] = values[0]
}
// Convert the map to a table and return it
L.Push(convert.Map2table(L, m))
return 1 // number of results
}))
// Redirect a request (as found, by default)
L.SetGlobal("redirect", L.NewFunction(func(L *lua.LState) int {
newurl := L.ToString(1)
httpStatusCode := http.StatusFound
if L.GetTop() == 2 {
httpStatusCode = int(L.ToNumber(2))
}
if httpStatus != nil {
httpStatus.code = httpStatusCode
}
http.Redirect(w, req, newurl, httpStatusCode)
return 0 // number of results
}))
// Permanently redirect a request, which is the same as redirect(url, 301)
L.SetGlobal("permanent_redirect", L.NewFunction(func(L *lua.LState) int {
newurl := L.ToString(1)
httpStatusCode := http.StatusMovedPermanently
if httpStatus != nil {
httpStatus.code = httpStatusCode
}
http.Redirect(w, req, newurl, httpStatusCode)
return 0 // number of results
}))
// Run the given Lua file (replacement for the built-in dofile, to look in the right directory)
// Returns whatever the Lua file returns when it is being run.
L.SetGlobal("dofile", L.NewFunction(func(L *lua.LState) int {
givenFilename := L.ToString(1)
luaFilename := filepath.Join(filepath.Dir(filename), givenFilename)
if !ac.fs.Exists(luaFilename) {
log.Error("Could not find:", luaFilename)
return 0 // number of results
}
if err := L.DoFile(luaFilename); err != nil {
log.Errorf("Error running %s: %s\n", luaFilename, err)
return 0 // number of results
}
// Retrieve the returned value from the script
retval := L.Get(-1)
L.Pop(1)
// Return the value returned from the script
L.Push(retval)
return 1 // number of results
}))
}