libs/core: Reworked some basic libraries to not use package.seeall
libs/json: Implemented own JSON-Decoder (Encoder will follow) modules/rpc: Preliminary implemented RPC-Exports for luci.fs, luci.sys and luci.model.uci
This commit is contained in:
parent
8e6d1e682a
commit
0c5dc7bc77
8 changed files with 476 additions and 521 deletions
|
@ -24,10 +24,12 @@ limitations under the License.
|
|||
|
||||
]]--
|
||||
|
||||
--- LuCI filesystem library.
|
||||
module("luci.fs", package.seeall)
|
||||
local posix = require "posix"
|
||||
local io = require "io"
|
||||
local type = type
|
||||
|
||||
require("posix")
|
||||
--- LuCI filesystem library.
|
||||
module "luci.fs"
|
||||
|
||||
--- Test for file access permission on given path.
|
||||
-- @class function
|
||||
|
|
|
@ -1,519 +1,319 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
|
||||
JSON Encoder and Parser for Lua 5.1
|
||||
|
||||
Copyright <EFBFBD> 2007 Shaun Brown (http://www.chipmunkav.com).
|
||||
All Rights Reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person
|
||||
obtaining a copy of this software to deal in the Software without
|
||||
restriction, including without limitation the rights to use,
|
||||
copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the
|
||||
Software is furnished to do so, subject to the following conditions:
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
If you find this software useful please give www.chipmunkav.com a mention.
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
||||
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
|
||||
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
|
||||
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Usage:
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
-- Lua script:
|
||||
local t = {
|
||||
["name1"] = "value1",
|
||||
["name2"] = {1, false, true, 23.54, "a \021 string"},
|
||||
name3 = Json.Null()
|
||||
}
|
||||
local util = require "luci.util"
|
||||
local ltn12 = require "luci.ltn12"
|
||||
local table = require "table"
|
||||
local coroutine = require "coroutine"
|
||||
|
||||
local json = Json.Encode (t)
|
||||
print (json)
|
||||
--> {"name1":"value1","name3":null,"name2":[1,false,true,23.54,"a \u0015 string"]}
|
||||
local assert = assert
|
||||
local tonumber = tonumber
|
||||
local error = error
|
||||
|
||||
local t = Json.Decode(json)
|
||||
print(t.name2[4])
|
||||
--> 23.54
|
||||
|
||||
Notes:
|
||||
1) Encodable Lua types: string, number, boolean, table, nil
|
||||
2) Use Json.Null() to insert a null value into a Json object
|
||||
3) All control chars are encoded to \uXXXX format eg "\021" encodes to "\u0015"
|
||||
4) All Json \uXXXX chars are decoded to chars (0-255 byte range only)
|
||||
5) Json single line // and /* */ block comments are discarded during decoding
|
||||
6) Numerically indexed Lua arrays are encoded to Json Lists eg [1,2,3]
|
||||
7) Lua dictionary tables are converted to Json objects eg {"one":1,"two":2}
|
||||
8) Json nulls are decoded to Lua nil and treated by Lua in the normal way
|
||||
module "luci.json"
|
||||
|
||||
--]]
|
||||
|
||||
local string = string
|
||||
local math = math
|
||||
local table = table
|
||||
local error = error
|
||||
local tonumber = tonumber
|
||||
local tostring = tostring
|
||||
local type = type
|
||||
local setmetatable = setmetatable
|
||||
local pairs = pairs
|
||||
local ipairs = ipairs
|
||||
local assert = assert
|
||||
local Chipmunk = Chipmunk
|
||||
|
||||
module("luci.json")
|
||||
|
||||
local StringBuilder = {
|
||||
buffer = {}
|
||||
}
|
||||
|
||||
function StringBuilder:New()
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.buffer = {}
|
||||
return o
|
||||
--- Null replacement function
|
||||
-- @return null
|
||||
function null()
|
||||
return null
|
||||
end
|
||||
|
||||
function StringBuilder:Append(s)
|
||||
self.buffer[#self.buffer+1] = s
|
||||
end
|
||||
Decoder = util.class()
|
||||
|
||||
function StringBuilder:ToString()
|
||||
return table.concat(self.buffer)
|
||||
end
|
||||
|
||||
local JsonWriter = {
|
||||
backslashes = {
|
||||
['\b'] = "\\b",
|
||||
['\t'] = "\\t",
|
||||
['\n'] = "\\n",
|
||||
['\f'] = "\\f",
|
||||
['\r'] = "\\r",
|
||||
['"'] = "\\\"",
|
||||
['\\'] = "\\\\",
|
||||
['/'] = "\\/"
|
||||
}
|
||||
}
|
||||
|
||||
function JsonWriter:New()
|
||||
local o = {}
|
||||
o.writer = StringBuilder:New()
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o
|
||||
end
|
||||
|
||||
function JsonWriter:Append(s)
|
||||
self.writer:Append(s)
|
||||
end
|
||||
|
||||
function JsonWriter:ToString()
|
||||
return self.writer:ToString()
|
||||
end
|
||||
|
||||
function JsonWriter:Write(o)
|
||||
local t = type(o)
|
||||
if t == "nil" then
|
||||
self:WriteNil()
|
||||
elseif t == "boolean" then
|
||||
self:WriteString(o)
|
||||
elseif t == "number" then
|
||||
self:WriteString(o)
|
||||
elseif t == "string" then
|
||||
self:ParseString(o)
|
||||
elseif t == "table" then
|
||||
self:WriteTable(o)
|
||||
elseif t == "function" then
|
||||
self:WriteFunction(o)
|
||||
elseif t == "thread" then
|
||||
self:WriteError(o)
|
||||
elseif t == "userdata" then
|
||||
self:WriteError(o)
|
||||
--- Create an LTN12 sink from the decoder object
|
||||
-- @return LTN12 sink
|
||||
function Decoder.sink(self)
|
||||
local sink = coroutine.create(self.dispatch)
|
||||
return function(...)
|
||||
return coroutine.resume(sink, self, ...)
|
||||
end
|
||||
end
|
||||
|
||||
function JsonWriter:WriteNil()
|
||||
self:Append("null")
|
||||
|
||||
--- Get the decoded data packets
|
||||
-- @return Decoded data
|
||||
function Decoder.get(self)
|
||||
return self.data
|
||||
end
|
||||
|
||||
function JsonWriter:WriteString(o)
|
||||
self:Append(tostring(o))
|
||||
end
|
||||
|
||||
function JsonWriter:ParseString(s)
|
||||
self:Append('"')
|
||||
self:Append(string.gsub(s, "[%z%c\\\"/]", function(n)
|
||||
local c = self.backslashes[n]
|
||||
if c then return c end
|
||||
return string.format("\\u%.4X", string.byte(n))
|
||||
end))
|
||||
self:Append('"')
|
||||
end
|
||||
|
||||
function JsonWriter:IsArray(t)
|
||||
local count = 0
|
||||
local isindex = function(k)
|
||||
if type(k) == "number" and k > 0 then
|
||||
if math.floor(k) == k then
|
||||
return true
|
||||
end
|
||||
function Decoder.dispatch(self, chunk, src_err, strict)
|
||||
local robject, object
|
||||
|
||||
while chunk do
|
||||
if #chunk < 1 then
|
||||
chunk = self:fetch()
|
||||
end
|
||||
return false
|
||||
end
|
||||
for k,v in pairs(t) do
|
||||
if not isindex(k) then
|
||||
return false, '{', '}'
|
||||
else
|
||||
count = math.max(count, k)
|
||||
end
|
||||
end
|
||||
return true, '[', ']', count
|
||||
end
|
||||
|
||||
function JsonWriter:WriteTable(t)
|
||||
local ba, st, et, n = self:IsArray(t)
|
||||
self:Append(st)
|
||||
if ba then
|
||||
for i = 1, n do
|
||||
self:Write(t[i])
|
||||
if i < n then
|
||||
self:Append(',')
|
||||
end
|
||||
end
|
||||
else
|
||||
local first = true;
|
||||
for k, v in pairs(t) do
|
||||
if not first then
|
||||
self:Append(',')
|
||||
end
|
||||
first = false;
|
||||
self:ParseString(k)
|
||||
self:Append(':')
|
||||
self:Write(v)
|
||||
end
|
||||
end
|
||||
self:Append(et)
|
||||
end
|
||||
|
||||
function JsonWriter:WriteError(o)
|
||||
error(string.format(
|
||||
"Encoding of %s unsupported",
|
||||
tostring(o)))
|
||||
end
|
||||
|
||||
function JsonWriter:WriteFunction(o)
|
||||
if o == Null then
|
||||
self:WriteNil()
|
||||
else
|
||||
self:WriteError(o)
|
||||
end
|
||||
end
|
||||
|
||||
local StringReader = {
|
||||
s = "",
|
||||
i = 0
|
||||
}
|
||||
|
||||
function StringReader:New(s)
|
||||
local o = {}
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
o.s = s or o.s
|
||||
return o
|
||||
end
|
||||
|
||||
function StringReader:Peek()
|
||||
local i = self.i + 1
|
||||
if i <= #self.s then
|
||||
return string.sub(self.s, i, i)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function StringReader:Next()
|
||||
self.i = self.i+1
|
||||
if self.i <= #self.s then
|
||||
return string.sub(self.s, self.i, self.i)
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function StringReader:All()
|
||||
return self.s
|
||||
end
|
||||
|
||||
local JsonReader = {
|
||||
escapes = {
|
||||
['t'] = '\t',
|
||||
['n'] = '\n',
|
||||
['f'] = '\f',
|
||||
['r'] = '\r',
|
||||
['b'] = '\b',
|
||||
}
|
||||
}
|
||||
|
||||
function JsonReader:New(s)
|
||||
local o = {}
|
||||
o.reader = StringReader:New(s)
|
||||
setmetatable(o, self)
|
||||
self.__index = self
|
||||
return o;
|
||||
end
|
||||
|
||||
function JsonReader:Read()
|
||||
self:SkipWhiteSpace()
|
||||
local peek = self:Peek()
|
||||
if peek == nil then
|
||||
error(string.format(
|
||||
"Nil string: '%s'",
|
||||
self:All()))
|
||||
elseif peek == '{' then
|
||||
return self:ReadObject()
|
||||
elseif peek == '[' then
|
||||
return self:ReadArray()
|
||||
elseif peek == '"' then
|
||||
return self:ReadString()
|
||||
elseif string.find(peek, "[%+%-%d]") then
|
||||
return self:ReadNumber()
|
||||
elseif peek == 't' then
|
||||
return self:ReadTrue()
|
||||
elseif peek == 'f' then
|
||||
return self:ReadFalse()
|
||||
elseif peek == 'n' then
|
||||
return self:ReadNull()
|
||||
elseif peek == '/' then
|
||||
self:ReadComment()
|
||||
return self:Read()
|
||||
else
|
||||
error(string.format(
|
||||
"Invalid input: '%s'",
|
||||
self:All()))
|
||||
end
|
||||
end
|
||||
|
||||
function JsonReader:ReadTrue()
|
||||
self:TestReservedWord{'t','r','u','e'}
|
||||
return true
|
||||
end
|
||||
|
||||
function JsonReader:ReadFalse()
|
||||
self:TestReservedWord{'f','a','l','s','e'}
|
||||
return false
|
||||
end
|
||||
|
||||
function JsonReader:ReadNull()
|
||||
self:TestReservedWord{'n','u','l','l'}
|
||||
return nil
|
||||
end
|
||||
|
||||
function JsonReader:TestReservedWord(t)
|
||||
for i, v in ipairs(t) do
|
||||
if self:Next() ~= v then
|
||||
error(string.format(
|
||||
"Error reading '%s': %s",
|
||||
table.concat(t),
|
||||
self:All()))
|
||||
assert(not strict or chunk, "Unexpected EOS")
|
||||
if not chunk then
|
||||
break
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function JsonReader:ReadNumber()
|
||||
local result = self:Next()
|
||||
local peek = self:Peek()
|
||||
while peek ~= nil and string.find(
|
||||
peek,
|
||||
"[%+%-%d%.eE]") do
|
||||
result = result .. self:Next()
|
||||
peek = self:Peek()
|
||||
end
|
||||
result = tonumber(result)
|
||||
if result == nil then
|
||||
error(string.format(
|
||||
"Invalid number: '%s'",
|
||||
result))
|
||||
else
|
||||
return result
|
||||
end
|
||||
end
|
||||
|
||||
function JsonReader:ReadString()
|
||||
local result = ""
|
||||
assert(self:Next() == '"')
|
||||
while self:Peek() ~= '"' do
|
||||
local ch = self:Next()
|
||||
if ch == '\\' then
|
||||
ch = self:Next()
|
||||
if self.escapes[ch] then
|
||||
ch = self.escapes[ch]
|
||||
|
||||
local parser = nil
|
||||
local char = chunk:sub(1, 1)
|
||||
|
||||
if char == '"' then
|
||||
parser = self.parse_string
|
||||
elseif char == 't' then
|
||||
parser = self.parse_true
|
||||
elseif char == 'f' then
|
||||
parser = self.parse_false
|
||||
elseif char == 'n' then
|
||||
parser = self.parse_null
|
||||
elseif char == '[' then
|
||||
parser = self.parse_array
|
||||
elseif char == '{' then
|
||||
parser = self.parse_object
|
||||
elseif char:match("%s") then
|
||||
parser = self.parse_space
|
||||
elseif char:match("[0-9-]") then
|
||||
parser = self.parse_number
|
||||
end
|
||||
|
||||
if parser then
|
||||
chunk, robject = parser(self, chunk)
|
||||
|
||||
if robject ~= nil then
|
||||
assert(object == nil, "Scope violation: Too many objects")
|
||||
object = robject
|
||||
end
|
||||
end
|
||||
result = result .. ch
|
||||
end
|
||||
assert(self:Next() == '"')
|
||||
local fromunicode = function(m)
|
||||
return string.char(tonumber(m, 16))
|
||||
end
|
||||
return string.gsub(
|
||||
result,
|
||||
"u%x%x(%x%x)",
|
||||
fromunicode)
|
||||
end
|
||||
|
||||
function JsonReader:ReadComment()
|
||||
assert(self:Next() == '/')
|
||||
local second = self:Next()
|
||||
if second == '/' then
|
||||
self:ReadSingleLineComment()
|
||||
elseif second == '*' then
|
||||
self:ReadBlockComment()
|
||||
else
|
||||
error(string.format(
|
||||
"Invalid comment: %s",
|
||||
self:All()))
|
||||
end
|
||||
end
|
||||
|
||||
function JsonReader:ReadBlockComment()
|
||||
local done = false
|
||||
while not done do
|
||||
local ch = self:Next()
|
||||
if ch == '*' and self:Peek() == '/' then
|
||||
done = true
|
||||
end
|
||||
if not done and
|
||||
ch == '/' and
|
||||
self:Peek() == "*" then
|
||||
error(string.format(
|
||||
"Invalid comment: %s, '/*' illegal.",
|
||||
self:All()))
|
||||
end
|
||||
end
|
||||
self:Next()
|
||||
end
|
||||
|
||||
function JsonReader:ReadSingleLineComment()
|
||||
local ch = self:Next()
|
||||
while ch ~= '\r' and ch ~= '\n' do
|
||||
ch = self:Next()
|
||||
end
|
||||
end
|
||||
|
||||
function JsonReader:ReadArray()
|
||||
local result = {}
|
||||
assert(self:Next() == '[')
|
||||
self:SkipWhiteSpace()
|
||||
local done = false
|
||||
if self:Peek() == ']' then
|
||||
done = true;
|
||||
end
|
||||
while not done do
|
||||
local item = self:Read()
|
||||
result[#result+1] = item
|
||||
self:SkipWhiteSpace()
|
||||
if self:Peek() == ']' then
|
||||
done = true
|
||||
end
|
||||
if not done then
|
||||
local ch = self:Next()
|
||||
if ch ~= ',' then
|
||||
error(string.format(
|
||||
"Invalid array: '%s' due to: '%s'",
|
||||
self:All(), ch))
|
||||
|
||||
if strict and object ~= nil then
|
||||
return chunk, object
|
||||
end
|
||||
end
|
||||
end
|
||||
assert(']' == self:Next())
|
||||
return result
|
||||
end
|
||||
|
||||
function JsonReader:ReadObject()
|
||||
local result = {}
|
||||
assert(self:Next() == '{')
|
||||
self:SkipWhiteSpace()
|
||||
local done = false
|
||||
if self:Peek() == '}' then
|
||||
done = true
|
||||
end
|
||||
while not done do
|
||||
local key = self:Read()
|
||||
if type(key) ~= "string" then
|
||||
error(string.format(
|
||||
"Invalid non-string object key: %s",
|
||||
key))
|
||||
end
|
||||
self:SkipWhiteSpace()
|
||||
local ch = self:Next()
|
||||
if ch ~= ':' then
|
||||
error(string.format(
|
||||
"Invalid object: '%s' due to: '%s'",
|
||||
self:All(),
|
||||
ch))
|
||||
end
|
||||
self:SkipWhiteSpace()
|
||||
local val = self:Read()
|
||||
result[key] = val
|
||||
self:SkipWhiteSpace()
|
||||
if self:Peek() == '}' then
|
||||
done = true
|
||||
end
|
||||
if not done then
|
||||
ch = self:Next()
|
||||
if ch ~= ',' then
|
||||
error(string.format(
|
||||
"Invalid array: '%s' near: '%s'",
|
||||
self:All(),
|
||||
ch))
|
||||
end
|
||||
end
|
||||
end
|
||||
assert(self:Next() == "}")
|
||||
return result
|
||||
end
|
||||
|
||||
function JsonReader:SkipWhiteSpace()
|
||||
local p = self:Peek()
|
||||
while p ~= nil and string.find(p, "[%s/]") do
|
||||
if p == '/' then
|
||||
self:ReadComment()
|
||||
else
|
||||
self:Next()
|
||||
error("Unexpected char '%s'" % char)
|
||||
end
|
||||
p = self:Peek()
|
||||
end
|
||||
|
||||
assert(not src_err, src_err)
|
||||
assert(object ~= nil, "Unexpected EOS")
|
||||
|
||||
self.data = object
|
||||
return chunk, 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", 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.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 == "/" 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"
|
||||
elseif char == "u" then
|
||||
chunk = self:fetch_atleast(chunk, 4)
|
||||
local s1, s2 = chunk:sub(1, 4):match("^([0-9a-fA-F][0-9a-fA-F])([0-9a-fA-F][0-9a-fA-F])$")
|
||||
assert(s1 and s2, "Invalid Unicode character 'U+%s%s'" % {s1, s2})
|
||||
s1, s2 = tonumber(s1, 16), tonumber(s2, 16)
|
||||
|
||||
-- ToDo: Unicode support
|
||||
return chunk:sub(5), s1 == 0 and s2 or ""
|
||||
else
|
||||
error("Unexpected escaping sequence '\\%s'" % char)
|
||||
end
|
||||
end
|
||||
|
||||
function JsonReader:Peek()
|
||||
return self.reader:Peek()
|
||||
|
||||
function Decoder.parse_array(self, chunk)
|
||||
chunk = chunk:sub(2)
|
||||
local array = {}
|
||||
|
||||
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, object)
|
||||
|
||||
chunk, object = self:parse_delimiter(chunk, ",%]")
|
||||
assert(object, "Delimiter expected")
|
||||
until object == "]"
|
||||
|
||||
return chunk, array
|
||||
end
|
||||
|
||||
function JsonReader:Next()
|
||||
return self.reader:Next()
|
||||
|
||||
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 JsonReader:All()
|
||||
return self.reader:All()
|
||||
end
|
||||
|
||||
function Encode(o)
|
||||
local writer = JsonWriter:New()
|
||||
writer:Write(o)
|
||||
return writer:ToString()
|
||||
end
|
||||
|
||||
function Decode(s)
|
||||
local reader = JsonReader:New(s)
|
||||
local object = reader:Read()
|
||||
reader:SkipWhiteSpace()
|
||||
assert(reader:Peek() == nil, "Invalid characters after JSON body")
|
||||
return object
|
||||
end
|
||||
|
||||
function Null()
|
||||
return Null
|
||||
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
|
|
@ -24,14 +24,26 @@ limitations under the License.
|
|||
|
||||
]]--
|
||||
|
||||
|
||||
local io = require "io"
|
||||
local os = require "os"
|
||||
local posix = require "posix"
|
||||
local table = require "table"
|
||||
|
||||
local luci = {}
|
||||
luci.util = require "luci.util"
|
||||
luci.fs = require "luci.fs"
|
||||
luci.ip = require "luci.ip"
|
||||
|
||||
local tonumber, ipairs, pairs = tonumber, ipairs, pairs
|
||||
|
||||
|
||||
--- LuCI Linux and POSIX system utilities.
|
||||
module("luci.sys", package.seeall)
|
||||
require("posix")
|
||||
require("luci.util")
|
||||
require("luci.fs")
|
||||
require("luci.ip")
|
||||
module "luci.sys"
|
||||
|
||||
|
||||
--- Invoke the luci-flash executable to write an image to the flash memory.
|
||||
-- @param image Local path or URL to image file
|
||||
-- @param kpattern Pattern of files to keep over flash process
|
||||
-- @return Return value of os.execute()
|
||||
function flash(image, kpattern)
|
||||
|
|
|
@ -23,11 +23,13 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
|
||||
]]--
|
||||
local uci = require("uci")
|
||||
local util = require("luci.util")
|
||||
local uci = require "uci"
|
||||
local util = require "luci.util"
|
||||
local table = require "table"
|
||||
|
||||
local setmetatable, rawget, rawset = setmetatable, rawget, rawset
|
||||
local error, pairs, ipairs, tostring = error, pairs, ipairs, tostring
|
||||
local table = table
|
||||
local require = require
|
||||
|
||||
--- LuCI UCI model library.
|
||||
module("luci.model.uci", function(m) setmetatable(m, {__index = uci}) end)
|
||||
|
@ -37,6 +39,14 @@ confdir_default = "/etc/config"
|
|||
|
||||
savedir_state = "/var/state"
|
||||
|
||||
|
||||
--- Applies the new config
|
||||
-- @param config UCI config
|
||||
function apply(config)
|
||||
local conf = require "luci.config"
|
||||
return conf.uci_oncommit[config] and os.execute(conf.uci_oncommit[config])
|
||||
end
|
||||
|
||||
--- Delete all sections of a given type that match certain criteria.
|
||||
-- @param config UCI config
|
||||
-- @param type UCI section type
|
||||
|
@ -149,7 +159,6 @@ function get_list(config, section, option)
|
|||
end
|
||||
|
||||
--- Set given values as list.
|
||||
-- Warning: This function is unsave! You should use save_config or save_state if possible.
|
||||
-- @param config UCI config
|
||||
-- @param section UCI section name
|
||||
-- @param option UCI option
|
||||
|
@ -244,7 +253,6 @@ end
|
|||
-- @see unload
|
||||
|
||||
--- Set a value or create a named section.
|
||||
-- Warning: This function is unsave! You should use save_config or save_state if possible.
|
||||
-- @class function
|
||||
-- @name set
|
||||
-- @param config UCI config
|
||||
|
|
|
@ -187,13 +187,15 @@ function dispatch(request)
|
|||
|
||||
if not luci.util.contains(accs, user) then
|
||||
if authen then
|
||||
local user = authen(luci.sys.user.checkpasswd, accs, def)
|
||||
local user, sess = authen(luci.sys.user.checkpasswd, accs, def)
|
||||
if not user or not luci.util.contains(accs, user) then
|
||||
return
|
||||
else
|
||||
local sid = luci.sys.uniqueid(16)
|
||||
local sid = sess or luci.sys.uniqueid(16)
|
||||
luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
|
||||
luci.sauth.write(sid, user)
|
||||
if not sess then
|
||||
luci.sauth.write(sid, user)
|
||||
end
|
||||
end
|
||||
else
|
||||
luci.http.status(403, "Forbidden")
|
||||
|
|
|
@ -12,15 +12,20 @@ You may obtain a copy of the License at
|
|||
|
||||
$Id$
|
||||
]]--
|
||||
module("luci.controller.rpc", package.seeall)
|
||||
|
||||
local require = require
|
||||
local pairs = pairs
|
||||
local print = print
|
||||
|
||||
module "luci.controller.rpc"
|
||||
|
||||
function index()
|
||||
local function authenticator(validator, accs)
|
||||
local args = luci.dispatcher.context.args
|
||||
if args and #args > 0 then
|
||||
local user = luci.sauth.read(args[1])
|
||||
local auth = luci.http.formvalue("auth", true)
|
||||
if auth then
|
||||
local user = luci.sauth.read(auth)
|
||||
if user and luci.util.contains(accs, user) then
|
||||
return user
|
||||
return user, auth
|
||||
end
|
||||
end
|
||||
luci.http.status(403, "Forbidden")
|
||||
|
@ -29,16 +34,25 @@ function index()
|
|||
uci = entry({"rpc", "uci"}, call("rpc_uci"))
|
||||
uci.sysauth = "root"
|
||||
uci.sysauth_authenticator = authenticator
|
||||
uci.leaf = true
|
||||
|
||||
fs = entry({"rpc", "fs"}, call("rpc_fs"))
|
||||
fs.sysauth = "root"
|
||||
fs.sysauth_authenticator = authenticator
|
||||
|
||||
fs = entry({"rpc", "sys"}, call("rpc_sys"))
|
||||
fs.sysauth = "root"
|
||||
fs.sysauth_authenticator = authenticator
|
||||
|
||||
uci = entry({"rpc", "auth"}, call("rpc_auth"))
|
||||
end
|
||||
|
||||
function rpc_auth()
|
||||
require "luci.jsonrpc"
|
||||
require "luci.sauth"
|
||||
local jsonrpc = require "luci.jsonrpc"
|
||||
local sauth = require "luci.sauth"
|
||||
local http = require "luci.http"
|
||||
local sys = require "luci.sys"
|
||||
|
||||
luci.http.setfilehandler()
|
||||
http.setfilehandler()
|
||||
|
||||
local loginstat
|
||||
|
||||
|
@ -46,21 +60,45 @@ function rpc_auth()
|
|||
server.login = function(user, pass)
|
||||
local sid
|
||||
|
||||
if luci.sys.user.checkpasswd(user, pass) then
|
||||
sid = luci.sys.uniqueid(16)
|
||||
luci.http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
|
||||
luci.sauth.write(sid, user)
|
||||
if sys.user.checkpasswd(user, pass) then
|
||||
sid = sys.uniqueid(16)
|
||||
http.header("Set-Cookie", "sysauth=" .. sid.."; path=/")
|
||||
sauth.write(sid, user)
|
||||
end
|
||||
|
||||
return sid
|
||||
end
|
||||
|
||||
luci.http.prepare_content("application/json")
|
||||
luci.http.write(luci.jsonrpc.handle(server, luci.http.content()))
|
||||
|
||||
return loginstat
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonrpc.handle(server, http.content()))
|
||||
end
|
||||
|
||||
function rpc_uci()
|
||||
local uci = require "luci.controller.rpc.uci"
|
||||
local jsonrpc = require "luci.jsonrpc"
|
||||
local http = require "luci.http"
|
||||
|
||||
http.setfilehandler()
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonrpc.handle(uci, http.content()))
|
||||
end
|
||||
|
||||
function rpc_fs()
|
||||
local fs = require "luci.fs"
|
||||
local jsonrpc = require "luci.jsonrpc"
|
||||
local http = require "luci.http"
|
||||
|
||||
http.setfilehandler()
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonrpc.handle(fs, http.content()))
|
||||
end
|
||||
|
||||
function rpc_sys()
|
||||
local sys = require "luci.sys"
|
||||
local jsonrpc = require "luci.jsonrpc"
|
||||
local http = require "luci.http"
|
||||
|
||||
http.setfilehandler()
|
||||
http.prepare_content("application/json")
|
||||
http.write(jsonrpc.handle(sys, http.content()))
|
||||
end
|
93
modules/rpc/luasrc/controller/rpc/uci.lua
Normal file
93
modules/rpc/luasrc/controller/rpc/uci.lua
Normal file
|
@ -0,0 +1,93 @@
|
|||
--[[
|
||||
LuCI - Lua Configuration Interface
|
||||
|
||||
Copyright 2008 Steven Barth <steven@midlink.org>
|
||||
Copyright 2008 Jo-Philipp Wich <xm@leipzig.freifunk.net>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
$Id$
|
||||
]]--
|
||||
|
||||
local uci = require "luci.model.uci"
|
||||
local table = require "table"
|
||||
|
||||
|
||||
module "luci.controller.rpc.uci"
|
||||
_M, _PACKAGE, _NAME = nil, nil, nil
|
||||
|
||||
function add(config, ...)
|
||||
uci.load_config(config)
|
||||
local stat = uci.add(config, ...)
|
||||
return uci.save_config(config) and stat
|
||||
end
|
||||
|
||||
function apply(config)
|
||||
return uci.apply(config)
|
||||
end
|
||||
|
||||
function changes(...)
|
||||
return uci.changes(...)
|
||||
end
|
||||
|
||||
function commit(config)
|
||||
return uci.load(config) and uci.commit(config)
|
||||
end
|
||||
|
||||
function delete(config, ...)
|
||||
uci.load(config)
|
||||
return uci.delete(config, ...) and uci.save(config)
|
||||
end
|
||||
|
||||
function delete_all(config, ...)
|
||||
uci.load(config)
|
||||
return uci.delete_all(config, ...) and uci.save(config)
|
||||
end
|
||||
|
||||
function foreach(config, stype)
|
||||
uci.load_config(config)
|
||||
local sections = {}
|
||||
|
||||
return uci.foreach(config, stype, function(section)
|
||||
table.insert(sections, section)
|
||||
end) and sections
|
||||
end
|
||||
|
||||
function get(config, ...)
|
||||
uci.load_config(config)
|
||||
return uci.get(config, ...)
|
||||
end
|
||||
|
||||
function get_all(config, ...)
|
||||
uci.load_config(config)
|
||||
return uci.get_all(config, ...)
|
||||
end
|
||||
|
||||
function get_state(config, ...)
|
||||
uci.load_state(config)
|
||||
return uci.get(config, ...)
|
||||
end
|
||||
|
||||
function revert(config)
|
||||
return uci.load(config) and uci.revert(config)
|
||||
end
|
||||
|
||||
function section(config, ...)
|
||||
uci.load_config(config)
|
||||
return uci.section(config, ...) and uci.save_config(config)
|
||||
end
|
||||
|
||||
function set(config, ...)
|
||||
uci.load_config(config)
|
||||
return uci.set(config, ...) and uci.save_config(config)
|
||||
end
|
||||
|
||||
function tset(config, ...)
|
||||
uci.load_config(config)
|
||||
return uci.tset(config, ...) and uci.save_config(config)
|
||||
end
|
||||
|
|
@ -23,12 +23,12 @@ function resolve(mod, method)
|
|||
if not type(mod) == "table" then
|
||||
break
|
||||
end
|
||||
mod = mod[path[j]]
|
||||
mod = rawget(mod, path[j])
|
||||
if not mod then
|
||||
break
|
||||
end
|
||||
end
|
||||
mod = type(mod) == "table" and mod[path[#path]] or nil
|
||||
mod = type(mod) == "table" and rawget(mod, path[#path]) or nil
|
||||
if type(mod) == "function" then
|
||||
return mod
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue