jnode.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. // Package jnode provides Lua functions for dealing with JSON documents and strings
  2. package jnode
  3. import (
  4. "bytes"
  5. "encoding/json"
  6. "io"
  7. "net/http" // For sending JSON requests
  8. "strings"
  9. log "github.com/sirupsen/logrus"
  10. "github.com/xyproto/gluamapper"
  11. lua "github.com/xyproto/gopher-lua"
  12. "github.com/xyproto/jpath"
  13. )
  14. const (
  15. // Class is an identifier for the JNode class in Lua
  16. Class = "JNode"
  17. // Prefix when indenting JSON
  18. indentPrefix = ""
  19. )
  20. // Get the first argument, "self", and cast it from userdata to a library (which is really a hash map).
  21. func checkJNode(L *lua.LState) *jpath.Node {
  22. ud := L.CheckUserData(1)
  23. if jnode, ok := ud.Value.(*jpath.Node); ok {
  24. return jnode
  25. }
  26. L.ArgError(1, "JSON node expected")
  27. return nil
  28. }
  29. // Takes a JNode, a JSON path (optional) and JSON data.
  30. // Stores the JSON data. Returns true if successful.
  31. func jnodeAdd(L *lua.LState) int {
  32. jnode := checkJNode(L) // arg 1
  33. top := L.GetTop()
  34. jsonpath := "x"
  35. jsondata := ""
  36. if top == 2 {
  37. jsondata = L.ToString(2)
  38. if jsondata == "" {
  39. L.ArgError(2, "JSON data expected")
  40. }
  41. } else if top == 3 {
  42. jsonpath = L.ToString(2)
  43. // Check for { to help avoid allowing JSON data as a JSON path
  44. if jsonpath == "" || strings.Contains(jsonpath, "{") {
  45. L.ArgError(2, "JSON path expected")
  46. }
  47. jsondata = L.ToString(3)
  48. if jsondata == "" {
  49. L.ArgError(3, "JSON data expected")
  50. }
  51. }
  52. err := jnode.AddJSON(jsonpath, []byte(jsondata))
  53. if err != nil {
  54. if top == 2 || strings.HasPrefix(err.Error(), "invalid character") {
  55. log.Error("JSON data: ", err)
  56. } else {
  57. log.Error(err)
  58. }
  59. }
  60. L.Push(lua.LBool(err == nil))
  61. return 1 // number of results
  62. }
  63. // Takes a JNode and a JSON path.
  64. // Returns a value or an empty string.
  65. func jnodeGetNode(L *lua.LState) int {
  66. jnode := checkJNode(L) // arg 1
  67. jsonpath := L.ToString(2)
  68. if jsonpath == "" {
  69. L.ArgError(2, "JSON path expected")
  70. }
  71. node := jnode.GetNode(jsonpath)
  72. ud := L.NewUserData()
  73. ud.Value = node
  74. L.SetMetatable(ud, L.GetTypeMetatable(Class))
  75. L.Push(ud)
  76. return 1 // number of results
  77. }
  78. // Takes a JNode and a JSON path.
  79. // Returns a value or an empty string.
  80. func jnodeGetString(L *lua.LState) int {
  81. jnode := checkJNode(L) // arg 1
  82. jsonpath := L.ToString(2)
  83. if jsonpath == "" {
  84. L.ArgError(2, "JSON path expected")
  85. }
  86. node := jnode.GetNode(jsonpath)
  87. L.Push(lua.LString(node.String()))
  88. return 1 // number of results
  89. }
  90. // Take a JNode, a JSON path and a string.
  91. // Returns nothing
  92. func jnodeSet(L *lua.LState) int {
  93. jnode := checkJNode(L) // arg 1
  94. jsonpath := L.ToString(2)
  95. if jsonpath == "" {
  96. L.ArgError(2, "JSON path expected")
  97. }
  98. sval := L.ToString(3)
  99. if sval == "" {
  100. L.ArgError(3, "String value expected")
  101. }
  102. jnode.Set(jsonpath, sval)
  103. return 0 // number of results
  104. }
  105. // Take a JNode and a JSON path.
  106. // Remove a key from a map. Return true if successful.
  107. func jnodeDelKey(L *lua.LState) int {
  108. jnode := checkJNode(L) // arg 1
  109. jsonpath := L.ToString(2)
  110. if jsonpath == "" {
  111. L.ArgError(2, "JSON path expected")
  112. }
  113. err := jnode.DelKey(jsonpath)
  114. if err != nil {
  115. log.Error(err)
  116. }
  117. L.Push(lua.LBool(nil == err))
  118. return 1 // number of results
  119. }
  120. // Given a JNode, return the JSON document.
  121. // May return an empty string.
  122. func jnodeJSON(L *lua.LState) int {
  123. jnode := checkJNode(L) // arg 1
  124. data, err := jnode.PrettyJSON()
  125. retval := ""
  126. if err == nil { // ok
  127. retval = string(data)
  128. }
  129. L.Push(lua.LString(retval))
  130. return 1 // number of results
  131. }
  132. // Given a JNode, return the JSON document.
  133. // May return an empty string.
  134. // Not prettily formatted.
  135. func jnodeJSONcompact(L *lua.LState) int {
  136. jnode := checkJNode(L) // arg 1
  137. data, err := jnode.JSON()
  138. retval := ""
  139. if err == nil { // ok
  140. retval = string(data)
  141. }
  142. L.Push(lua.LString(retval))
  143. return 1 // number of results
  144. }
  145. // Send JSON to host. First argument: URL
  146. // Second argument (optional) Auth token.
  147. // Returns a string that starts with FAIL if it fails.
  148. // Returns the HTTP status code if it works out.
  149. func jnodePOSTToURL(L *lua.LState) int {
  150. jnode := checkJNode(L) // arg 1
  151. posturl := L.ToString(2)
  152. if posturl == "" {
  153. L.ArgError(2, "URL for sending a JSON POST requests to expected")
  154. }
  155. if !strings.HasPrefix(posturl, "http") {
  156. L.ArgError(2, "URL must start with http or https")
  157. }
  158. top := L.GetTop()
  159. authtoken := ""
  160. if top == 3 {
  161. // Optional
  162. authtoken = L.ToString(3)
  163. }
  164. // Render JSON
  165. jsonData, err := jnode.JSON()
  166. if err != nil {
  167. L.Push(lua.LString("FAIL: " + err.Error()))
  168. return 1 // number of results
  169. }
  170. // Set up request
  171. client := &http.Client{}
  172. req, err := http.NewRequest("POST", posturl, bytes.NewReader(jsonData))
  173. if err != nil {
  174. log.Error(err)
  175. return 0 // number of results
  176. }
  177. if authtoken != "" {
  178. req.Header.Add("Authorization", "auth_token=\""+authtoken+"\"")
  179. }
  180. req.Header.Add("Content-Type", "application/json; charset=utf-8")
  181. // Send request and return result
  182. resp, err := client.Do(req)
  183. if err != nil {
  184. log.Error(err)
  185. return 0 // number of results
  186. }
  187. L.Push(lua.LString(resp.Status))
  188. return 1 // number of results
  189. }
  190. // Send JSON to host. First argument: URL
  191. // Second argument (optional) Auth token.
  192. // Returns a string that starts with FAIL if it fails.
  193. // Returns the HTTP status code if it works out.
  194. func jnodePUTToURL(L *lua.LState) int {
  195. jnode := checkJNode(L) // arg 1
  196. puturl := L.ToString(2)
  197. if puturl == "" {
  198. L.ArgError(2, "URL for sending a JSON PUT requests to expected")
  199. }
  200. if !strings.HasPrefix(puturl, "http") {
  201. L.ArgError(2, "URL must start with http or https")
  202. }
  203. top := L.GetTop()
  204. authtoken := ""
  205. if top == 3 {
  206. // Optional
  207. authtoken = L.ToString(3)
  208. }
  209. // Render JSON
  210. jsonData, err := jnode.JSON()
  211. if err != nil {
  212. L.Push(lua.LString("FAIL: " + err.Error()))
  213. return 1 // number of results
  214. }
  215. // Set up request
  216. client := &http.Client{}
  217. req, err := http.NewRequest("PUT", puturl, bytes.NewReader(jsonData))
  218. if err != nil {
  219. log.Error(err)
  220. return 0 // number of results
  221. }
  222. if authtoken != "" {
  223. req.Header.Add("Authorization", "auth_token=\""+authtoken+"\"")
  224. }
  225. req.Header.Add("Content-Type", "application/json; charset=utf-8")
  226. // Send request and return result
  227. resp, err := client.Do(req)
  228. if err != nil {
  229. log.Error(err)
  230. return 0 // number of results
  231. }
  232. L.Push(lua.LString(resp.Status))
  233. return 1 // number of results
  234. }
  235. // Receive JSON from host. First argument: URL
  236. // Returns a string that starts with FAIL if it fails.
  237. // Fills the current JSON node if it works out.
  238. func jnodeGETFromURL(L *lua.LState) int {
  239. jnode := checkJNode(L) // arg 1
  240. posturl := L.ToString(2)
  241. if posturl == "" {
  242. L.ArgError(2, "URL for sending a JSON POST requests to expected")
  243. }
  244. if !strings.HasPrefix(posturl, "http") {
  245. L.ArgError(2, "URL must start with http or https")
  246. }
  247. // Send request
  248. resp, err := http.Get(posturl)
  249. if err != nil {
  250. log.Error(err.Error())
  251. return 0 // number of results
  252. }
  253. if resp.Status != "200 OK" {
  254. L.Push(lua.LString(resp.Status))
  255. return 1 // number of results
  256. }
  257. bodyData, err := io.ReadAll(resp.Body)
  258. resp.Body.Close()
  259. if err != nil {
  260. log.Error(err)
  261. return 0 // number of results
  262. }
  263. newJnode, err := jpath.New(bodyData)
  264. if err != nil {
  265. log.Error(err)
  266. return 0 // number of results
  267. }
  268. *jnode = *newJnode
  269. L.Push(lua.LString(resp.Status))
  270. return 1 // number of results
  271. }
  272. // Create a new JSON node. JSON data as the first argument is optional.
  273. // Logs an error if the given JSON can't be parsed.
  274. // Always returns a JSON Node.
  275. func constructJNode(L *lua.LState) (*lua.LUserData, error) {
  276. // Create a new JNode
  277. var jnode *jpath.Node
  278. top := L.GetTop()
  279. if top == 1 {
  280. // Optional
  281. jsondata := []byte(L.ToString(1))
  282. var err error
  283. jnode, err = jpath.New(jsondata)
  284. if err != nil {
  285. log.Error(err)
  286. jnode = jpath.NewNode()
  287. }
  288. } else {
  289. jnode = jpath.NewNode()
  290. }
  291. // Create a new userdata struct
  292. ud := L.NewUserData()
  293. ud.Value = jnode
  294. L.SetMetatable(ud, L.GetTypeMetatable(Class))
  295. return ud, nil
  296. }
  297. // The hash map methods that are to be registered
  298. var jnodeMethods = map[string]lua.LGFunction{
  299. "__tostring": jnodeJSON,
  300. "add": jnodeAdd,
  301. "get": jnodeGetNode,
  302. "getstring": jnodeGetString,
  303. "set": jnodeSet,
  304. "delkey": jnodeDelKey,
  305. "pretty": jnodeJSON,
  306. "compact": jnodeJSONcompact,
  307. "send": jnodePOSTToURL,
  308. "POST": jnodePOSTToURL,
  309. "PUT": jnodePUTToURL,
  310. "receive": jnodeGETFromURL,
  311. "GET": jnodeGETFromURL,
  312. }
  313. // Load makes functions related JSON nodes available to the given Lua state
  314. func Load(L *lua.LState) {
  315. // Register the JNode class and the methods that belongs with it.
  316. mt := L.NewTypeMetatable(Class)
  317. mt.RawSetH(lua.LString("__index"), mt)
  318. L.SetFuncs(mt, jnodeMethods)
  319. // The constructor for new Libraries takes only an optional id
  320. L.SetGlobal("JNode", L.NewFunction(func(L *lua.LState) int {
  321. // Construct a new JNode
  322. userdata, err := constructJNode(L)
  323. if err != nil {
  324. log.Error(err)
  325. L.Push(lua.LString(err.Error()))
  326. return 1 // Number of returned values
  327. }
  328. // Return the Lua JNode object
  329. L.Push(userdata)
  330. return 1 // number of results
  331. }))
  332. }
  333. // LoadJSONFunctions makes helper functions for converting to JSON available
  334. func LoadJSONFunctions(L *lua.LState) {
  335. // Lua function for converting a table to JSON (string or int)
  336. toJSON := L.NewFunction(func(L *lua.LState) int {
  337. var (
  338. b []byte
  339. err error
  340. )
  341. table := L.ToTable(1)
  342. if table == nil {
  343. L.ArgError(1, "Expected a table as the first argument")
  344. }
  345. // Convert the Lua table to a map that can be used when converting to JSON (map[string]any)
  346. mapinterface := gluamapper.ToGoValue(table, gluamapper.Option{
  347. NameFunc: func(s string) string {
  348. return s
  349. },
  350. })
  351. //
  352. // NOTE:
  353. // JSON keys in maps are always strings!
  354. // See: https://stackoverflow.com/questions/24284612/failed-to-json-marshal-map-with-non-string-keys
  355. //
  356. // If an optional argument is supplied, indent the given number of spaces
  357. if L.GetTop() == 2 {
  358. indentLevel := L.ToInt(2)
  359. indentString := ""
  360. for i := 0; i < indentLevel; i++ {
  361. indentString += " "
  362. }
  363. b, err = json.MarshalIndent(mapinterface, indentPrefix, indentString)
  364. } else {
  365. b, err = json.Marshal(mapinterface)
  366. }
  367. if err != nil {
  368. log.Error(err)
  369. return 0 // number of results
  370. }
  371. L.Push(lua.LString(string(b)))
  372. return 1 // number of results
  373. })
  374. // Convert a table to JSON
  375. L.SetGlobal("json", toJSON)
  376. // Also add backward compatible aliases for the toJSON function
  377. L.SetGlobal("JSON", toJSON)
  378. L.SetGlobal("toJSON", toJSON)
  379. L.SetGlobal("ToJSON", toJSON)
  380. }