lua.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. package engine
  2. import (
  3. "github.com/xyproto/algernon/lua/mysql"
  4. "html/template"
  5. "net/http"
  6. "path/filepath"
  7. "strconv"
  8. log "github.com/sirupsen/logrus"
  9. "github.com/xyproto/algernon/cachemode"
  10. "github.com/xyproto/algernon/lua/codelib"
  11. "github.com/xyproto/algernon/lua/convert"
  12. "github.com/xyproto/algernon/lua/datastruct"
  13. "github.com/xyproto/algernon/lua/httpclient"
  14. "github.com/xyproto/algernon/lua/jnode"
  15. "github.com/xyproto/algernon/lua/mssql"
  16. "github.com/xyproto/algernon/lua/onthefly"
  17. "github.com/xyproto/algernon/lua/pquery"
  18. "github.com/xyproto/algernon/lua/pure"
  19. "github.com/xyproto/algernon/lua/upload"
  20. "github.com/xyproto/algernon/lua/users"
  21. "github.com/xyproto/algernon/utils"
  22. "github.com/xyproto/gluamapper"
  23. lua "github.com/xyproto/gopher-lua"
  24. )
  25. // LoadCommonFunctions adds most of the available Lua functions in algernon to
  26. // the given Lua state struct
  27. func (ac *Config) LoadCommonFunctions(w http.ResponseWriter, req *http.Request, filename string, L *lua.LState, flushFunc func(), httpStatus *FutureStatus) {
  28. // Make basic functions, like print, available to the Lua script.
  29. // Only exports functions that can relate to HTTP responses or requests.
  30. ac.LoadBasicWeb(w, req, L, filename, flushFunc, httpStatus)
  31. // Make other basic functions available
  32. ac.LoadBasicSystemFunctions(L)
  33. // Functions for rendering markdown or amber
  34. ac.LoadRenderFunctions(w, req, L)
  35. // If there is a database backend
  36. if ac.perm != nil {
  37. // Retrieve the userstate
  38. userstate := ac.perm.UserState()
  39. // Set the cookie secret, if set
  40. if ac.cookieSecret != "" {
  41. userstate.SetCookieSecret(ac.cookieSecret)
  42. }
  43. // Functions for serving files in the same directory as a script
  44. ac.LoadServeFile(w, req, L, filename)
  45. // Functions mainly for adding admin prefixes and configuring permissions
  46. ac.LoadServerConfigFunctions(L, filename)
  47. // Make the functions related to userstate available to the Lua script
  48. users.Load(w, req, L, userstate)
  49. creator := userstate.Creator()
  50. // Simpleredis data structures
  51. datastruct.LoadList(L, creator)
  52. datastruct.LoadSet(L, creator)
  53. datastruct.LoadHash(L, creator)
  54. datastruct.LoadKeyValue(L, creator)
  55. // For saving and loading Lua functions
  56. codelib.Load(L, creator)
  57. // For executing PostgreSQL queries
  58. pquery.Load(L)
  59. // For executing MSSQL queries
  60. mssql.Load(L)
  61. mysql.Load(L, "mysql")
  62. }
  63. // For handling JSON data
  64. jnode.LoadJSONFunctions(L)
  65. ac.LoadJFile(L, filepath.Dir(filename))
  66. jnode.Load(L)
  67. // Extras
  68. pure.Load(L)
  69. // pprint
  70. // exportREPL(L)
  71. // Plugins
  72. ac.LoadPluginFunctions(L, nil)
  73. // Cache
  74. ac.LoadCacheFunctions(L)
  75. // Pages and Tags
  76. onthefly.Load(L)
  77. // File uploads
  78. upload.Load(L, w, req, filepath.Dir(filename))
  79. // HTTP Client
  80. httpclient.Load(L, ac.serverHeaderName)
  81. }
  82. // RunLua uses a Lua file as the HTTP handler. Also has access to the userstate
  83. // and permissions. Returns an error if there was a problem with running the lua
  84. // script, otherwise nil.
  85. func (ac *Config) RunLua(w http.ResponseWriter, req *http.Request, filename string, flushFunc func(), fust *FutureStatus) error {
  86. // Retrieve a Lua state
  87. L := ac.luapool.Get()
  88. defer ac.luapool.Put(L)
  89. // Warn if the connection is closed before the script has finished.
  90. if ac.verboseMode {
  91. done := make(chan bool)
  92. // Stop the background goroutine when this function returns
  93. // There must be a receiver for the done channel,
  94. // or else this will hang everything!
  95. defer func() {
  96. done <- true
  97. }()
  98. // Set up a background notifier
  99. go func() {
  100. ctx := req.Context()
  101. for {
  102. select {
  103. case <-ctx.Done():
  104. // Client is done
  105. log.Warn("Connection to client closed")
  106. case <-done:
  107. // We are done
  108. return
  109. }
  110. }
  111. }() // Call the goroutine
  112. }
  113. // Export functions to the Lua state
  114. // Flush can be an uninitialized channel, it is handled in the function.
  115. ac.LoadCommonFunctions(w, req, filename, L, flushFunc, fust)
  116. // Run the script and return the error value.
  117. // Logging and/or HTTP response is handled elsewhere.
  118. if filepath.Ext(filename) == ".tl" {
  119. return L.DoString(`
  120. local fname = [[` + filename + `]]
  121. local do_cache = ` + strconv.FormatBool(ac.cacheMode == cachemode.Production) + `
  122. if do_cache and tl.cache[fname] then
  123. tl.cache[fname]()
  124. return
  125. end
  126. local result, err = tl.process(fname)
  127. if err ~= nil then
  128. throw('Teal failed to process file: '..err)
  129. end
  130. if #result.syntax_errors > 0 then
  131. local err = result.syntax_errors[1]
  132. throw(err.filename..':'..err.y..': Teal processing error: '..err.msg, 0)
  133. end
  134. local code, gen_error = tl.pretty_print_ast(result.ast, "5.1")
  135. if gen_error ~= nil then
  136. throw('Teal failed to generate Lua: '..err)
  137. end
  138. local chunk = load(code)
  139. if do_cache then
  140. tl.cache[fname] = chunk
  141. end
  142. chunk()
  143. `)
  144. }
  145. return L.DoFile(filename)
  146. }
  147. /* RunConfiguration runs a Lua file as a configuration script. Also has access
  148. * to the userstate and permissions. Returns an error if there was a problem
  149. * with running the lua script, otherwise nil. perm can be nil, but then several
  150. * Lua functions will not be exposed.
  151. *
  152. * The idea is not to change the Lua struct or the luapool, but to set the
  153. * configuration variables with the given Lua configuration script.
  154. *
  155. * luaHandler is a flag that lets Lua functions like "handle" and "servedir" be available or not.
  156. */
  157. func (ac *Config) RunConfiguration(filename string, mux *http.ServeMux, withHandlerFunctions bool) error {
  158. // Retrieve a Lua state
  159. L := ac.luapool.Get()
  160. // Basic system functions, like log()
  161. ac.LoadBasicSystemFunctions(L)
  162. // If there is a database backend
  163. if ac.perm != nil {
  164. // Retrieve the userstate
  165. userstate := ac.perm.UserState()
  166. // Server configuration functions
  167. ac.LoadServerConfigFunctions(L, filename)
  168. creator := userstate.Creator()
  169. // Simpleredis data structures (could be used for storing server stats)
  170. datastruct.LoadList(L, creator)
  171. datastruct.LoadSet(L, creator)
  172. datastruct.LoadHash(L, creator)
  173. datastruct.LoadKeyValue(L, creator)
  174. // For saving and loading Lua functions
  175. codelib.Load(L, creator)
  176. // For executing PostgreSQL queries
  177. pquery.Load(L)
  178. // For executing MSSQL queries
  179. mssql.Load(L)
  180. }
  181. // For handling JSON data
  182. jnode.LoadJSONFunctions(L)
  183. ac.LoadJFile(L, filepath.Dir(filename))
  184. jnode.Load(L)
  185. // Extras
  186. pure.Load(L)
  187. // Plugins
  188. ac.LoadPluginFunctions(L, nil)
  189. // Cache
  190. ac.LoadCacheFunctions(L)
  191. // Pages and Tags
  192. onthefly.Load(L)
  193. // HTTP Client
  194. httpclient.Load(L, ac.serverHeaderName)
  195. if withHandlerFunctions {
  196. // Lua HTTP handlers
  197. ac.LoadLuaHandlerFunctions(L, filename, mux, false, nil, ac.defaultTheme)
  198. }
  199. // Run the script
  200. if err := L.DoFile(filename); err != nil {
  201. // Close the Lua state
  202. L.Close()
  203. // Logging and/or HTTP response is handled elsewhere
  204. return err
  205. }
  206. // Only put the Lua state back if there were no errors
  207. ac.luapool.Put(L)
  208. return nil
  209. }
  210. /* LuaFunctionMap returns the functions available in the given Lua code as
  211. * functions in a map that can be used by templates.
  212. *
  213. * Note that the lua functions must only accept and return strings
  214. * and that only the first returned value will be accessible.
  215. * The Lua functions may take an optional number of arguments.
  216. */
  217. func (ac *Config) LuaFunctionMap(w http.ResponseWriter, req *http.Request, luadata []byte, filename string) (template.FuncMap, error) {
  218. ac.pongomutex.Lock()
  219. defer ac.pongomutex.Unlock()
  220. // Retrieve a Lua state
  221. L := ac.luapool.Get()
  222. defer ac.luapool.Put(L)
  223. // Prepare an empty map of functions (and variables)
  224. funcs := make(template.FuncMap)
  225. // Give no filename (an empty string will be handled correctly by the function).
  226. ac.LoadCommonFunctions(w, req, filename, L, nil, nil)
  227. // Run the script
  228. if err := L.DoString(string(luadata)); err != nil {
  229. // Close the Lua state
  230. L.Close()
  231. // Logging and/or HTTP response is handled elsewhere
  232. return funcs, err
  233. }
  234. // Extract the available functions from the Lua state
  235. globalTable := L.G.Global
  236. globalTable.ForEach(func(key, value lua.LValue) {
  237. // Check if the current value is a string variable
  238. if luaString, ok := value.(lua.LString); ok {
  239. // Store the variable in the same map as the functions (string -> interface)
  240. // for ease of use together with templates.
  241. funcs[key.String()] = luaString.String()
  242. } else if luaTable, ok := value.(*lua.LTable); ok {
  243. // Convert the table to a map and save it.
  244. // Ignore values of a different type.
  245. mapinterface, _ := convert.Table2map(luaTable, false)
  246. switch m := mapinterface.(type) {
  247. case map[string]string:
  248. funcs[key.String()] = map[string]string(m)
  249. case map[string]int:
  250. funcs[key.String()] = map[string]int(m)
  251. case map[int]string:
  252. funcs[key.String()] = map[int]string(m)
  253. case map[int]int:
  254. funcs[key.String()] = map[int]int(m)
  255. }
  256. // Check if the current value is a function
  257. } else if luaFunc, ok := value.(*lua.LFunction); ok {
  258. // Only export the functions defined in the given Lua code,
  259. // not all the global functions. IsG is true if the function is global.
  260. if !luaFunc.IsG {
  261. functionName := key.String()
  262. // Register the function, with a variable number of string arguments
  263. // Functions returning (string, error) are supported by html.template
  264. funcs[functionName] = func(args ...string) (any, error) {
  265. // Create a brand new Lua state
  266. L2 := ac.luapool.New()
  267. defer L2.Close()
  268. // Set up a new Lua state with the current http.ResponseWriter and *http.Request
  269. ac.LoadCommonFunctions(w, req, filename, L2, nil, nil)
  270. // Push the Lua function to run
  271. L2.Push(luaFunc)
  272. // Push the given arguments
  273. for _, arg := range args {
  274. L2.Push(lua.LString(arg))
  275. }
  276. // Run the Lua function
  277. err := L2.PCall(len(args), lua.MultRet, nil)
  278. if err != nil {
  279. // If calling the function did not work out, return the infostring and error
  280. return utils.Infostring(functionName, args), err
  281. }
  282. // Empty return value if no values were returned
  283. var retval any
  284. // Return the first of the returned arguments, as a string
  285. if L2.GetTop() >= 1 {
  286. lv := L2.Get(-1)
  287. tbl, isTable := lv.(*lua.LTable)
  288. switch {
  289. case isTable:
  290. // lv was a Lua Table
  291. retval = gluamapper.ToGoValue(tbl, gluamapper.Option{
  292. NameFunc: func(s string) string {
  293. return s
  294. },
  295. })
  296. if ac.debugMode && ac.verboseMode {
  297. log.Info(utils.Infostring(functionName, args) + " -> (map)")
  298. }
  299. case lv.Type() == lua.LTString:
  300. // lv is a Lua String
  301. retstr := L2.ToString(1)
  302. retval = retstr
  303. if ac.debugMode && ac.verboseMode {
  304. log.Info(utils.Infostring(functionName, args) + " -> \"" + retstr + "\"")
  305. }
  306. default:
  307. retval = ""
  308. log.Warn("The return type of " + utils.Infostring(functionName, args) + " can't be converted")
  309. }
  310. }
  311. // No return value, return an empty string and nil
  312. return retval, nil
  313. }
  314. }
  315. }
  316. })
  317. // Return the map of functions
  318. return funcs, nil
  319. }