template.lua 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. -- vendored from https://raw.githubusercontent.com/bungle/lua-resty-template/1f9a5c24fc7572dbf5be0b9f8168cc3984b03d24/lib/resty/template.lua
  2. -- only modification: remove / from HTML_ENTITIES to not escape it, and fix the appropriate regex.
  3. --[[
  4. Copyright (c) 2014 - 2017 Aapo Talvensaari
  5. All rights reserved.
  6. Redistribution and use in source and binary forms, with or without modification,
  7. are permitted provided that the following conditions are met:
  8. * Redistributions of source code must retain the above copyright notice, this
  9. list of conditions and the following disclaimer.
  10. * Redistributions in binary form must reproduce the above copyright notice, this
  11. list of conditions and the following disclaimer in the documentation and/or
  12. other materials provided with the distribution.
  13. * Neither the name of the {organization} nor the names of its
  14. contributors may be used to endorse or promote products derived from
  15. this software without specific prior written permission.
  16. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
  17. ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
  18. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  19. DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  20. ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
  21. (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  22. LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
  23. ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  24. (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  25. SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26. --]]
  27. local setmetatable = setmetatable
  28. local loadstring = loadstring
  29. local loadchunk
  30. local tostring = tostring
  31. local setfenv = setfenv
  32. local require = require
  33. local capture
  34. local concat = table.concat
  35. local assert = assert
  36. local prefix
  37. local write = io.write
  38. local pcall = pcall
  39. local phase
  40. local open = io.open
  41. local load = load
  42. local type = type
  43. local dump = string.dump
  44. local find = string.find
  45. local gsub = string.gsub
  46. local byte = string.byte
  47. local null
  48. local sub = string.sub
  49. local ngx = ngx
  50. local jit = jit
  51. local var
  52. local _VERSION = _VERSION
  53. local _ENV = _ENV
  54. local _G = _G
  55. local HTML_ENTITIES = {
  56. ["&"] = "&",
  57. ["<"] = "&lt;",
  58. [">"] = "&gt;",
  59. ['"'] = "&quot;",
  60. ["'"] = "&#39;",
  61. }
  62. local CODE_ENTITIES = {
  63. ["{"] = "&#123;",
  64. ["}"] = "&#125;",
  65. ["&"] = "&amp;",
  66. ["<"] = "&lt;",
  67. [">"] = "&gt;",
  68. ['"'] = "&quot;",
  69. ["'"] = "&#39;",
  70. ["/"] = "&#47;"
  71. }
  72. local VAR_PHASES
  73. local ok, newtab = pcall(require, "table.new")
  74. if not ok then newtab = function() return {} end end
  75. local caching = true
  76. local template = newtab(0, 12)
  77. template._VERSION = "1.9"
  78. template.cache = {}
  79. local function enabled(val)
  80. if val == nil then return true end
  81. return val == true or (val == "1" or val == "true" or val == "on")
  82. end
  83. local function trim(s)
  84. return gsub(gsub(s, "^%s+", ""), "%s+$", "")
  85. end
  86. local function rpos(view, s)
  87. while s > 0 do
  88. local c = sub(view, s, s)
  89. if c == " " or c == "\t" or c == "\0" or c == "\x0B" then
  90. s = s - 1
  91. else
  92. break
  93. end
  94. end
  95. return s
  96. end
  97. local function escaped(view, s)
  98. if s > 1 and sub(view, s - 1, s - 1) == "\\" then
  99. if s > 2 and sub(view, s - 2, s - 2) == "\\" then
  100. return false, 1
  101. else
  102. return true, 1
  103. end
  104. end
  105. return false, 0
  106. end
  107. local function readfile(path)
  108. local file = open(path, "rb")
  109. if not file then return nil end
  110. local content = file:read "*a"
  111. file:close()
  112. return content
  113. end
  114. local function loadlua(path)
  115. return readfile(path) or path
  116. end
  117. local function loadngx(path)
  118. local vars = VAR_PHASES[phase()]
  119. local file, location = path, vars and var.template_location
  120. if sub(file, 1) == "/" then file = sub(file, 2) end
  121. if location and location ~= "" then
  122. if sub(location, -1) == "/" then location = sub(location, 1, -2) end
  123. local res = capture(concat{ location, '/', file})
  124. if res.status == 200 then return res.body end
  125. end
  126. local root = vars and (var.template_root or var.document_root) or prefix
  127. if sub(root, -1) == "/" then root = sub(root, 1, -2) end
  128. return readfile(concat{ root, "/", file }) or path
  129. end
  130. do
  131. if ngx then
  132. VAR_PHASES = {
  133. set = true,
  134. rewrite = true,
  135. access = true,
  136. content = true,
  137. header_filter = true,
  138. body_filter = true,
  139. log = true
  140. }
  141. template.print = ngx.print or write
  142. template.load = loadngx
  143. prefix, var, capture, null, phase = ngx.config.prefix(), ngx.var, ngx.location.capture, ngx.null, ngx.get_phase
  144. if VAR_PHASES[phase()] then
  145. caching = enabled(var.template_cache)
  146. end
  147. else
  148. template.print = write
  149. template.load = loadlua
  150. end
  151. if _VERSION == "Lua 5.1" then
  152. local context = { __index = function(t, k)
  153. return t.context[k] or t.template[k] or _G[k]
  154. end }
  155. if jit then
  156. loadchunk = function(view)
  157. return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
  158. end
  159. else
  160. loadchunk = function(view)
  161. local func = assert(loadstring(view))
  162. setfenv(func, setmetatable({ template = template }, context))
  163. return func
  164. end
  165. end
  166. else
  167. local context = { __index = function(t, k)
  168. return t.context[k] or t.template[k] or _ENV[k]
  169. end }
  170. loadchunk = function(view)
  171. return assert(load(view, nil, nil, setmetatable({ template = template }, context)))
  172. end
  173. end
  174. end
  175. function template.caching(enable)
  176. if enable ~= nil then caching = enable == true end
  177. return caching
  178. end
  179. function template.output(s)
  180. if s == nil or s == null then return "" end
  181. if type(s) == "function" then return template.output(s()) end
  182. return tostring(s)
  183. end
  184. function template.escape(s, c)
  185. if type(s) == "string" then
  186. if c then return gsub(s, "[}{\">/<'&]", CODE_ENTITIES) end
  187. return gsub(s, "[\"><'&]", HTML_ENTITIES)
  188. end
  189. return template.output(s)
  190. end
  191. function template.new(view, layout)
  192. assert(view, "view was not provided for template.new(view, layout).")
  193. local render, compile = template.render, template.compile
  194. if layout then
  195. if type(layout) == "table" then
  196. return setmetatable({ render = function(self, context)
  197. local context = context or self
  198. context.blocks = context.blocks or {}
  199. context.view = compile(view)(context)
  200. layout.blocks = context.blocks or {}
  201. layout.view = context.view or ""
  202. return layout:render()
  203. end }, { __tostring = function(self)
  204. local context = self
  205. context.blocks = context.blocks or {}
  206. context.view = compile(view)(context)
  207. layout.blocks = context.blocks or {}
  208. layout.view = context.view
  209. return tostring(layout)
  210. end })
  211. else
  212. return setmetatable({ render = function(self, context)
  213. local context = context or self
  214. context.blocks = context.blocks or {}
  215. context.view = compile(view)(context)
  216. return render(layout, context)
  217. end }, { __tostring = function(self)
  218. local context = self
  219. context.blocks = context.blocks or {}
  220. context.view = compile(view)(context)
  221. return compile(layout)(context)
  222. end })
  223. end
  224. end
  225. return setmetatable({ render = function(self, context)
  226. return render(view, context or self)
  227. end }, { __tostring = function(self)
  228. return compile(view)(self)
  229. end })
  230. end
  231. function template.precompile(view, path, strip)
  232. local chunk = dump(template.compile(view), strip ~= false)
  233. if path then
  234. local file = open(path, "wb")
  235. file:write(chunk)
  236. file:close()
  237. end
  238. return chunk
  239. end
  240. function template.compile(view, key, plain)
  241. assert(view, "view was not provided for template.compile(view, key, plain).")
  242. if key == "no-cache" then
  243. return loadchunk(template.parse(view, plain)), false
  244. end
  245. key = key or view
  246. local cache = template.cache
  247. if cache[key] then return cache[key], true end
  248. local func = loadchunk(template.parse(view, plain))
  249. if caching then cache[key] = func end
  250. return func, false
  251. end
  252. function template.parse(view, plain)
  253. assert(view, "view was not provided for template.parse(view, plain).")
  254. if not plain then
  255. view = template.load(view)
  256. if byte(view, 1, 1) == 27 then return view end
  257. end
  258. local j = 2
  259. local c = {[[
  260. context=... or {}
  261. local function include(v, c) return template.compile(v)(c or context) end
  262. local ___,blocks,layout={},blocks or {}
  263. ]] }
  264. local i, s = 1, find(view, "{", 1, true)
  265. while s do
  266. local t, p = sub(view, s + 1, s + 1), s + 2
  267. if t == "{" then
  268. local e = find(view, "}}", p, true)
  269. if e then
  270. local z, w = escaped(view, s)
  271. if i < s - w then
  272. c[j] = "___[#___+1]=[=[\n"
  273. c[j+1] = sub(view, i, s - 1 - w)
  274. c[j+2] = "]=]\n"
  275. j=j+3
  276. end
  277. if z then
  278. i = s
  279. else
  280. c[j] = "___[#___+1]=template.escape("
  281. c[j+1] = trim(sub(view, p, e - 1))
  282. c[j+2] = ")\n"
  283. j=j+3
  284. s, i = e + 1, e + 2
  285. end
  286. end
  287. elseif t == "*" then
  288. local e = find(view, "*}", p, true)
  289. if e then
  290. local z, w = escaped(view, s)
  291. if i < s - w then
  292. c[j] = "___[#___+1]=[=[\n"
  293. c[j+1] = sub(view, i, s - 1 - w)
  294. c[j+2] = "]=]\n"
  295. j=j+3
  296. end
  297. if z then
  298. i = s
  299. else
  300. c[j] = "___[#___+1]=template.output("
  301. c[j+1] = trim(sub(view, p, e - 1))
  302. c[j+2] = ")\n"
  303. j=j+3
  304. s, i = e + 1, e + 2
  305. end
  306. end
  307. elseif t == "%" then
  308. local e = find(view, "%}", p, true)
  309. if e then
  310. local z, w = escaped(view, s)
  311. if z then
  312. if i < s - w then
  313. c[j] = "___[#___+1]=[=[\n"
  314. c[j+1] = sub(view, i, s - 1 - w)
  315. c[j+2] = "]=]\n"
  316. j=j+3
  317. end
  318. i = s
  319. else
  320. local n = e + 2
  321. if sub(view, n, n) == "\n" then
  322. n = n + 1
  323. end
  324. local r = rpos(view, s - 1)
  325. if i <= r then
  326. c[j] = "___[#___+1]=[=[\n"
  327. c[j+1] = sub(view, i, r)
  328. c[j+2] = "]=]\n"
  329. j=j+3
  330. end
  331. c[j] = trim(sub(view, p, e - 1))
  332. c[j+1] = "\n"
  333. j=j+2
  334. s, i = n - 1, n
  335. end
  336. end
  337. elseif t == "(" then
  338. local e = find(view, ")}", p, true)
  339. if e then
  340. local z, w = escaped(view, s)
  341. if i < s - w then
  342. c[j] = "___[#___+1]=[=[\n"
  343. c[j+1] = sub(view, i, s - 1 - w)
  344. c[j+2] = "]=]\n"
  345. j=j+3
  346. end
  347. if z then
  348. i = s
  349. else
  350. local f = sub(view, p, e - 1)
  351. local x = find(f, ",", 2, true)
  352. if x then
  353. c[j] = "___[#___+1]=include([=["
  354. c[j+1] = trim(sub(f, 1, x - 1))
  355. c[j+2] = "]=],"
  356. c[j+3] = trim(sub(f, x + 1))
  357. c[j+4] = ")\n"
  358. j=j+5
  359. else
  360. c[j] = "___[#___+1]=include([=["
  361. c[j+1] = trim(f)
  362. c[j+2] = "]=])\n"
  363. j=j+3
  364. end
  365. s, i = e + 1, e + 2
  366. end
  367. end
  368. elseif t == "[" then
  369. local e = find(view, "]}", p, true)
  370. if e then
  371. local z, w = escaped(view, s)
  372. if i < s - w then
  373. c[j] = "___[#___+1]=[=[\n"
  374. c[j+1] = sub(view, i, s - 1 - w)
  375. c[j+2] = "]=]\n"
  376. j=j+3
  377. end
  378. if z then
  379. i = s
  380. else
  381. c[j] = "___[#___+1]=include("
  382. c[j+1] = trim(sub(view, p, e - 1))
  383. c[j+2] = ")\n"
  384. j=j+3
  385. s, i = e + 1, e + 2
  386. end
  387. end
  388. elseif t == "-" then
  389. local e = find(view, "-}", p, true)
  390. if e then
  391. local x, y = find(view, sub(view, s, e + 1), e + 2, true)
  392. if x then
  393. local z, w = escaped(view, s)
  394. if z then
  395. if i < s - w then
  396. c[j] = "___[#___+1]=[=[\n"
  397. c[j+1] = sub(view, i, s - 1 - w)
  398. c[j+2] = "]=]\n"
  399. j=j+3
  400. end
  401. i = s
  402. else
  403. y = y + 1
  404. x = x - 1
  405. if sub(view, y, y) == "\n" then
  406. y = y + 1
  407. end
  408. local b = trim(sub(view, p, e - 1))
  409. if b == "verbatim" or b == "raw" then
  410. if i < s - w then
  411. c[j] = "___[#___+1]=[=[\n"
  412. c[j+1] = sub(view, i, s - 1 - w)
  413. c[j+2] = "]=]\n"
  414. j=j+3
  415. end
  416. c[j] = "___[#___+1]=[=["
  417. c[j+1] = sub(view, e + 2, x)
  418. c[j+2] = "]=]\n"
  419. j=j+3
  420. else
  421. if sub(view, x, x) == "\n" then
  422. x = x - 1
  423. end
  424. local r = rpos(view, s - 1)
  425. if i <= r then
  426. c[j] = "___[#___+1]=[=[\n"
  427. c[j+1] = sub(view, i, r)
  428. c[j+2] = "]=]\n"
  429. j=j+3
  430. end
  431. c[j] = 'blocks["'
  432. c[j+1] = b
  433. c[j+2] = '"]=include[=['
  434. c[j+3] = sub(view, e + 2, x)
  435. c[j+4] = "]=]\n"
  436. j=j+5
  437. end
  438. s, i = y - 1, y
  439. end
  440. end
  441. end
  442. elseif t == "#" then
  443. local e = find(view, "#}", p, true)
  444. if e then
  445. local z, w = escaped(view, s)
  446. if i < s - w then
  447. c[j] = "___[#___+1]=[=[\n"
  448. c[j+1] = sub(view, i, s - 1 - w)
  449. c[j+2] = "]=]\n"
  450. j=j+3
  451. end
  452. if z then
  453. i = s
  454. else
  455. e = e + 2
  456. if sub(view, e, e) == "\n" then
  457. e = e + 1
  458. end
  459. s, i = e - 1, e
  460. end
  461. end
  462. end
  463. s = find(view, "{", s + 1, true)
  464. end
  465. s = sub(view, i)
  466. if s and s ~= "" then
  467. c[j] = "___[#___+1]=[=[\n"
  468. c[j+1] = s
  469. c[j+2] = "]=]\n"
  470. j=j+3
  471. end
  472. c[j] = "return layout and include(layout,setmetatable({view=table.concat(___),blocks=blocks},{__index=context})) or table.concat(___)"
  473. return concat(c)
  474. end
  475. function template.render(view, context, key, plain)
  476. assert(view, "view was not provided for template.render(view, context, key, plain).")
  477. return template.print(template.compile(view, key, plain)(context))
  478. end
  479. return template