spegling av
https://git.openwrt.org/project/luci.git
synced 2025-01-18 23:45:02 +00:00
09de5a74e1
Fixes the following error when attempting to serialize userdata objects: .../json.lua:81: attempt to call local 'parser' (a nil value) Signed-off-by: Jo-Philipp Wich <jo@mein.io>
535 rader
11 KiB
Lua
535 rader
11 KiB
Lua
-- Copyright 2008 Steven Barth <steven@midlink.org>
|
|
-- Copyright 2008 Jo-Philipp Wich <jow@openwrt.org>
|
|
-- Licensed to the public under the Apache License 2.0.
|
|
|
|
local nixio = require "nixio"
|
|
local util = require "luci.util"
|
|
local table = require "table"
|
|
local string = require "string"
|
|
local coroutine = require "coroutine"
|
|
|
|
local assert = assert
|
|
local tonumber = tonumber
|
|
local tostring = tostring
|
|
local error = error
|
|
local type = type
|
|
local pairs = pairs
|
|
local ipairs = ipairs
|
|
local next = next
|
|
local pcall = pcall
|
|
|
|
local band = nixio.bit.band
|
|
local bor = nixio.bit.bor
|
|
local rshift = nixio.bit.rshift
|
|
local char = string.char
|
|
|
|
local getmetatable = getmetatable
|
|
|
|
module "luci.json"
|
|
|
|
|
|
function decode(json, ...)
|
|
local a = ActiveDecoder(function() return nil end, ...)
|
|
a.chunk = json
|
|
local s, obj = pcall(a.get, a)
|
|
return s and obj or nil
|
|
end
|
|
|
|
|
|
function encode(obj, ...)
|
|
local out = {}
|
|
local e = Encoder(obj, 1, ...):source()
|
|
local chnk, err
|
|
repeat
|
|
chnk, err = e()
|
|
out[#out+1] = chnk
|
|
until not chnk
|
|
return not err and table.concat(out) or nil
|
|
end
|
|
|
|
|
|
function null()
|
|
return null
|
|
end
|
|
|
|
Encoder = util.class()
|
|
|
|
function Encoder.__init__(self, data, buffersize, fastescape)
|
|
self.data = data
|
|
self.buffersize = buffersize or 512
|
|
self.buffer = ""
|
|
self.fastescape = fastescape
|
|
|
|
getmetatable(self).__call = Encoder.source
|
|
end
|
|
|
|
function Encoder.source(self)
|
|
local source = coroutine.create(self.dispatch)
|
|
return function()
|
|
local res, data = coroutine.resume(source, self, self.data, true)
|
|
if res then
|
|
return data
|
|
else
|
|
return nil, data
|
|
end
|
|
end
|
|
end
|
|
|
|
function Encoder.dispatch(self, data, start)
|
|
local parser = self.parsers[type(data)]
|
|
|
|
parser(self, data)
|
|
|
|
if start then
|
|
if #self.buffer > 0 then
|
|
coroutine.yield(self.buffer)
|
|
end
|
|
|
|
coroutine.yield()
|
|
end
|
|
end
|
|
|
|
function Encoder.put(self, chunk)
|
|
if self.buffersize < 2 then
|
|
coroutine.yield(chunk)
|
|
else
|
|
if #self.buffer + #chunk > self.buffersize then
|
|
local written = 0
|
|
local fbuffer = self.buffersize - #self.buffer
|
|
|
|
coroutine.yield(self.buffer .. chunk:sub(written + 1, fbuffer))
|
|
written = fbuffer
|
|
|
|
while #chunk - written > self.buffersize do
|
|
fbuffer = written + self.buffersize
|
|
coroutine.yield(chunk:sub(written + 1, fbuffer))
|
|
written = fbuffer
|
|
end
|
|
|
|
self.buffer = chunk:sub(written + 1)
|
|
else
|
|
self.buffer = self.buffer .. chunk
|
|
end
|
|
end
|
|
end
|
|
|
|
function Encoder.parse_nil(self)
|
|
self:put("null")
|
|
end
|
|
|
|
function Encoder.parse_bool(self, obj)
|
|
self:put(obj and "true" or "false")
|
|
end
|
|
|
|
function Encoder.parse_number(self, obj)
|
|
self:put(tostring(obj))
|
|
end
|
|
|
|
function Encoder.parse_string(self, obj)
|
|
if self.fastescape then
|
|
self:put('"' .. obj:gsub('\\', '\\\\'):gsub('"', '\\"') .. '"')
|
|
else
|
|
self:put('"' ..
|
|
obj:gsub('[%c\\"]',
|
|
function(char)
|
|
return '\\u00%02x' % char:byte()
|
|
end
|
|
)
|
|
.. '"')
|
|
end
|
|
end
|
|
|
|
function Encoder.parse_iter(self, obj)
|
|
if obj == null then
|
|
return self:put("null")
|
|
end
|
|
|
|
if type(obj) == "table" and (#obj == 0 and next(obj)) then
|
|
self:put("{")
|
|
local first = true
|
|
|
|
for key, entry in pairs(obj) do
|
|
if key ~= null then
|
|
first = first or self:put(",")
|
|
first = first and false
|
|
self:parse_string(tostring(key))
|
|
self:put(":")
|
|
self:dispatch(entry)
|
|
end
|
|
end
|
|
|
|
self:put("}")
|
|
else
|
|
self:put("[")
|
|
local first = true
|
|
|
|
if type(obj) == "table" then
|
|
for i=1, #obj do
|
|
first = first or self:put(",")
|
|
first = first and nil
|
|
self:dispatch(obj[i])
|
|
end
|
|
else
|
|
for entry in obj do
|
|
first = first or self:put(",")
|
|
first = first and nil
|
|
self:dispatch(entry)
|
|
end
|
|
end
|
|
|
|
self:put("]")
|
|
end
|
|
end
|
|
|
|
function Encoder.parse_udata(self, obj)
|
|
return self:parse_string(tostring(obj))
|
|
end
|
|
|
|
Encoder.parsers = {
|
|
['nil'] = Encoder.parse_nil,
|
|
['table'] = Encoder.parse_iter,
|
|
['number'] = Encoder.parse_number,
|
|
['string'] = Encoder.parse_string,
|
|
['boolean'] = Encoder.parse_bool,
|
|
['function'] = Encoder.parse_iter,
|
|
['userdata'] = Encoder.parse_udata,
|
|
}
|
|
|
|
|
|
Decoder = util.class()
|
|
|
|
function Decoder.__init__(self, customnull)
|
|
self.cnull = customnull
|
|
getmetatable(self).__call = Decoder.sink
|
|
end
|
|
|
|
function Decoder.sink(self)
|
|
local sink = coroutine.create(self.dispatch)
|
|
return function(...)
|
|
return coroutine.resume(sink, self, ...)
|
|
end
|
|
end
|
|
|
|
|
|
function Decoder.get(self)
|
|
return self.data
|
|
end
|
|
|
|
function Decoder.dispatch(self, chunk, src_err, strict)
|
|
local robject, object
|
|
local oset = false
|
|
|
|
while chunk do
|
|
while chunk and #chunk < 1 do
|
|
chunk = self:fetch()
|
|
end
|
|
|
|
assert(not strict or chunk, "Unexpected EOS")
|
|
if not chunk then break end
|
|
|
|
local char = chunk:sub(1, 1)
|
|
local parser = self.parsers[char]
|
|
or (char:match("%s") and self.parse_space)
|
|
or (char:match("[0-9-]") and self.parse_number)
|
|
or error("Unexpected char '%s'" % char)
|
|
|
|
chunk, robject = parser(self, chunk)
|
|
|
|
if parser ~= self.parse_space then
|
|
assert(not oset, "Scope violation: Too many objects")
|
|
object = robject
|
|
oset = true
|
|
|
|
if strict then
|
|
return chunk, object
|
|
end
|
|
end
|
|
end
|
|
|
|
assert(not src_err, src_err)
|
|
assert(oset, "Unexpected EOS")
|
|
|
|
self.data = object
|
|
end
|
|
|
|
|
|
function Decoder.fetch(self)
|
|
local tself, chunk, src_err = coroutine.yield()
|
|
assert(chunk or not src_err, src_err)
|
|
return chunk
|
|
end
|
|
|
|
|
|
function Decoder.fetch_atleast(self, chunk, bytes)
|
|
while #chunk < bytes do
|
|
local nchunk = self:fetch()
|
|
assert(nchunk, "Unexpected EOS")
|
|
chunk = chunk .. nchunk
|
|
end
|
|
|
|
return chunk
|
|
end
|
|
|
|
|
|
function Decoder.fetch_until(self, chunk, pattern)
|
|
local start = chunk:find(pattern)
|
|
|
|
while not start do
|
|
local nchunk = self:fetch()
|
|
assert(nchunk, "Unexpected EOS")
|
|
chunk = chunk .. nchunk
|
|
start = chunk:find(pattern)
|
|
end
|
|
|
|
return chunk, start
|
|
end
|
|
|
|
|
|
function Decoder.parse_space(self, chunk)
|
|
local start = chunk:find("[^%s]")
|
|
|
|
while not start do
|
|
chunk = self:fetch()
|
|
if not chunk then
|
|
return nil
|
|
end
|
|
start = chunk:find("[^%s]")
|
|
end
|
|
|
|
return chunk:sub(start)
|
|
end
|
|
|
|
|
|
function Decoder.parse_literal(self, chunk, literal, value)
|
|
chunk = self:fetch_atleast(chunk, #literal)
|
|
assert(chunk:sub(1, #literal) == literal, "Invalid character sequence")
|
|
return chunk:sub(#literal + 1), value
|
|
end
|
|
|
|
|
|
function Decoder.parse_null(self, chunk)
|
|
return self:parse_literal(chunk, "null", self.cnull and null)
|
|
end
|
|
|
|
|
|
function Decoder.parse_true(self, chunk)
|
|
return self:parse_literal(chunk, "true", true)
|
|
end
|
|
|
|
|
|
function Decoder.parse_false(self, chunk)
|
|
return self:parse_literal(chunk, "false", false)
|
|
end
|
|
|
|
|
|
function Decoder.parse_number(self, chunk)
|
|
local chunk, start = self:fetch_until(chunk, "[^0-9eE.+-]")
|
|
local number = tonumber(chunk:sub(1, start - 1))
|
|
assert(number, "Invalid number specification")
|
|
return chunk:sub(start), number
|
|
end
|
|
|
|
|
|
function Decoder.parse_string(self, chunk)
|
|
local str = ""
|
|
local object = nil
|
|
assert(chunk:sub(1, 1) == '"', 'Expected "')
|
|
chunk = chunk:sub(2)
|
|
|
|
while true do
|
|
local spos = chunk:find('[\\"]')
|
|
if spos then
|
|
str = str .. chunk:sub(1, spos - 1)
|
|
|
|
local char = chunk:sub(spos, spos)
|
|
if char == '"' then -- String end
|
|
chunk = chunk:sub(spos + 1)
|
|
break
|
|
elseif char == "\\" then -- Escape sequence
|
|
chunk, object = self:parse_escape(chunk:sub(spos))
|
|
str = str .. object
|
|
end
|
|
else
|
|
str = str .. chunk
|
|
chunk = self:fetch()
|
|
assert(chunk, "Unexpected EOS while parsing a string")
|
|
end
|
|
end
|
|
|
|
return chunk, str
|
|
end
|
|
|
|
|
|
function Decoder.utf8_encode(self, s1, s2)
|
|
local n = s1 * 256 + s2
|
|
|
|
if n >= 0 and n <= 0x7F then
|
|
return char(n)
|
|
elseif n >= 0 and n <= 0x7FF then
|
|
return char(
|
|
bor(band(rshift(n, 6), 0x1F), 0xC0),
|
|
bor(band(n, 0x3F), 0x80)
|
|
)
|
|
elseif n >= 0 and n <= 0xFFFF then
|
|
return char(
|
|
bor(band(rshift(n, 12), 0x0F), 0xE0),
|
|
bor(band(rshift(n, 6), 0x3F), 0x80),
|
|
bor(band(n, 0x3F), 0x80)
|
|
)
|
|
elseif n >= 0 and n <= 0x10FFFF then
|
|
return char(
|
|
bor(band(rshift(n, 18), 0x07), 0xF0),
|
|
bor(band(rshift(n, 12), 0x3F), 0x80),
|
|
bor(band(rshift(n, 6), 0x3F), 0x80),
|
|
bor(band(n, 0x3F), 0x80)
|
|
)
|
|
else
|
|
return "?"
|
|
end
|
|
end
|
|
|
|
|
|
function Decoder.parse_escape(self, chunk)
|
|
local str = ""
|
|
chunk = self:fetch_atleast(chunk:sub(2), 1)
|
|
local char = chunk:sub(1, 1)
|
|
chunk = chunk:sub(2)
|
|
|
|
if char == '"' then
|
|
return chunk, '"'
|
|
elseif char == "\\" then
|
|
return chunk, "\\"
|
|
elseif char == "u" then
|
|
chunk = self:fetch_atleast(chunk, 4)
|
|
local s1, s2 = chunk:sub(1, 2), chunk:sub(3, 4)
|
|
s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
|
|
assert(s1 and s2, "Invalid Unicode character")
|
|
|
|
return chunk:sub(5), self:utf8_encode(s1, s2)
|
|
elseif char == "/" then
|
|
return chunk, "/"
|
|
elseif char == "b" then
|
|
return chunk, "\b"
|
|
elseif char == "f" then
|
|
return chunk, "\f"
|
|
elseif char == "n" then
|
|
return chunk, "\n"
|
|
elseif char == "r" then
|
|
return chunk, "\r"
|
|
elseif char == "t" then
|
|
return chunk, "\t"
|
|
else
|
|
error("Unexpected escaping sequence '\\%s'" % char)
|
|
end
|
|
end
|
|
|
|
|
|
function Decoder.parse_array(self, chunk)
|
|
chunk = chunk:sub(2)
|
|
local array = {}
|
|
local nextp = 1
|
|
|
|
local chunk, object = self:parse_delimiter(chunk, "%]")
|
|
|
|
if object then
|
|
return chunk, array
|
|
end
|
|
|
|
repeat
|
|
chunk, object = self:dispatch(chunk, nil, true)
|
|
table.insert(array, nextp, object)
|
|
nextp = nextp + 1
|
|
|
|
chunk, object = self:parse_delimiter(chunk, ",%]")
|
|
assert(object, "Delimiter expected")
|
|
until object == "]"
|
|
|
|
return chunk, array
|
|
end
|
|
|
|
|
|
function Decoder.parse_object(self, chunk)
|
|
chunk = chunk:sub(2)
|
|
local array = {}
|
|
local name
|
|
|
|
local chunk, object = self:parse_delimiter(chunk, "}")
|
|
|
|
if object then
|
|
return chunk, array
|
|
end
|
|
|
|
repeat
|
|
chunk = self:parse_space(chunk)
|
|
assert(chunk, "Unexpected EOS")
|
|
|
|
chunk, name = self:parse_string(chunk)
|
|
|
|
chunk, object = self:parse_delimiter(chunk, ":")
|
|
assert(object, "Separator expected")
|
|
|
|
chunk, object = self:dispatch(chunk, nil, true)
|
|
array[name] = object
|
|
|
|
chunk, object = self:parse_delimiter(chunk, ",}")
|
|
assert(object, "Delimiter expected")
|
|
until object == "}"
|
|
|
|
return chunk, array
|
|
end
|
|
|
|
|
|
function Decoder.parse_delimiter(self, chunk, delimiter)
|
|
while true do
|
|
chunk = self:fetch_atleast(chunk, 1)
|
|
local char = chunk:sub(1, 1)
|
|
if char:match("%s") then
|
|
chunk = self:parse_space(chunk)
|
|
assert(chunk, "Unexpected EOS")
|
|
elseif char:match("[%s]" % delimiter) then
|
|
return chunk:sub(2), char
|
|
else
|
|
return chunk, nil
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
Decoder.parsers = {
|
|
['"'] = Decoder.parse_string,
|
|
['t'] = Decoder.parse_true,
|
|
['f'] = Decoder.parse_false,
|
|
['n'] = Decoder.parse_null,
|
|
['['] = Decoder.parse_array,
|
|
['{'] = Decoder.parse_object
|
|
}
|
|
|
|
|
|
ActiveDecoder = util.class(Decoder)
|
|
|
|
function ActiveDecoder.__init__(self, source, customnull)
|
|
Decoder.__init__(self, customnull)
|
|
self.source = source
|
|
self.chunk = nil
|
|
getmetatable(self).__call = self.get
|
|
end
|
|
|
|
|
|
function ActiveDecoder.get(self)
|
|
local chunk, src_err, object
|
|
if not self.chunk then
|
|
chunk, src_err = self.source()
|
|
else
|
|
chunk = self.chunk
|
|
end
|
|
|
|
self.chunk, object = self:dispatch(chunk, src_err, true)
|
|
return object
|
|
end
|
|
|
|
|
|
function ActiveDecoder.fetch(self)
|
|
local chunk, src_err = self.source()
|
|
assert(chunk or not src_err, src_err)
|
|
return chunk
|
|
end
|